diff --git a/src/main/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemController.java b/src/main/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemController.java index bd05ad66..e3154f76 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemController.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemController.java @@ -17,6 +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.mapper.PostgresDateRange.toPostgresDateRange; @RestController @@ -56,7 +57,7 @@ public class HsBookingItemController implements HsBookingItemsApi { final var entityToSave = mapper.map(body, HsBookingItemEntity.class, RESOURCE_TO_ENTITY_POSTMAPPER); - final var saved = bookingItemRepo.save(entityToSave); + final var saved = bookingItemRepo.save(valid(entityToSave)); final var uri = MvcUriComponentsBuilder.fromController(getClass()) @@ -111,7 +112,7 @@ public class HsBookingItemController implements HsBookingItemsApi { new HsBookingItemEntityPatcher(current).apply(body); - final var saved = bookingItemRepo.save(current); + final var saved = bookingItemRepo.save(valid(current)); final var mapped = mapper.map(saved, HsBookingItemResource.class, ENTITY_TO_RESOURCE_POSTMAPPER); return ResponseEntity.ok(mapped); } diff --git a/src/main/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemEntity.java b/src/main/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemEntity.java index 5eb831de..60dd2935 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemEntity.java @@ -11,6 +11,7 @@ import lombok.NoArgsConstructor; import lombok.Setter; import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorEntity; import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationEntity; +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 +66,7 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify; @Setter @NoArgsConstructor @AllArgsConstructor -public class HsBookingItemEntity implements Stringifyable, RbacObject { +public class HsBookingItemEntity implements Stringifyable, RbacObject, Validatable { private static Stringify stringify = stringify(HsBookingItemEntity.class) .withProp(HsBookingItemEntity::getDebitor) @@ -142,6 +143,16 @@ public class HsBookingItemEntity implements Stringifyable, RbacObject { ":" + caption; } + @Override + public String getPropertiesName() { + return "resources"; + } + + @Override + public Map getProperties() { + return resources; + } + public static RbacView rbac() { return rbacViewFor("bookingItem", HsBookingItemEntity.class) .withIdentityView(SQL.query(""" diff --git a/src/main/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsBookingItemEntityValidators.java b/src/main/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsBookingItemEntityValidators.java new file mode 100644 index 00000000..1f4493e2 --- /dev/null +++ b/src/main/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsBookingItemEntityValidators.java @@ -0,0 +1,50 @@ +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.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; + +@UtilityClass +public class HsBookingItemEntityValidators { + + private static final Map, HsEntityValidator> validators = new HashMap<>(); + static { + register(CLOUD_SERVER, new HsCloudServerBookingItemValidator()); + register(MANAGED_SERVER, new HsManagedServerBookingItemValidator()); + register(MANAGED_WEBSPACE, new HsManagedWebspaceBookingItemValidator()); + } + + private static void register(final Enum type, final HsEntityValidator validator) { + stream(validator.propertyValidators).forEach( entry -> { + entry.verifyConsistency(Map.entry(type, validator)); + }); + validators.put(type, validator); + } + + public static HsEntityValidator forType(final Enum type) { + return validators.get(type); + } + + public static Set> types() { + return validators.keySet(); + } + + public static HsBookingItemEntity valid(final HsBookingItemEntity entityToSave) { + final var violations = HsBookingItemEntityValidators.forType(entityToSave.getType()).validate(entityToSave); + if (!violations.isEmpty()) { + throw new ValidationException(violations.toString()); + } + return entityToSave; + } +} diff --git a/src/main/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsCloudServerBookingItemValidator.java b/src/main/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsCloudServerBookingItemValidator.java new file mode 100644 index 00000000..fa09f2c3 --- /dev/null +++ b/src/main/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsCloudServerBookingItemValidator.java @@ -0,0 +1,22 @@ +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.EnumerationPropertyValidator.enumerationProperty; +import static net.hostsharing.hsadminng.hs.validation.IntegerPropertyValidator.integerProperty; + +class HsCloudServerBookingItemValidator extends HsEntityValidator { + + HsCloudServerBookingItemValidator() { + super( + integerProperty("CPUs").min(1).max(32).required(), + integerProperty("RAM").unit("GB").min(1).max(128).required(), + integerProperty("SSD").unit("GB").min(25).max(1000).step(25).required(), + integerProperty("HDD").unit("GB").min(0).max(4000).step(250).optional(), + integerProperty("Traffic").unit("GB").min(250).max(10000).step(250).required(), + enumerationProperty("SLA-Infrastructure").values("BASIC", "EXT8H", "EXT4H", "EXT2H").optional() + ); + } +} diff --git a/src/main/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsManagedServerBookingItemValidator.java b/src/main/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsManagedServerBookingItemValidator.java new file mode 100644 index 00000000..79c41070 --- /dev/null +++ b/src/main/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsManagedServerBookingItemValidator.java @@ -0,0 +1,28 @@ +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.BooleanPropertyValidator.booleanProperty; +import static net.hostsharing.hsadminng.hs.validation.EnumerationPropertyValidator.enumerationProperty; +import static net.hostsharing.hsadminng.hs.validation.IntegerPropertyValidator.integerProperty; + +class HsManagedServerBookingItemValidator extends HsEntityValidator { + + HsManagedServerBookingItemValidator() { + super( + integerProperty("CPUs").min(1).max(32).required(), + integerProperty("RAM").unit("GB").min(1).max(128).required(), + integerProperty("SSD").unit("GB").min(25).max(1000).step(25).required(), + integerProperty("HDD").unit("GB").min(0).max(4000).step(250).optional(), + integerProperty("Traffic").unit("GB").min(250).max(10000).step(250).required(), + enumerationProperty("SLA-Platform").values("BASIC", "EXT8H", "EXT4H", "EXT2H").optional(), + booleanProperty("SLA-EMail").falseIf("SLA-Platform", "BASIC").optional(), + booleanProperty("SLA-Maria").falseIf("SLA-Platform", "BASIC").optional(), + booleanProperty("SLA-PgSQL").falseIf("SLA-Platform", "BASIC").optional(), + booleanProperty("SLA-Office").falseIf("SLA-Platform", "BASIC").optional(), + booleanProperty("SLA-Web").falseIf("SLA-Platform", "BASIC").optional() + ); + } +} diff --git a/src/main/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsManagedWebspaceBookingItemValidator.java b/src/main/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsManagedWebspaceBookingItemValidator.java new file mode 100644 index 00000000..482d0900 --- /dev/null +++ b/src/main/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsManagedWebspaceBookingItemValidator.java @@ -0,0 +1,24 @@ +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.BooleanPropertyValidator.booleanProperty; +import static net.hostsharing.hsadminng.hs.validation.EnumerationPropertyValidator.enumerationProperty; +import static net.hostsharing.hsadminng.hs.validation.IntegerPropertyValidator.integerProperty; + +class HsManagedWebspaceBookingItemValidator extends HsEntityValidator { + + 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("Daemons").min(0).max(10).optional(), + booleanProperty("Online Office Server").optional() + ); + } +} diff --git a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetController.java b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetController.java index 384fc2e3..57e91ec5 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetController.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetController.java @@ -1,6 +1,5 @@ package net.hostsharing.hsadminng.hs.hosting.asset; -import net.hostsharing.hsadminng.hs.hosting.asset.validator.HsHostingAssetValidator; import net.hostsharing.hsadminng.hs.hosting.generated.api.v1.api.HsHostingAssetsApi; import net.hostsharing.hsadminng.context.Context; @@ -16,11 +15,12 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder; -import jakarta.validation.ValidationException; +import jakarta.persistence.EntityNotFoundException; import java.util.List; import java.util.UUID; import java.util.function.BiConsumer; +import static net.hostsharing.hsadminng.hs.hosting.asset.validators.HsHostingAssetEntityValidators.valid; @RestController public class HsHostingAssetController implements HsHostingAssetsApi { @@ -117,21 +117,17 @@ public class HsHostingAssetController implements HsHostingAssetsApi { new HsHostingAssetEntityPatcher(current).apply(body); - final var saved = assetRepo.save(current); + final var saved = assetRepo.save(valid(current)); final var mapped = mapper.map(saved, HsHostingAssetResource.class); return ResponseEntity.ok(mapped); } - private HsHostingAssetEntity valid(final HsHostingAssetEntity entityToSave) { - final var violations = HsHostingAssetValidator.forType(entityToSave.getType()).validate(entityToSave); - if (!violations.isEmpty()) { - throw new ValidationException(violations.toString()); - } - return entityToSave; - } - - @SuppressWarnings("unchecked") final BiConsumer RESOURCE_TO_ENTITY_POSTMAPPER = (resource, entity) -> { entity.putConfig(KeyValueMap.from(resource.getConfig())); + if (resource.getParentAssetUuid() != null) { + entity.setParentAsset(assetRepo.findByUuid(resource.getParentAssetUuid()) + .orElseThrow(() -> new EntityNotFoundException("ERROR: [400] parentAssetUuid %s not found".formatted( + resource.getParentAssetUuid())))); + } }; } diff --git a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetEntity.java b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetEntity.java index 52466e82..1f4ec01a 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetEntity.java @@ -8,6 +8,7 @@ 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; @@ -40,7 +41,6 @@ import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.CaseDef.inOtherCas import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Column.dependsOnColumn; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.ColumnValue.usingCase; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.ColumnValue.usingDefaultCase; -import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Nullable.NOT_NULL; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Nullable.NULLABLE; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.DELETE; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.INSERT; @@ -61,7 +61,7 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify; @Setter @NoArgsConstructor @AllArgsConstructor -public class HsHostingAssetEntity implements Stringifyable, RbacObject { +public class HsHostingAssetEntity implements Stringifyable, RbacObject, Validatable { private static Stringify stringify = stringify(HsHostingAssetEntity.class) .withProp(HsHostingAssetEntity::getType) @@ -114,6 +114,16 @@ public class HsHostingAssetEntity implements Stringifyable, RbacObject { PatchableMapWrapper.of(configWrapper, (newWrapper) -> {configWrapper = newWrapper; }, config).assign(newConfg); } + @Override + public String getPropertiesName() { + return "config"; + } + + @Override + public Map getProperties() { + return config; + } + @Override public String toString() { return stringify.apply(this); @@ -137,7 +147,7 @@ public class HsHostingAssetEntity implements Stringifyable, RbacObject { .importEntityAlias("bookingItem", HsBookingItemEntity.class, usingDefaultCase(), dependsOnColumn("bookingItemUuid"), directlyFetchedByDependsOnColumn(), - NOT_NULL) + NULLABLE) .switchOnColumn("type", inCaseOf(CLOUD_SERVER.name(), diff --git a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetPropsController.java b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetPropsController.java index 8a3f1523..47852310 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetPropsController.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetPropsController.java @@ -1,6 +1,6 @@ package net.hostsharing.hsadminng.hs.hosting.asset; -import net.hostsharing.hsadminng.hs.hosting.asset.validator.HsHostingAssetValidator; +import net.hostsharing.hsadminng.hs.hosting.asset.validators.HsHostingAssetEntityValidators; import net.hostsharing.hsadminng.hs.hosting.generated.api.v1.api.HsHostingAssetPropsApi; import net.hostsharing.hsadminng.hs.hosting.generated.api.v1.model.HsHostingAssetTypeResource; import org.springframework.http.ResponseEntity; @@ -15,7 +15,7 @@ public class HsHostingAssetPropsController implements HsHostingAssetPropsApi { @Override public ResponseEntity> listAssetTypes() { - final var resource = HsHostingAssetValidator.types().stream() + final var resource = HsHostingAssetEntityValidators.types().stream() .map(Enum::name) .toList(); return ResponseEntity.ok(resource); @@ -25,7 +25,7 @@ public class HsHostingAssetPropsController implements HsHostingAssetPropsApi { public ResponseEntity> listAssetTypeProps( final HsHostingAssetTypeResource assetType) { - final var propValidators = HsHostingAssetValidator.forType(HsHostingAssetType.of(assetType)); + final var propValidators = HsHostingAssetEntityValidators.forType(HsHostingAssetType.of(assetType)); final List> resource = propValidators.properties(); return ResponseEntity.ok(toListOfObjects(resource)); } diff --git a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validator/HsHostingAssetPropertyValidator.java b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validator/HsHostingAssetPropertyValidator.java deleted file mode 100644 index 7e61845f..00000000 --- a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validator/HsHostingAssetPropertyValidator.java +++ /dev/null @@ -1,172 +0,0 @@ -package net.hostsharing.hsadminng.hs.hosting.asset.validator; - -import com.fasterxml.jackson.databind.ObjectMapper; -import lombok.RequiredArgsConstructor; -import lombok.Setter; -import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType; - -import java.util.AbstractMap.SimpleImmutableEntry; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.Objects; - -@RequiredArgsConstructor -public abstract class HsHostingAssetPropertyValidator { - - final Class type; - final String propertyName; - private Boolean required; - - public static Map.Entry defType(K k, V v) { - return new SimpleImmutableEntry<>(k, v); - } - - public HsHostingAssetPropertyValidator required() { - required = Boolean.TRUE; - return this; - } - - public HsHostingAssetPropertyValidator optional() { - required = Boolean.FALSE; - return this; - } - - public final List validate(final Map props) { - final var result = new ArrayList(); - 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 result, final T propValue, final Map props); - - public void verifyConsistency(final Map.Entry typeDef) { - if (required == null ) { - throw new IllegalStateException(typeDef.getKey() + "[" + propertyName + "] not fully initialized, please call either .required() or .optional()" ); - } - } - - public Map toMap(final ObjectMapper mapper) { - final Map map = mapper.convertValue(this, Map.class); - map.put("type", simpleTypeName()); - return map; - } - - protected abstract String simpleTypeName(); -} - -@Setter -class IntegerPropertyValidator extends HsHostingAssetPropertyValidator{ - - 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 result, final Integer propValue, final Map 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"; - } -} - -@Setter -class EnumPropertyValidator extends HsHostingAssetPropertyValidator { - - private String[] values; - - private EnumPropertyValidator(final String propertyName) { - super(String.class, propertyName); - } - - public static EnumPropertyValidator enumerationProperty(final String propertyName) { - return new EnumPropertyValidator(propertyName); - } - - public HsHostingAssetPropertyValidator values(final String... values) { - this.values = values; - return this; - } - - @Override - protected void validate(final ArrayList result, final String propValue, final Map 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"; - } -} - -@Setter -class BooleanPropertyValidator extends HsHostingAssetPropertyValidator { - - private Map.Entry falseIf; - - private BooleanPropertyValidator(final String propertyName) { - super(Boolean.class, propertyName); - } - - public static BooleanPropertyValidator booleanProperty(final String propertyName) { - return new BooleanPropertyValidator(propertyName); - } - - HsHostingAssetPropertyValidator falseIf(final String refPropertyName, final String refPropertyValue) { - this.falseIf = new SimpleImmutableEntry<>(refPropertyName, refPropertyValue); - return this; - } - - @Override - protected void validate(final ArrayList result, final Boolean propValue, final Map props) { - if (falseIf != null && !Objects.equals(props.get(falseIf.getKey()), falseIf.getValue())) { - if (propValue) { - result.add("'" + propertyName + "' is expected to be false because " + - falseIf.getKey()+ "=" + falseIf.getValue() + " but is " + propValue); - } - } - } - - @Override - protected String simpleTypeName() { - return "boolean"; - } -} diff --git a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validator/HsHostingAssetValidator.java b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validator/HsHostingAssetValidator.java deleted file mode 100644 index 1389de21..00000000 --- a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validator/HsHostingAssetValidator.java +++ /dev/null @@ -1,99 +0,0 @@ -package net.hostsharing.hsadminng.hs.hosting.asset.validator; - -import com.fasterxml.jackson.annotation.JsonAutoDetect; -import com.fasterxml.jackson.annotation.PropertyAccessor; -import com.fasterxml.jackson.databind.ObjectMapper; -import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity; -import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import static java.util.Arrays.stream; -import static net.hostsharing.hsadminng.hs.hosting.asset.validator.EnumPropertyValidator.enumerationProperty; -import static net.hostsharing.hsadminng.hs.hosting.asset.validator.HsHostingAssetPropertyValidator.defType; -import static net.hostsharing.hsadminng.hs.hosting.asset.validator.BooleanPropertyValidator.booleanProperty; -import static net.hostsharing.hsadminng.hs.hosting.asset.validator.IntegerPropertyValidator.integerProperty; - -public class HsHostingAssetValidator { - - private static final Map validators = Map.ofEntries( - defType(HsHostingAssetType.CLOUD_SERVER, new HsHostingAssetValidator( - integerProperty("CPUs").min(1).max(32).required(), - integerProperty("RAM").unit("GB").min(1).max(128).required(), - integerProperty("SSD").unit("GB").min(25).max(1000).step(25).required(), - integerProperty("HDD").unit("GB").min(0).max(4000).step(250).optional(), - integerProperty("Traffic").unit("GB").min(250).max(10000).step(250).required(), - enumerationProperty("SLA-Infrastructure").values("BASIC", "EXT8H", "EXT4H", "EXT2H").optional())), - defType(HsHostingAssetType.MANAGED_SERVER, new HsHostingAssetValidator( - integerProperty("CPUs").min(1).max(32).required(), - integerProperty("RAM").unit("GB").min(1).max(128).required(), - integerProperty("SSD").unit("GB").min(25).max(1000).step(25).required(), - integerProperty("HDD").unit("GB").min(0).max(4000).step(250).optional(), - integerProperty("Traffic").unit("GB").min(250).max(10000).step(250).required(), - enumerationProperty("SLA-Platform").values("BASIC", "EXT8H", "EXT4H", "EXT2H").optional(), - booleanProperty("SLA-EMail").falseIf("SLA-Platform", "BASIC").optional(), - booleanProperty("SLA-Maria").falseIf("SLA-Platform", "BASIC").optional(), - booleanProperty("SLA-PgSQL").falseIf("SLA-Platform", "BASIC").optional(), - booleanProperty("SLA-Office").falseIf("SLA-Platform", "BASIC").optional(), - booleanProperty("SLA-Web").falseIf("SLA-Platform", "BASIC").optional())), - defType(HsHostingAssetType.MANAGED_WEBSPACE, new HsHostingAssetValidator( - 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("Daemons").min(0).max(10).optional(), - booleanProperty("Online Office Server").optional()) - )); - static { - validators.entrySet().forEach(typeDef -> { - stream(typeDef.getValue().propertyValidators).forEach( entry -> { - entry.verifyConsistency(typeDef); - }); - }); - } - private final HsHostingAssetPropertyValidator[] propertyValidators; - - public static HsHostingAssetValidator forType(final HsHostingAssetType type) { - return validators.get(type); - } - - HsHostingAssetValidator(final HsHostingAssetPropertyValidator... validators) { - propertyValidators = validators; - } - - public static Set types() { - return validators.keySet(); - } - - public List validate(final HsHostingAssetEntity assetEntity) { - final var result = new ArrayList(); - assetEntity.getConfig().keySet().forEach( givenPropName -> { - if (stream(propertyValidators).map(pv -> pv.propertyName).noneMatch(propName -> propName.equals(givenPropName))) { - result.add("'" + givenPropName + "' is not expected but is '" +assetEntity.getConfig().get(givenPropName) + "'"); - } - }); - stream(propertyValidators).forEach(pv -> { - result.addAll(pv.validate(assetEntity.getConfig())); - }); - return result; - } - - public List> properties() { - final var mapper = new ObjectMapper(); - mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY); - return Arrays.stream(propertyValidators) - .map(propertyValidator -> propertyValidator.toMap(mapper)) - .map(HsHostingAssetValidator::asKeyValueMap) - .toList(); - } - - @SuppressWarnings({ "unchecked", "rawtypes" }) - private static Map asKeyValueMap(final Map map) { - return (Map) map; - } - -} diff --git a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsCloudServerHostingAssetValidator.java b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsCloudServerHostingAssetValidator.java new file mode 100644 index 00000000..8c43dd43 --- /dev/null +++ b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsCloudServerHostingAssetValidator.java @@ -0,0 +1,20 @@ +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.IntegerPropertyValidator.integerProperty; + +class HsCloudServerHostingAssetValidator extends HsEntityValidator { + + public HsCloudServerHostingAssetValidator() { + super( + integerProperty("CPUs").min(1).max(32).required(), + integerProperty("RAM").unit("GB").min(1).max(128).required(), + integerProperty("SSD").unit("GB").min(25).max(1000).step(25).required(), + integerProperty("HDD").unit("GB").min(0).max(4000).step(250).optional(), + integerProperty("Traffic").unit("GB").min(250).max(10000).step(250).required() + ); + } +} diff --git a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsHostingAssetEntityValidators.java b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsHostingAssetEntityValidators.java new file mode 100644 index 00000000..c4eaef0f --- /dev/null +++ b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsHostingAssetEntityValidators.java @@ -0,0 +1,51 @@ +package net.hostsharing.hsadminng.hs.hosting.asset.validators; + +import lombok.experimental.UtilityClass; +import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity; +import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType; +import net.hostsharing.hsadminng.hs.validation.HsEntityValidator; + +import jakarta.validation.ValidationException; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import static java.util.Arrays.stream; +import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.CLOUD_SERVER; +import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_SERVER; +import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_WEBSPACE; + +@UtilityClass +public class HsHostingAssetEntityValidators { + + private static final Map, HsEntityValidator> validators = new HashMap<>(); + static { + register(CLOUD_SERVER, new HsCloudServerHostingAssetValidator()); + register(MANAGED_SERVER, new HsManagedServerHostingAssetValidator()); + register(MANAGED_WEBSPACE, new HsManagedWebspaceHostingAssetValidator()); + } + + private static void register(final Enum type, final HsEntityValidator validator) { + stream(validator.propertyValidators).forEach( entry -> { + entry.verifyConsistency(Map.entry(type, validator)); + }); + validators.put(type, validator); + } + + public static HsEntityValidator forType(final Enum type) { + return validators.get(type); + } + + public static Set> types() { + return validators.keySet(); + } + + + public static HsHostingAssetEntity valid(final HsHostingAssetEntity entityToSave) { + final var violations = HsHostingAssetEntityValidators.forType(entityToSave.getType()).validate(entityToSave); + if (!violations.isEmpty()) { + throw new ValidationException(violations.toString()); + } + return entityToSave; + } +} diff --git a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsManagedServerHostingAssetValidator.java b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsManagedServerHostingAssetValidator.java new file mode 100644 index 00000000..aee10839 --- /dev/null +++ b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsManagedServerHostingAssetValidator.java @@ -0,0 +1,20 @@ +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.IntegerPropertyValidator.integerProperty; + +class HsManagedServerHostingAssetValidator extends HsEntityValidator { + + public HsManagedServerHostingAssetValidator() { + super( + integerProperty("CPUs").min(1).max(32).required(), + integerProperty("RAM").unit("GB").min(1).max(128).required(), + integerProperty("SSD").unit("GB").min(25).max(1000).step(25).required(), + integerProperty("HDD").unit("GB").min(0).max(4000).step(250).optional(), + integerProperty("Traffic").unit("GB").min(250).max(10000).step(250).required() + ); + } +} diff --git a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsManagedWebspaceHostingAssetValidator.java b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsManagedWebspaceHostingAssetValidator.java new file mode 100644 index 00000000..452bb116 --- /dev/null +++ b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsManagedWebspaceHostingAssetValidator.java @@ -0,0 +1,34 @@ +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; + +import static net.hostsharing.hsadminng.hs.validation.IntegerPropertyValidator.integerProperty; + +class HsManagedWebspaceHostingAssetValidator extends HsEntityValidator { + public HsManagedWebspaceHostingAssetValidator() { + 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() + ); + } + + @Override + public List validate(final HsHostingAssetEntity assetEntity) { + final var result = super.validate(assetEntity); + validateIdentifierPattern(result, assetEntity); + + return result; + } + + private static void validateIdentifierPattern(final List result, final HsHostingAssetEntity assetEntity) { + final var expectedIdentifierPattern = "^" + assetEntity.getParentAsset().getBookingItem().getDebitor().getDefaultPrefix() + "[0-9][0-9]$"; + if ( !assetEntity.getIdentifier().matches(expectedIdentifierPattern)) { + result.add("'identifier' expected to match '"+expectedIdentifierPattern+"', but is '" + assetEntity.getIdentifier() + "'"); + } + } +} diff --git a/src/main/java/net/hostsharing/hsadminng/hs/validation/BooleanPropertyValidator.java b/src/main/java/net/hostsharing/hsadminng/hs/validation/BooleanPropertyValidator.java new file mode 100644 index 00000000..2838e0f5 --- /dev/null +++ b/src/main/java/net/hostsharing/hsadminng/hs/validation/BooleanPropertyValidator.java @@ -0,0 +1,42 @@ +package net.hostsharing.hsadminng.hs.validation; + +import lombok.Setter; + +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.Map; +import java.util.Objects; + +@Setter +public class BooleanPropertyValidator extends HsPropertyValidator { + + private Map.Entry falseIf; + + private BooleanPropertyValidator(final String propertyName) { + super(Boolean.class, propertyName); + } + + public static BooleanPropertyValidator booleanProperty(final String propertyName) { + return new BooleanPropertyValidator(propertyName); + } + + public HsPropertyValidator falseIf(final String refPropertyName, final String refPropertyValue) { + this.falseIf = new AbstractMap.SimpleImmutableEntry<>(refPropertyName, refPropertyValue); + return this; + } + + @Override + protected void validate(final ArrayList result, final String propertiesName, final Boolean propValue, final Map 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); + } + } + } + + @Override + protected String simpleTypeName() { + return "boolean"; + } +} diff --git a/src/main/java/net/hostsharing/hsadminng/hs/validation/EnumerationPropertyValidator.java b/src/main/java/net/hostsharing/hsadminng/hs/validation/EnumerationPropertyValidator.java new file mode 100644 index 00000000..329feb74 --- /dev/null +++ b/src/main/java/net/hostsharing/hsadminng/hs/validation/EnumerationPropertyValidator.java @@ -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 EnumerationPropertyValidator extends HsPropertyValidator { + + 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 values(final String... values) { + this.values = values; + return this; + } + + @Override + protected void validate(final ArrayList result, final String propertiesName, final String propValue, final Map 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"; + } +} diff --git a/src/main/java/net/hostsharing/hsadminng/hs/validation/HsEntityValidator.java b/src/main/java/net/hostsharing/hsadminng/hs/validation/HsEntityValidator.java new file mode 100644 index 00000000..43be4d10 --- /dev/null +++ b/src/main/java/net/hostsharing/hsadminng/hs/validation/HsEntityValidator.java @@ -0,0 +1,49 @@ +package net.hostsharing.hsadminng.hs.validation; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.PropertyAccessor; +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import static java.util.Arrays.stream; + +public class HsEntityValidator, T extends Enum> { + + public final HsPropertyValidator[] propertyValidators; + + public HsEntityValidator(final HsPropertyValidator... validators) { + propertyValidators = validators; + } + + public List validate(final E assetEntity) { + final var result = new ArrayList(); + 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; + } + + public List> properties() { + final var mapper = new ObjectMapper(); + mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY); + return Arrays.stream(propertyValidators) + .map(propertyValidator -> propertyValidator.toMap(mapper)) + .map(HsEntityValidator::asKeyValueMap) + .toList(); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + private static Map asKeyValueMap(final Map map) { + return (Map) map; + } + +} diff --git a/src/main/java/net/hostsharing/hsadminng/hs/validation/HsPropertyValidator.java b/src/main/java/net/hostsharing/hsadminng/hs/validation/HsPropertyValidator.java new file mode 100644 index 00000000..891c8a7a --- /dev/null +++ b/src/main/java/net/hostsharing/hsadminng/hs/validation/HsPropertyValidator.java @@ -0,0 +1,67 @@ +package net.hostsharing.hsadminng.hs.validation; + +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.RequiredArgsConstructor; + +import java.util.AbstractMap.SimpleImmutableEntry; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +@RequiredArgsConstructor +public abstract class HsPropertyValidator { + + final Class type; + final String propertyName; + private Boolean required; + + public static Map.Entry defType(K k, V v) { + return new SimpleImmutableEntry<>(k, v); + } + + public HsPropertyValidator required() { + required = Boolean.TRUE; + return this; + } + + public HsPropertyValidator optional() { + required = Boolean.FALSE; + return this; + } + + public final List validate(final String propertiesName, final Map props) { + final var result = new ArrayList(); + 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 result, final String propertiesName, final T propValue, final Map props); + + public void verifyConsistency(final Map.Entry, ?> typeDef) { + if (required == null ) { + throw new IllegalStateException(typeDef.getKey() + "[" + propertyName + "] not fully initialized, please call either .required() or .optional()" ); + } + } + + public Map toMap(final ObjectMapper mapper) { + final Map map = mapper.convertValue(this, Map.class); + map.put("type", simpleTypeName()); + return map; + } + + protected abstract String simpleTypeName(); +} diff --git a/src/main/java/net/hostsharing/hsadminng/hs/validation/IntegerPropertyValidator.java b/src/main/java/net/hostsharing/hsadminng/hs/validation/IntegerPropertyValidator.java new file mode 100644 index 00000000..d6fb85f5 --- /dev/null +++ b/src/main/java/net/hostsharing/hsadminng/hs/validation/IntegerPropertyValidator.java @@ -0,0 +1,42 @@ +package net.hostsharing.hsadminng.hs.validation; + +import lombok.Setter; + +import java.util.ArrayList; +import java.util.Map; + +@Setter +public class IntegerPropertyValidator extends HsPropertyValidator { + + 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 result, final String propertiesName, final Integer propValue, final Map 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"; + } +} diff --git a/src/main/java/net/hostsharing/hsadminng/hs/validation/Validatable.java b/src/main/java/net/hostsharing/hsadminng/hs/validation/Validatable.java new file mode 100644 index 00000000..6f214b04 --- /dev/null +++ b/src/main/java/net/hostsharing/hsadminng/hs/validation/Validatable.java @@ -0,0 +1,13 @@ +package net.hostsharing.hsadminng.hs.validation; + + +import java.util.Map; + +public interface Validatable> { + + + Enum getType(); + + String getPropertiesName(); + Map getProperties(); +} diff --git a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validator/lombok.config b/src/main/java/net/hostsharing/hsadminng/hs/validation/lombok.config similarity index 100% rename from src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validator/lombok.config rename to src/main/java/net/hostsharing/hsadminng/hs/validation/lombok.config diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacgrant/RbacGrantsDiagramService.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacgrant/RbacGrantsDiagramService.java index f4dc2167..2290c948 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacgrant/RbacGrantsDiagramService.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacgrant/RbacGrantsDiagramService.java @@ -8,6 +8,7 @@ import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceContext; import jakarta.validation.constraints.NotNull; import java.io.BufferedWriter; +import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.util.*; @@ -25,6 +26,7 @@ public class RbacGrantsDiagramService { public static void writeToFile(final String title, final String graph, final String fileName) { + new File("doc/temp").mkdirs(); try (BufferedWriter writer = new BufferedWriter(new FileWriter(fileName))) { writer.write(""" ### all grants to %s @@ -192,8 +194,9 @@ public class RbacGrantsDiagramService { return "[" + roleType + "\nref:" + uuid + "]"; } if (refType.equals("perm")) { - final var roleType = idName.split(":")[1]; - return "{{" + roleType + "\nref:" + uuid + "}}"; + final var parts = idName.split(":"); + final var permType = parts[2]; + return "{{" + permType + "\nref:" + uuid + "}}"; } return ""; } @@ -205,7 +208,7 @@ public class RbacGrantsDiagramService { @NotNull private static String cleanId(final String idName) { return idName.replaceAll("@.*", "") - .replace("[", "").replace("]", "").replace("(", "").replace(")", "").replace(",", ""); + .replace("[", "").replace("]", "").replace("(", "").replace(")", "").replace(",", "").replace(">", ":"); } diff --git a/src/main/resources/api-definition/hs-hosting/hs-hosting-asset-schemas.yaml b/src/main/resources/api-definition/hs-hosting/hs-hosting-asset-schemas.yaml index 59696a23..7390c3c8 100644 --- a/src/main/resources/api-definition/hs-hosting/hs-hosting-asset-schemas.yaml +++ b/src/main/resources/api-definition/hs-hosting/hs-hosting-asset-schemas.yaml @@ -53,7 +53,11 @@ components: bookingItemUuid: type: string format: uuid - nullable: false + nullable: true + parentAssetUuid: + type: string + format: uuid + nullable: true type: $ref: '#/components/schemas/HsHostingAssetType' identifier: @@ -72,7 +76,6 @@ components: - type - identifier - caption - - debitorUuid - config additionalProperties: false diff --git a/src/main/resources/db/changelog/6-hs-booking/601-booking-item/6018-hs-booking-item-test-data.sql b/src/main/resources/db/changelog/6-hs-booking/601-booking-item/6018-hs-booking-item-test-data.sql index 21300070..88ada16f 100644 --- a/src/main/resources/db/changelog/6-hs-booking/601-booking-item/6018-hs-booking-item-test-data.sql +++ b/src/main/resources/db/changelog/6-hs-booking/601-booking-item/6018-hs-booking-item-test-data.sql @@ -32,9 +32,9 @@ begin raise notice '- using debitor (%): %', relatedDebitor.uuid, relatedDebitor; insert into hs_booking_item (uuid, debitoruuid, type, caption, validity, resources) - values (uuid_generate_v4(), relatedDebitor.uuid, 'MANAGED_SERVER', 'some ManagedServer', daterange('20221001', null, '[]'), '{ "CPU": 2, "SDD": 512, "extra": 42 }'::jsonb), - (uuid_generate_v4(), relatedDebitor.uuid, 'CLOUD_SERVER', 'some CloudServer', daterange('20230115', '20240415', '[)'), '{ "CPU": 2, "HDD": 1024, "extra": 42 }'::jsonb), - (uuid_generate_v4(), relatedDebitor.uuid, 'PRIVATE_CLOUD', 'some PrivateCloud', daterange('20240401', null, '[]'), '{ "CPU": 10, "SDD": 10240, "HDD": 10240, "extra": 42 }'::jsonb); + values (uuid_generate_v4(), relatedDebitor.uuid, 'MANAGED_SERVER', 'some ManagedServer', daterange('20221001', null, '[]'), '{ "CPUs": 2, "RAM": 8, "SDD": 512, "Traffic": 42 }'::jsonb), + (uuid_generate_v4(), relatedDebitor.uuid, 'CLOUD_SERVER', 'some CloudServer', daterange('20230115', '20240415', '[)'), '{ "CPUs": 2, "RAM": 4, "HDD": 1024, "Traffic": 42 }'::jsonb), + (uuid_generate_v4(), relatedDebitor.uuid, 'PRIVATE_CLOUD', 'some PrivateCloud', daterange('20240401', null, '[]'), '{ "CPUs": 10, "SDD": 10240, "HDD": 10240, "Traffic": 42 }'::jsonb); end; $$; --// diff --git a/src/main/resources/db/changelog/7-hs-hosting/701-hosting-asset/7010-hs-hosting-asset.sql b/src/main/resources/db/changelog/7-hs-hosting/701-hosting-asset/7010-hs-hosting-asset.sql index 57f8b866..4aa9e099 100644 --- a/src/main/resources/db/changelog/7-hs-hosting/701-hosting-asset/7010-hs-hosting-asset.sql +++ b/src/main/resources/db/changelog/7-hs-hosting/701-hosting-asset/7010-hs-hosting-asset.sql @@ -24,12 +24,14 @@ create table if not exists hs_hosting_asset ( uuid uuid unique references RbacObject (uuid), version int not null default 0, - bookingItemUuid uuid not null references hs_booking_item(uuid), + bookingItemUuid uuid null references hs_booking_item(uuid), type HsHostingAssetType not null, parentAssetUuid uuid null references hs_hosting_asset(uuid), identifier varchar(80) not null, caption varchar(80) not null, - config jsonb not null + config jsonb not null, + + constraint chk_hs_hosting_asset_has_booking_item_or_parent_asset check (bookingItemUuid is not null or parentAssetUuid is not null) ); --// diff --git a/src/main/resources/db/changelog/7-hs-hosting/701-hosting-asset/7013-hs-hosting-asset-rbac.sql b/src/main/resources/db/changelog/7-hs-hosting/701-hosting-asset/7013-hs-hosting-asset-rbac.sql index 4924f25e..2495f1ea 100644 --- a/src/main/resources/db/changelog/7-hs-hosting/701-hosting-asset/7013-hs-hosting-asset-rbac.sql +++ b/src/main/resources/db/changelog/7-hs-hosting/701-hosting-asset/7013-hs-hosting-asset-rbac.sql @@ -39,8 +39,6 @@ begin SELECT * FROM hs_hosting_asset WHERE uuid = NEW.parentAssetUuid INTO newParentServer; SELECT * FROM hs_booking_item WHERE uuid = NEW.bookingItemUuid INTO newBookingItem; - assert newBookingItem.uuid is not null, format('newBookingItem must not be null for NEW.bookingItemUuid = %s', NEW.bookingItemUuid); - perform createRoleWithGrants( hsHostingAssetOWNER(NEW), diff --git a/src/main/resources/db/changelog/7-hs-hosting/701-hosting-asset/7018-hs-hosting-asset-test-data.sql b/src/main/resources/db/changelog/7-hs-hosting/701-hosting-asset/7018-hs-hosting-asset-test-data.sql index 496bea15..e8bcbc05 100644 --- a/src/main/resources/db/changelog/7-hs-hosting/701-hosting-asset/7018-hs-hosting-asset-test-data.sql +++ b/src/main/resources/db/changelog/7-hs-hosting/701-hosting-asset/7018-hs-hosting-asset-test-data.sql @@ -44,10 +44,10 @@ begin raise notice 'creating test hosting-asset: %', givenPartnerNumber::text || givenDebitorSuffix::text; raise notice '- using debitor (%): %', relatedDebitor.uuid, relatedDebitor; insert into hs_hosting_asset - (uuid, bookingitemuuid, type, parentAssetUuid, identifier, caption, config) + (uuid, bookingitemuuid, type, parentAssetUuid, identifier, caption, config) values (managedServerUuid, relatedPrivateCloudBookingItem.uuid, 'MANAGED_SERVER', null, 'vm10' || givenDebitorSuffix, 'some ManagedServer', '{ "CPU": 2, "SDD": 512, "extra": 42 }'::jsonb), - (uuid_generate_v4(), relatedPrivateCloudBookingItem.uuid, 'CLOUD_SERVER', null, 'vm20' || givenDebitorSuffix, 'another CloudServer', '{ "CPU": 2, "HDD": 1024, "extra": 42 }'::jsonb), - (uuid_generate_v4(), relatedManagedServerBookingItem.uuid, 'MANAGED_WEBSPACE', managedServerUuid, givenWebspacePrefix || '01', 'some Webspace', '{ "RAM": 1, "SDD": 512, "HDD": 2048, "extra": 42 }'::jsonb); + (uuid_generate_v4(), relatedPrivateCloudBookingItem.uuid, 'CLOUD_SERVER', null, 'vm20' || givenDebitorSuffix, 'another CloudServer', '{ "CPU": 2, "HDD": 1024, "extra": 42 }'::jsonb), + (uuid_generate_v4(), relatedManagedServerBookingItem.uuid, 'MANAGED_WEBSPACE', managedServerUuid, givenWebspacePrefix || '01', 'some Webspace', '{ "RAM": 1, "SDD": 512, "HDD": 2048, "extra": 42 }'::jsonb); end; $$; --// diff --git a/src/test/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemControllerAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemControllerAcceptanceTest.java index e29a93e1..0a92ff3f 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemControllerAcceptanceTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemControllerAcceptanceTest.java @@ -21,6 +21,7 @@ import java.util.Map; import java.util.UUID; import static java.util.Map.entry; +import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.MANAGED_WEBSPACE; import static net.hostsharing.hsadminng.rbac.test.JsonMatcher.lenientlyEquals; import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.Matchers.matchesRegex; @@ -69,37 +70,42 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup .body("", lenientlyEquals(""" [ { - "caption": "some ManagedServer", - "validFrom": "2022-10-01", - "validTo": null, - "resources": { - "CPU": 2, - "SDD": 512, - "extra": 42 - } - }, - { - "caption": "some CloudServer", - "validFrom": "2023-01-15", - "validTo": "2024-04-14", - "resources": { - "CPU": 2, - "HDD": 1024, - "extra": 42 - } - }, - { - "caption": "some PrivateCloud", - "validFrom": "2024-04-01", - "validTo": null, - "resources": { - "CPU": 10, - "HDD": 10240, - "SDD": 10240, - "extra": 42 - } - } - ] + "type": "MANAGED_SERVER", + "caption": "some ManagedServer", + "validFrom": "2022-10-01", + "validTo": null, + "resources": { + "RAM": 8, + "SDD": 512, + "CPUs": 2, + "Traffic": 42 + } + }, + { + "type": "CLOUD_SERVER", + "caption": "some CloudServer", + "validFrom": "2023-01-15", + "validTo": "2024-04-14", + "resources": { + "HDD": 1024, + "RAM": 4, + "CPUs": 2, + "Traffic": 42 + } + }, + { + "type": "PRIVATE_CLOUD", + "caption": "some PrivateCloud", + "validFrom": "2024-04-01", + "validTo": null, + "resources": { + "HDD": 10240, + "SDD": 10240, + "CPUs": 10, + "Traffic": 42 + } + } + ] """)); // @formatter:on } @@ -123,7 +129,7 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup "debitorUuid": "%s", "type": "MANAGED_SERVER", "caption": "some new booking", - "resources": { "CPU": 12, "extra": 42 }, + "resources": { "CPUs": 12, "RAM": 4, "SSD": 100, "Traffic": 250 }, "validFrom": "2022-10-13" } """.formatted(givenDebitor.getUuid())) @@ -139,7 +145,7 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup "caption": "some new booking", "validFrom": "2022-10-13", "validTo": null, - "resources": { "CPU": 12 } + "resources": { "CPUs": 12, "SSD": 100, "Traffic": 250 } } """)) .header("Location", matchesRegex("http://localhost:[1-9][0-9]*/api/hs/booking/items/[^/]*")) @@ -177,7 +183,12 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup "caption": "some CloudServer", "validFrom": "2023-01-15", "validTo": "2024-04-14", - "resources": { CPU: 2, HDD: 1024 } + "resources": { + "HDD": 1024, + "RAM": 4, + "CPUs": 2, + "Traffic": 42 + } } """)); // @formatter:on } @@ -222,7 +233,12 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup "caption": "some CloudServer", "validFrom": "2023-01-15", "validTo": "2024-04-14", - "resources": { CPU: 2, HDD: 1024 } + "resources": { + "HDD": 1024, + "RAM": 4, + "CPUs": 2, + "Traffic": 42 + } } """)); // @formatter:on } @@ -234,7 +250,8 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup @Test void globalAdmin_canPatchAllUpdatablePropertiesOfBookingItem() { - final var givenBookingItem = givenSomeTemporaryBookingItemForDebitorNumber(1000111, entry("something", 1)); + final var givenBookingItem = givenSomeBookingItem(1000111, MANAGED_WEBSPACE, + resource("HDD", 100), resource("SSD", 50), resource("Traffic", 250)); RestAssured // @formatter:off .given() @@ -245,9 +262,9 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup "validFrom": "2020-06-05", "validTo": "2022-12-31", "resources": { - "CPU": "4", + "Traffic": 500, "HDD": null, - "SSD": "4096" + "SSD": 100 } } """) @@ -263,9 +280,8 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup "validFrom": "2022-11-01", "validTo": "2022-12-31", "resources": { - "CPU": "4", - "SSD": "4096", - "something": 1 + "Traffic": 500, + "SSD": 100 } } """)); // @formatter:on @@ -288,7 +304,8 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup @Test void globalAdmin_canDeleteArbitraryBookingItem() { context.define("superuser-alex@hostsharing.net"); - final var givenBookingItem = givenSomeTemporaryBookingItemForDebitorNumber(1000111, entry("something", 1)); + final var givenBookingItem = givenSomeBookingItem(1000111, MANAGED_WEBSPACE, + resource("HDD", 100), resource("SSD", 50), resource("Traffic", 250)); RestAssured // @formatter:off .given() @@ -306,7 +323,8 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup @Test void normalUser_canNotDeleteUnrelatedBookingItem() { context.define("superuser-alex@hostsharing.net"); - final var givenBookingItem = givenSomeTemporaryBookingItemForDebitorNumber(1000111, entry("something", 1)); + final var givenBookingItem = givenSomeBookingItem(1000111, MANAGED_WEBSPACE, + resource("HDD", 100), resource("SSD", 50), resource("Traffic", 250)); RestAssured // @formatter:off .given() @@ -322,15 +340,16 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup } } - private HsBookingItemEntity givenSomeTemporaryBookingItemForDebitorNumber(final int debitorNumber, - final Map.Entry resources) { + @SafeVarargs + private HsBookingItemEntity givenSomeBookingItem(final int debitorNumber, + final HsBookingItemType hsBookingItemType, final Map.Entry... resources) { return jpaAttempt.transacted(() -> { context.define("superuser-alex@hostsharing.net"); final var givenDebitor = debitorRepo.findDebitorByDebitorNumber(debitorNumber).get(0); final var newBookingItem = HsBookingItemEntity.builder() .uuid(UUID.randomUUID()) .debitor(givenDebitor) - .type(HsBookingItemType.MANAGED_WEBSPACE) + .type(hsBookingItemType) .caption("some test-booking") .resources(Map.ofEntries(resources)) .validity(Range.closedOpen( @@ -340,4 +359,8 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup return bookingItemRepo.save(newBookingItem); }).assertSuccessful().returnedValue(); } + + private Map.Entry resource(final String key, final Object value) { + return entry(key, value); + } } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemRepositoryIntegrationTest.java b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemRepositoryIntegrationTest.java index 05d9cbc0..c76d30df 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemRepositoryIntegrationTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemRepositoryIntegrationTest.java @@ -167,9 +167,9 @@ class HsBookingItemRepositoryIntegrationTest extends ContextBasedTestWithCleanup // then allTheseBookingItemsAreReturned( result, - "HsBookingItemEntity(D-1000212, MANAGED_SERVER, [2022-10-01,), some ManagedServer, { CPU: 2, SDD: 512, extra: 42 })", - "HsBookingItemEntity(D-1000212, CLOUD_SERVER, [2023-01-15,2024-04-15), some CloudServer, { CPU: 2, HDD: 1024, extra: 42 })", - "HsBookingItemEntity(D-1000212, PRIVATE_CLOUD, [2024-04-01,), some PrivateCloud, { CPU: 10, HDD: 10240, SDD: 10240, extra: 42 })"); + "HsBookingItemEntity(D-1000212, MANAGED_SERVER, [2022-10-01,), some ManagedServer, { CPUs: 2, RAM: 8, SDD: 512, Traffic: 42 })", + "HsBookingItemEntity(D-1000212, CLOUD_SERVER, [2023-01-15,2024-04-15), some CloudServer, { CPUs: 2, HDD: 1024, RAM: 4, Traffic: 42 })", + "HsBookingItemEntity(D-1000212, PRIVATE_CLOUD, [2024-04-01,), some PrivateCloud, { CPUs: 10, HDD: 10240, SDD: 10240, Traffic: 42 })"); } @Test @@ -184,9 +184,9 @@ class HsBookingItemRepositoryIntegrationTest extends ContextBasedTestWithCleanup // then: exactlyTheseBookingItemsAreReturned( result, - "HsBookingItemEntity(D-1000111, MANAGED_SERVER, [2022-10-01,), some ManagedServer, { CPU: 2, SDD: 512, extra: 42 })", - "HsBookingItemEntity(D-1000111, CLOUD_SERVER, [2023-01-15,2024-04-15), some CloudServer, { CPU: 2, HDD: 1024, extra: 42 })", - "HsBookingItemEntity(D-1000111, PRIVATE_CLOUD, [2024-04-01,), some PrivateCloud, { CPU: 10, HDD: 10240, SDD: 10240, extra: 42 })"); + "HsBookingItemEntity(D-1000111, MANAGED_SERVER, [2022-10-01,), some ManagedServer, { CPUs: 2, RAM: 8, SDD: 512, Traffic: 42 })", + "HsBookingItemEntity(D-1000111, CLOUD_SERVER, [2023-01-15,2024-04-15), some CloudServer, { CPUs: 2, HDD: 1024, RAM: 4, Traffic: 42 })", + "HsBookingItemEntity(D-1000111, PRIVATE_CLOUD, [2024-04-01,), some PrivateCloud, { CPUs: 10, HDD: 10240, SDD: 10240, Traffic: 42 })"); } } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsBookingItemEntityValidatorsUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsBookingItemEntityValidatorsUnitTest.java new file mode 100644 index 00000000..741d7c1e --- /dev/null +++ b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsBookingItemEntityValidatorsUnitTest.java @@ -0,0 +1,44 @@ +package net.hostsharing.hsadminng.hs.booking.item.validators; + +import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity; +import org.junit.jupiter.api.Test; + +import jakarta.validation.ValidationException; + +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.HsBookingItemType.CLOUD_SERVER; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.catchThrowable; + +class HsBookingItemEntityValidatorsUnitTest { + + @Test + void validThrowsException() { + // given + final var cloudServerBookingItemEntity = HsBookingItemEntity.builder() + .type(CLOUD_SERVER) + .build(); + + // when + final var result = catchThrowable( ()-> valid(cloudServerBookingItemEntity) ); + + // 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"); + } + + @Test + void listsTypes() { + // when + final var result = HsBookingItemEntityValidators.types(); + + // then + assertThat(result).containsExactlyInAnyOrder(CLOUD_SERVER, MANAGED_SERVER, MANAGED_WEBSPACE); + } +} diff --git a/src/test/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsCloudServerBookingItemValidatorUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsCloudServerBookingItemValidatorUnitTest.java new file mode 100644 index 00000000..e15b95d7 --- /dev/null +++ b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsCloudServerBookingItemValidatorUnitTest.java @@ -0,0 +1,51 @@ +package net.hostsharing.hsadminng.hs.booking.item.validators; + +import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity; +import org.junit.jupiter.api.Test; + +import java.util.Map; + +import static java.util.Map.entry; +import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.CLOUD_SERVER; +import static net.hostsharing.hsadminng.hs.booking.item.validators.HsBookingItemEntityValidators.forType; +import static org.assertj.core.api.Assertions.assertThat; + +class HsCloudServerBookingItemValidatorUnitTest { + + @Test + void validatesProperties() { + // given + final var validator = HsBookingItemEntityValidators.forType(CLOUD_SERVER); + final var cloudServerBookingItemEntity = HsBookingItemEntity.builder() + .type(CLOUD_SERVER) + .resources(Map.ofEntries( + entry("CPUs", 2), + entry("RAM", 25), + entry("SSD", 25), + entry("Traffic", 250), + entry("SLA-EMail", true) + )) + .build(); + + // when + final var result = validator.validate(cloudServerBookingItemEntity); + + // then + assertThat(result).containsExactly("'resources.SLA-EMail' is not expected but is set to 'true'"); + } + + @Test + void containsAllValidations() { + // when + final var validator = forType(CLOUD_SERVER); + + // 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]}"); + } +} diff --git a/src/test/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsManagedServerBookingItemValidatorUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsManagedServerBookingItemValidatorUnitTest.java new file mode 100644 index 00000000..5f2bdfc3 --- /dev/null +++ b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsManagedServerBookingItemValidatorUnitTest.java @@ -0,0 +1,56 @@ +package net.hostsharing.hsadminng.hs.booking.item.validators; + +import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity; +import org.junit.jupiter.api.Test; + +import java.util.Map; + +import static java.util.Map.entry; +import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.MANAGED_SERVER; +import static net.hostsharing.hsadminng.hs.booking.item.validators.HsBookingItemEntityValidators.forType; +import static org.assertj.core.api.Assertions.assertThat; + +class HsManagedServerBookingItemValidatorUnitTest { + + @Test + void validatesProperties() { + // given + final var validator = HsBookingItemEntityValidators.forType(MANAGED_SERVER); + final var mangedServerBookingItemEntity = HsBookingItemEntity.builder() + .type(MANAGED_SERVER) + .resources(Map.ofEntries( + entry("CPUs", 2), + entry("RAM", 25), + entry("SSD", 25), + entry("Traffic", 250), + entry("SLA-EMail", true) + )) + .build(); + + // when + final var result = validator.validate(mangedServerBookingItemEntity); + + // then + assertThat(result).containsExactly("'resources.SLA-EMail' is expected to be false because resources.SLA-Platform=BASIC but is true"); + } + + @Test + void containsAllValidations() { + // when + final var validator = forType(MANAGED_SERVER); + + // 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}}"); + } +} diff --git a/src/test/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsManagedWebspaceBookingItemValidatorUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsManagedWebspaceBookingItemValidatorUnitTest.java new file mode 100644 index 00000000..8a278850 --- /dev/null +++ b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsManagedWebspaceBookingItemValidatorUnitTest.java @@ -0,0 +1,54 @@ +package net.hostsharing.hsadminng.hs.booking.item.validators; + +import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity; +import org.junit.jupiter.api.Test; + +import java.util.Map; + +import static java.util.Map.entry; +import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.MANAGED_WEBSPACE; +import static net.hostsharing.hsadminng.hs.booking.item.validators.HsBookingItemEntityValidators.forType; +import static org.assertj.core.api.Assertions.assertThat; + +class HsManagedWebspaceBookingItemValidatorUnitTest { + + @Test + void validatesProperties() { + // given + final var mangedServerBookingItemEntity = HsBookingItemEntity.builder() + .type(MANAGED_WEBSPACE) + .resources(Map.ofEntries( + entry("CPUs", 2), + entry("RAM", 25), + entry("SSD", 25), + entry("Traffic", 250), + entry("SLA-EMail", true) + )) + .build(); + final var validator = forType(mangedServerBookingItemEntity.getType()); + + // when + final var result = validator.validate(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'"); + } + + @Test + void containsAllValidations() { + // when + final var validator = 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}"); + } +} diff --git a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetControllerAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetControllerAcceptanceTest.java index 0cde4075..f3eb66ee 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetControllerAcceptanceTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetControllerAcceptanceTest.java @@ -19,6 +19,8 @@ import java.util.Map; import java.util.UUID; import static java.util.Map.entry; +import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.CLOUD_SERVER; +import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_SERVER; import static net.hostsharing.hsadminng.rbac.test.JsonMatcher.lenientlyEquals; import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.Matchers.matchesRegex; @@ -113,7 +115,7 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup .header("current-user", "superuser-alex@hostsharing.net") .port(port) .when() - .get("http://localhost/api/hs/hosting/assets?type=" + HsHostingAssetType.MANAGED_SERVER) + .get("http://localhost/api/hs/hosting/assets?type=" + MANAGED_SERVER) .then().log().all().assertThat() .statusCode(200) .contentType("application/json") @@ -159,7 +161,7 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup class AddServer { @Test - void globalAdmin_canAddAsset() { + void globalAdmin_canAddBookedAsset() { context.define("superuser-alex@hostsharing.net"); final var givenBookingItem = givenBookingItem("First", "some PrivateCloud"); @@ -173,7 +175,7 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup "bookingItemUuid": "%s", "type": "MANAGED_SERVER", "identifier": "vm1400", - "caption": "some new CloudServer", + "caption": "some new ManagedServer", "config": { "CPUs": 2, "RAM": 100, "SSD": 300, "Traffic": 250 } } """.formatted(givenBookingItem.getUuid())) @@ -187,7 +189,7 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup { "type": "MANAGED_SERVER", "identifier": "vm1400", - "caption": "some new CloudServer", + "caption": "some new ManagedServer", "config": { "CPUs": 2, "RAM": 100, "SSD": 300, "Traffic": 250 } } """)) @@ -200,6 +202,48 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup assertThat(newUserUuid).isNotNull(); } + @Test + void parentAssetAgent_canAddSubAsset() { + + context.define("superuser-alex@hostsharing.net"); + final var givenParentAsset = givenParentAsset("First", MANAGED_SERVER); + + final var location = RestAssured // @formatter:off + .given() + .header("current-user", "person-FirbySusan@example.com") + .contentType(ContentType.JSON) + .body(""" + { + "parentAssetUuid": "%s", + "type": "MANAGED_WEBSPACE", + "identifier": "fir90", + "caption": "some new ManagedWebspace in client's ManagedServer", + "config": { "SSD": 100, "Traffic": 250 } + } + """.formatted(givenParentAsset.getUuid())) + .port(port) + .when() + .post("http://localhost/api/hs/hosting/assets") + .then().log().all().assertThat() + .statusCode(201) + .contentType(ContentType.JSON) + .body("", lenientlyEquals(""" + { + "type": "MANAGED_WEBSPACE", + "identifier": "fir90", + "caption": "some new ManagedWebspace in client's ManagedServer", + "config": { "SSD": 100, "Traffic": 250 } + } + """)) + .header("Location", matchesRegex("http://localhost:[1-9][0-9]*/api/hs/hosting/assets/[^/]*")) + .extract().header("Location"); // @formatter:on + + // finally, the new asset can be accessed under the generated UUID + final var newUserUuid = UUID.fromString( + location.substring(location.lastIndexOf('/') + 1)); + assertThat(newUserUuid).isNotNull(); + } + @Test void additionalValidationsArePerformend_whenAddingAsset() { @@ -215,7 +259,7 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup "bookingItemUuid": "%s", "type": "MANAGED_SERVER", "identifier": "vm1400", - "caption": "some new CloudServer", + "caption": "some new ManagedServer", "config": { "CPUs": 0, "extra": 42 } } """.formatted(givenBookingItem.getUuid())) @@ -228,14 +272,14 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup .body("", lenientlyEquals(""" { "statusPhrase": "Bad Request", - "message": "['extra' is not expected but is '42', 'CPUs' is expected to be >= 1 but is 0, 'RAM' is required but missing, 'SSD' is required but missing, 'Traffic' is required but missing]" + "message": "['config.extra' is not expected but is set to '42', 'config.CPUs' is expected to be >= 1 but is 0, 'config.RAM' is required but missing, 'config.SSD' is required but missing, 'config.Traffic' is required but missing]" } """)); // @formatter:on } } @Nested - class GetASset { + class GetAsset { @Test void globalAdmin_canGetArbitraryAsset() { @@ -321,7 +365,8 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup @Test void globalAdmin_canPatchAllUpdatablePropertiesOfAsset() { - final var givenAsset = givenSomeTemporaryAssetForDebitorNumber("2001", entry("something", 1)); + final var givenAsset = givenSomeTemporaryHostingAsset("2001", CLOUD_SERVER, + config("CPUs", 4), config("RAM", 100), config("HDD", 100), config("Traffic", 2000)); RestAssured // @formatter:off .given() @@ -330,9 +375,9 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup .body(""" { "config": { - "CPU": "4", + "CPUs": 2, "HDD": null, - "SSD": "4096" + "SSD": 250 } } """) @@ -348,9 +393,9 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup "identifier": "vm2001", "caption": "some test-asset", "config": { - "CPU": "4", - "SSD": "4096", - "something": 1 + "CPUs": 2, + "RAM": 100, + "SSD": 250 } } """)); // @formatter:on @@ -359,7 +404,7 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup context.define("superuser-alex@hostsharing.net"); assertThat(assetRepo.findByUuid(givenAsset.getUuid())).isPresent().get() .matches(asset -> { - assertThat(asset.toString()).isEqualTo("HsHostingAssetEntity(CLOUD_SERVER, vm2001, some test-asset, D-1000111:some CloudServer, { CPU: 4, SSD: 4096, something: 1 })"); + assertThat(asset.toString()).isEqualTo("HsHostingAssetEntity(CLOUD_SERVER, vm2001, some test-asset, D-1000111:some CloudServer, { CPUs: 2, RAM: 100, SSD: 250, Traffic: 2000 })"); return true; }); } @@ -371,7 +416,8 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup @Test void globalAdmin_canDeleteArbitraryAsset() { context.define("superuser-alex@hostsharing.net"); - final var givenAsset = givenSomeTemporaryAssetForDebitorNumber("2002", entry("something", 1)); + final var givenAsset = givenSomeTemporaryHostingAsset("2002", CLOUD_SERVER, + config("CPUs", 4), config("RAM", 100), config("HDD", 100), config("Traffic", 2000)); RestAssured // @formatter:off .given() @@ -389,7 +435,8 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup @Test void normalUser_canNotDeleteUnrelatedAsset() { context.define("superuser-alex@hostsharing.net"); - final var givenAsset = givenSomeTemporaryAssetForDebitorNumber("2003", entry("something", 1)); + final var givenAsset = givenSomeTemporaryHostingAsset("2003", CLOUD_SERVER, + config("CPUs", 4), config("RAM", 100), config("HDD", 100), config("Traffic", 2000)); RestAssured // @formatter:off .given() @@ -412,14 +459,22 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup .findAny().orElseThrow(); } - private HsHostingAssetEntity givenSomeTemporaryAssetForDebitorNumber(final String identifierSuffix, - final Map.Entry resources) { + HsHostingAssetEntity givenParentAsset(final String debitorName, final HsHostingAssetType assetType) { + final var givenDebitor = debitorRepo.findDebitorByOptionalNameLike(debitorName).stream().findAny().orElseThrow(); + final var givenAsset = assetRepo.findAllByCriteria(givenDebitor.getUuid(), null, assetType).stream().findAny().orElseThrow(); + return givenAsset; + } + + @SafeVarargs + private HsHostingAssetEntity givenSomeTemporaryHostingAsset(final String identifierSuffix, + final HsHostingAssetType hostingAssetType, + final Map.Entry... resources) { return jpaAttempt.transacted(() -> { context.define("superuser-alex@hostsharing.net"); final var newAsset = HsHostingAssetEntity.builder() .uuid(UUID.randomUUID()) .bookingItem(givenBookingItem("First", "some CloudServer")) - .type(HsHostingAssetType.CLOUD_SERVER) + .type(hostingAssetType) .identifier("vm" + identifierSuffix) .caption("some test-asset") .config(Map.ofEntries(resources)) @@ -428,4 +483,8 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup return assetRepo.save(newAsset); }).assertSuccessful().returnedValue(); } + + private Map.Entry config(final String key, final Object value) { + return entry(key, value); + } } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetPropsControllerAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetPropsControllerAcceptanceTest.java index 58c7bf91..e6cc9acd 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetPropsControllerAcceptanceTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetPropsControllerAcceptanceTest.java @@ -40,7 +40,7 @@ class HsHostingAssetPropsControllerAcceptanceTest { } @Test - void globalAdmin_canListPropertiesOfGivenAssetType() { + void anyone_canListPropertiesOfGivenAssetType() { RestAssured // @formatter:off .given() @@ -52,101 +52,50 @@ class HsHostingAssetPropsControllerAcceptanceTest { .contentType("application/json") .body("", lenientlyEquals(""" [ - { - "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, + "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 } ] """)); diff --git a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetValidatorUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetValidatorUnitTest.java deleted file mode 100644 index d7f21222..00000000 --- a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetValidatorUnitTest.java +++ /dev/null @@ -1,97 +0,0 @@ -package net.hostsharing.hsadminng.hs.hosting.asset; - -import net.hostsharing.hsadminng.hs.hosting.asset.validator.HsHostingAssetValidator; -import org.junit.jupiter.api.Test; - -import java.util.Map; - -import static java.util.Collections.emptyMap; -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; - -class HsHostingAssetValidatorUnitTest { - - @Test - void validatesMissingProperties() { - // given - final var validator = HsHostingAssetValidator.forType(MANAGED_WEBSPACE); - final var mangedWebspaceHostingAssetEntity = HsHostingAssetEntity.builder() - .type(MANAGED_WEBSPACE) - .config(emptyMap()) - .build(); - - // when - final var result = validator.validate(mangedWebspaceHostingAssetEntity); - - // then - assertThat(result).containsExactlyInAnyOrder( - "'SSD' is required but missing", - "'Traffic' is required but missing" - ); - } - - @Test - void validatesUnknownProperties() { - // given - final var validator = HsHostingAssetValidator.forType(MANAGED_WEBSPACE); - final var mangedWebspaceHostingAssetEntity = HsHostingAssetEntity.builder() - .type(MANAGED_WEBSPACE) - .config(Map.ofEntries( - entry("HDD", 0), - entry("SSD", 1), - entry("Traffic", 10), - entry("unknown", "some value") - )) - .build(); - - // when - final var result = validator.validate(mangedWebspaceHostingAssetEntity); - - // then - assertThat(result).containsExactly("'unknown' is not expected but is 'some value'"); - } - - @Test - void validatesDependentProperties() { - // given - final var validator = HsHostingAssetValidator.forType(MANAGED_SERVER); - final var mangedWebspaceHostingAssetEntity = HsHostingAssetEntity.builder() - .type(MANAGED_SERVER) - .config(Map.ofEntries( - entry("CPUs", 2), - entry("RAM", 25), - entry("SSD", 25), - entry("Traffic", 250), - entry("SLA-EMail", true) - )) - .build(); - - // when - final var result = validator.validate(mangedWebspaceHostingAssetEntity); - - // then - assertThat(result).containsExactly("'SLA-EMail' is expected to be false because SLA-Platform=BASIC but is true"); - } - - @Test - void validatesValidProperties() { - // given - final var validator = HsHostingAssetValidator.forType(MANAGED_WEBSPACE); - final var mangedWebspaceHostingAssetEntity = HsHostingAssetEntity.builder() - .type(MANAGED_WEBSPACE) - .config(Map.ofEntries( - entry("HDD", 200), - entry("SSD", 25), - entry("Traffic", 250) - )) - .build(); - - // when - final var result = validator.validate(mangedWebspaceHostingAssetEntity); - - // then - assertThat(result).isEmpty(); - } -} diff --git a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsCloudServerHostingAssetValidatorUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsCloudServerHostingAssetValidatorUnitTest.java new file mode 100644 index 00000000..ee77c565 --- /dev/null +++ b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsCloudServerHostingAssetValidatorUnitTest.java @@ -0,0 +1,55 @@ +package net.hostsharing.hsadminng.hs.hosting.asset.validators; + +import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity; +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.CLOUD_SERVER; +import static net.hostsharing.hsadminng.hs.hosting.asset.validators.HsHostingAssetEntityValidators.forType; +import static org.assertj.core.api.Assertions.assertThat; + +class HsCloudServerHostingAssetValidatorUnitTest { + + @Test + void validatesProperties() { + // given + final var cloudServerHostingAssetEntity = HsHostingAssetEntity.builder() + .type(CLOUD_SERVER) + .config(Map.ofEntries( + entry("RAM", 2000), + entry("SSD", 256), + entry("Traffic", "250"), + entry("SLA-Platform", "xxx") + )) + .build(); + final var validator = forType(cloudServerHostingAssetEntity.getType()); + + + // when + final var result = validator.validate(cloudServerHostingAssetEntity); + + // then + assertThat(result).containsExactlyInAnyOrder( + "'config.SLA-Platform' is not expected but is set to 'xxx'", + "'config.CPUs' is required but missing", + "'config.RAM' is expected to be <= 128 but is 2000", + "'config.SSD' is expected to be multiple of 25 but is 256", + "'config.Traffic' is expected to be of type class java.lang.Integer, but is of type 'String'"); + } + + @Test + void containsAllValidations() { + // when + final var validator = forType(CLOUD_SERVER); + + // 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}"); + } +} diff --git a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsHostingAssetEntityValidatorsUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsHostingAssetEntityValidatorsUnitTest.java new file mode 100644 index 00000000..07eb7517 --- /dev/null +++ b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsHostingAssetEntityValidatorsUnitTest.java @@ -0,0 +1,33 @@ +package net.hostsharing.hsadminng.hs.hosting.asset.validators; + +import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity; +import org.junit.jupiter.api.Test; + +import jakarta.validation.ValidationException; + +import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.CLOUD_SERVER; +import static net.hostsharing.hsadminng.hs.hosting.asset.validators.HsHostingAssetEntityValidators.valid; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.catchThrowable; + +class HsHostingAssetEntityValidatorsUnitTest { + + @Test + void validThrowsException() { + // given + final var cloudServerHostingAssetEntity = HsHostingAssetEntity.builder() + .type(CLOUD_SERVER) + .build(); + + // when + final var result = catchThrowable( ()-> valid(cloudServerHostingAssetEntity) ); + + // then + assertThat(result).isInstanceOf(ValidationException.class) + .hasMessageContaining( + "'config.CPUs' is required but missing", + "'config.RAM' is required but missing", + "'config.SSD' is required but missing", + "'config.Traffic' is required but missing"); + } +} diff --git a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsManagedServerHostingAssetValidatorUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsManagedServerHostingAssetValidatorUnitTest.java new file mode 100644 index 00000000..a9ee1433 --- /dev/null +++ b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsManagedServerHostingAssetValidatorUnitTest.java @@ -0,0 +1,40 @@ +package net.hostsharing.hsadminng.hs.hosting.asset.validators; + +import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity; +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.validators.HsHostingAssetEntityValidators.forType; +import static org.assertj.core.api.Assertions.assertThat; + +class HsManagedServerHostingAssetValidatorUnitTest { + + @Test + void validatesProperties() { + // given + final var mangedWebspaceHostingAssetEntity = HsHostingAssetEntity.builder() + .type(MANAGED_SERVER) + .config(Map.ofEntries( + entry("RAM", 2000), + entry("SSD", 256), + entry("Traffic", "250"), + entry("SLA-Platform", "xxx") + )) + .build(); + final var validator = forType(mangedWebspaceHostingAssetEntity.getType()); + + // when + final var result = validator.validate(mangedWebspaceHostingAssetEntity); + + // then + assertThat(result).containsExactlyInAnyOrder( + "'config.SLA-Platform' is not expected but is set to 'xxx'", + "'config.CPUs' is required but missing", + "'config.RAM' is expected to be <= 128 but is 2000", + "'config.SSD' is expected to be multiple of 25 but is 256", + "'config.Traffic' is expected to be of type class java.lang.Integer, but is of type 'String'"); + } +} diff --git a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsManagedWebspaceHostingAssetValidatorUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsManagedWebspaceHostingAssetValidatorUnitTest.java new file mode 100644 index 00000000..e0397036 --- /dev/null +++ b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsManagedWebspaceHostingAssetValidatorUnitTest.java @@ -0,0 +1,120 @@ +package net.hostsharing.hsadminng.hs.hosting.asset.validators; + +import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity; +import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity; +import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorEntity; +import org.junit.jupiter.api.Test; + +import java.util.Map; + +import static java.util.Collections.emptyMap; +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; + +class HsManagedWebspaceHostingAssetValidatorUnitTest { + + final HsBookingItemEntity managedServerBookingItem = HsBookingItemEntity.builder() + .debitor(HsOfficeDebitorEntity.builder().defaultPrefix("abc").build() + ) + .build(); + final HsHostingAssetEntity mangedServerAssetEntity = HsHostingAssetEntity.builder() + .type(MANAGED_SERVER) + .bookingItem(managedServerBookingItem) + .config(Map.ofEntries( + entry("HDD", 0), + entry("SSD", 1), + entry("Traffic", 10) + )) + .build(); + + @Test + void validatesIdentifier() { + // given + final var validator = HsHostingAssetEntityValidators.forType(MANAGED_WEBSPACE); + final var mangedWebspaceHostingAssetEntity = HsHostingAssetEntity.builder() + .type(MANAGED_WEBSPACE) + .parentAsset(mangedServerAssetEntity) + .identifier("xyz00") + .config(Map.ofEntries( + entry("HDD", 0), + entry("SSD", 1), + entry("Traffic", 10) + )) + .build(); + + // when + final var result = validator.validate(mangedWebspaceHostingAssetEntity); + + // then + assertThat(result).containsExactly("'identifier' expected to match '^abc[0-9][0-9]$', but is 'xyz00'"); + } + + + @Test + void validatesMissingProperties() { + // given + final var validator = HsHostingAssetEntityValidators.forType(MANAGED_WEBSPACE); + final var mangedWebspaceHostingAssetEntity = HsHostingAssetEntity.builder() + .type(MANAGED_WEBSPACE) + .parentAsset(mangedServerAssetEntity) + .identifier("abc00") + .config(emptyMap()) + .build(); + + // when + final var result = validator.validate(mangedWebspaceHostingAssetEntity); + + // then + assertThat(result).containsExactlyInAnyOrder( + "'config.SSD' is required but missing", + "'config.Traffic' is required but missing" + ); + } + + @Test + void validatesUnknownProperties() { + // given + final var validator = HsHostingAssetEntityValidators.forType(MANAGED_WEBSPACE); + final var mangedWebspaceHostingAssetEntity = HsHostingAssetEntity.builder() + .type(MANAGED_WEBSPACE) + .parentAsset(mangedServerAssetEntity) + .identifier("abc00") + .config(Map.ofEntries( + entry("HDD", 0), + entry("SSD", 1), + entry("Traffic", 10), + entry("unknown", "some value") + )) + .build(); + + // when + final var result = validator.validate(mangedWebspaceHostingAssetEntity); + + // then + assertThat(result).containsExactly("'config.unknown' is not expected but is set to 'some value'"); + } + + @Test + void validatesValidProperties() { + // given + final var validator = HsHostingAssetEntityValidators.forType(MANAGED_WEBSPACE); + final var mangedWebspaceHostingAssetEntity = HsHostingAssetEntity.builder() + .type(MANAGED_WEBSPACE) + .parentAsset(mangedServerAssetEntity) + .identifier("abc00") + .config(Map.ofEntries( + entry("HDD", 200), + entry("SSD", 25), + entry("Traffic", 250) + )) + .build(); + + // when + final var result = validator.validate(mangedWebspaceHostingAssetEntity); + + // then + assertThat(result).isEmpty(); + } +}