From de88f1d842794ff88d682135a8128304184b02c5 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Mon, 24 Jun 2024 12:33:14 +0200 Subject: [PATCH] hosting-asset-validation-beyond-property-validators (#65) Co-authored-by: Michael Hoennig Reviewed-on: https://dev.hostsharing.net/hostsharing/hs.hsadmin.ng/pulls/65 Reviewed-by: Timotheus Pokorra --- .../hs/booking/item/HsBookingItemEntity.java | 4 +- .../hosting/asset/HsHostingAssetEntity.java | 13 +- .../HsCloudServerHostingAssetValidator.java | 23 +++ .../HsHostingAssetEntityValidator.java | 170 +++++++++++++++++- ...HsHostingAssetEntityValidatorRegistry.java | 5 +- .../HsManagedServerHostingAssetValidator.java | 16 +- ...sManagedWebspaceHostingAssetValidator.java | 31 ++-- .../HsUnixUserHostingAssetValidator.java | 23 +++ .../hs/hosting/asset/validators/README.md | 40 +++++ .../hs/booking/item/TestHsBookingItem.java | 12 +- ...sHostingAssetControllerAcceptanceTest.java | 54 ++++-- .../HsHostingAssetEntityPatcherUnitTest.java | 4 +- .../asset/HsHostingAssetEntityUnitTest.java | 8 +- ...HostingAssetRepositoryIntegrationTest.java | 2 + ...udServerHostingAssetValidatorUnitTest.java | 66 ++++++- ...gAssetEntityValidatorRegistryUnitTest.java | 64 +++++++ ...HsHostingAssetEntityValidatorUnitTest.java | 10 +- ...edServerHostingAssetValidatorUnitTest.java | 47 +++++ ...WebspaceHostingAssetValidatorUnitTest.java | 72 +++++++- ...UnixUserHostingAssetValidatorUnitTest.java | 30 ++++ 20 files changed, 639 insertions(+), 55 deletions(-) create mode 100644 src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsCloudServerHostingAssetValidator.java create mode 100644 src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsUnixUserHostingAssetValidator.java create mode 100644 src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/README.md create mode 100644 src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsHostingAssetEntityValidatorRegistryUnitTest.java create mode 100644 src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsUnixUserHostingAssetValidatorUnitTest.java 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 90774110..94b80984 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 @@ -34,6 +34,7 @@ import jakarta.persistence.OneToOne; import jakarta.persistence.Table; import jakarta.persistence.Transient; import jakarta.persistence.Version; +import jakarta.validation.constraints.NotNull; import java.io.IOException; import java.time.LocalDate; import java.util.HashMap; @@ -60,8 +61,8 @@ import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL.directlyFetche import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.rbacViewFor; import static net.hostsharing.hsadminng.stringify.Stringify.stringify; -@Builder @Entity +@Builder(toBuilder = true) @Table(name = "hs_booking_item_rv") @Getter @Setter @@ -92,6 +93,7 @@ public class HsBookingItemEntity implements Stringifyable, RbacObject { @JoinColumn(name = "parentitemuuid") private HsBookingItemEntity parentItem; + @NotNull @Column(name = "type") @Enumerated(EnumType.STRING) private HsBookingItemType type; 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 76bcd40d..ff7bfd33 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 @@ -29,6 +29,7 @@ import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import jakarta.persistence.OneToMany; import jakarta.persistence.OneToOne; +import jakarta.persistence.PostLoad; import jakarta.persistence.Table; import jakarta.persistence.Transient; import jakarta.persistence.Version; @@ -120,12 +121,20 @@ public class HsHostingAssetEntity implements Stringifyable, RbacObject { @Transient private PatchableMapWrapper configWrapper; + @Transient + private boolean isLoaded = false; + + @PostLoad + public void markAsLoaded() { + this.isLoaded = true; + } + public PatchableMapWrapper getConfig() { return PatchableMapWrapper.of(configWrapper, (newWrapper) -> {configWrapper = newWrapper; }, config ); } - public void putConfig(Map newConfg) { - PatchableMapWrapper.of(configWrapper, (newWrapper) -> {configWrapper = newWrapper; }, config).assign(newConfg); + public void putConfig(Map newConfig) { + PatchableMapWrapper.of(configWrapper, (newWrapper) -> {configWrapper = newWrapper; }, config).assign(newConfig); } @Override 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..9144189b --- /dev/null +++ b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsCloudServerHostingAssetValidator.java @@ -0,0 +1,23 @@ +package net.hostsharing.hsadminng.hs.hosting.asset.validators; + +import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType; +import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity; + +import java.util.regex.Pattern; + +class HsCloudServerHostingAssetValidator extends HsHostingAssetEntityValidator { + + HsCloudServerHostingAssetValidator() { + super( + BookingItem.mustBeOfType(HsBookingItemType.CLOUD_SERVER), + ParentAsset.mustBeNull(), + AssignedToAsset.mustBeNull(), + AlarmContact.isOptional(), + NO_EXTRA_PROPERTIES); + } + + @Override + protected Pattern identifierPattern(final HsHostingAssetEntity assetEntity) { + return Pattern.compile("^vm[0-9][0-9][0-9][0-9]$"); + } +} diff --git a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsHostingAssetEntityValidator.java b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsHostingAssetEntityValidator.java index c452d378..15ea12df 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsHostingAssetEntityValidator.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsHostingAssetEntityValidator.java @@ -1,35 +1,80 @@ package net.hostsharing.hsadminng.hs.hosting.asset.validators; import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity; +import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType; import net.hostsharing.hsadminng.hs.booking.item.validators.HsBookingItemEntityValidatorRegistry; import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity; +import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType; +import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity; import net.hostsharing.hsadminng.hs.validation.HsEntityValidator; import net.hostsharing.hsadminng.hs.validation.ValidatableProperty; +import jakarta.validation.constraints.NotNull; +import java.util.Collections; import java.util.List; import java.util.Objects; +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.regex.Pattern; +import java.util.stream.Stream; import static java.util.Arrays.stream; import static java.util.Collections.emptyList; import static java.util.Optional.ofNullable; -public class HsHostingAssetEntityValidator extends HsEntityValidator { +public abstract class HsHostingAssetEntityValidator extends HsEntityValidator { - public HsHostingAssetEntityValidator(final ValidatableProperty... properties) { + static final ValidatableProperty[] NO_EXTRA_PROPERTIES = new ValidatableProperty[0]; + + private final HsHostingAssetEntityValidator.BookingItem bookingItemValidation; + private final HsHostingAssetEntityValidator.ParentAsset parentAssetValidation; + private final HsHostingAssetEntityValidator.AssignedToAsset assignedToAssetValidation; + private final HsHostingAssetEntityValidator.AlarmContact alarmContactValidation; + + HsHostingAssetEntityValidator( + @NotNull final BookingItem bookingItemValidation, + @NotNull final ParentAsset parentAssetValidation, + @NotNull final AssignedToAsset assignedToAssetValidation, + @NotNull final AlarmContact alarmContactValidation, + final ValidatableProperty... properties) { super(properties); + this.bookingItemValidation = bookingItemValidation; + this.parentAssetValidation = parentAssetValidation; + this.assignedToAssetValidation = assignedToAssetValidation; + this.alarmContactValidation = alarmContactValidation; } - @Override public List validate(final HsHostingAssetEntity assetEntity) { return sequentiallyValidate( - () -> validateProperties(assetEntity), + () -> validateEntityReferences(assetEntity), + () -> validateIdentifierPattern(assetEntity), // might need proper parentAsset or billingItem () -> optionallyValidate(assetEntity.getBookingItem()), () -> optionallyValidate(assetEntity.getParentAsset()), () -> validateAgainstSubEntities(assetEntity) ); } + private List validateEntityReferences(final HsHostingAssetEntity assetEntity) { + return Stream.of( + validateReferencedEntity(assetEntity, "bookingItem", bookingItemValidation::validate), + validateReferencedEntity(assetEntity, "parentAsset", parentAssetValidation::validate), + validateReferencedEntity(assetEntity, "assignedToAsset", assignedToAssetValidation::validate), + validateReferencedEntity(assetEntity, "alarmContact", alarmContactValidation::validate), + validateProperties(assetEntity)) + .filter(Objects::nonNull) + .flatMap(List::stream) + .filter(Objects::nonNull) + .toList(); + } + + private List validateReferencedEntity( + final HsHostingAssetEntity assetEntity, + final String referenceFieldName, + final BiFunction> validator) { + return enrich(prefix(assetEntity.toShortString()), validator.apply(assetEntity, referenceFieldName)); + } + private List validateProperties(final HsHostingAssetEntity assetEntity) { return enrich(prefix(assetEntity.toShortString(), "config"), validateProperties(assetEntity.getConfig())); } @@ -57,6 +102,7 @@ public class HsHostingAssetEntityValidator extends HsEntityValidator propDef) { @@ -73,4 +119,120 @@ public class HsHostingAssetEntityValidator extends HsEntityValidator validateIdentifierPattern(final HsHostingAssetEntity assetEntity) { + final var expectedIdentifierPattern = identifierPattern(assetEntity); + if (assetEntity.getIdentifier() == null || + !expectedIdentifierPattern.matcher(assetEntity.getIdentifier()).matches()) { + return List.of("'identifier' expected to match '"+expectedIdentifierPattern+"', but is '" + assetEntity.getIdentifier() + "'"); + } + return Collections.emptyList(); + } + + protected abstract Pattern identifierPattern(HsHostingAssetEntity assetEntity); + + static abstract class ReferenceValidator { + + private final Policy policy; + private final T subEntityType; + private final Function subEntityGetter; + private final Function subEntityTypeGetter; + + public ReferenceValidator( + final Policy policy, + final T subEntityType, + final Function subEntityGetter, + final Function subEntityTypeGetter) { + this.policy = policy; + this.subEntityType = subEntityType; + this.subEntityGetter = subEntityGetter; + this.subEntityTypeGetter = subEntityTypeGetter; + } + + public ReferenceValidator( + final Policy policy, + final Function subEntityGetter) { + this.policy = policy; + this.subEntityType = null; + this.subEntityGetter = subEntityGetter; + this.subEntityTypeGetter = e -> null; + } + + enum Policy { + OPTIONAL, FORBIDDEN, REQUIRED + } + + List validate(final HsHostingAssetEntity assetEntity, final String referenceFieldName) { + + final var subEntity = subEntityGetter.apply(assetEntity); + if (policy == Policy.REQUIRED && subEntity == null) { + return List.of(referenceFieldName + "' must not be null but is null"); + } + if (policy == Policy.FORBIDDEN && subEntity != null) { + return List.of(referenceFieldName + "' must be null but is set to "+ assetEntity.getBookingItem().toShortString()); + } + final var subItemType = subEntity != null ? subEntityTypeGetter.apply(subEntity) : null; + if (subEntityType != null && subItemType != subEntityType) { + return List.of(referenceFieldName + "' must be of type " + subEntityType + " but is of type " + subItemType); + } + return emptyList(); + } + } + + static class BookingItem extends ReferenceValidator { + + BookingItem(final Policy policy, final HsBookingItemType bookingItemType) { + super(policy, bookingItemType, HsHostingAssetEntity::getBookingItem, HsBookingItemEntity::getType); + } + + static BookingItem mustBeNull() { + return new BookingItem(Policy.FORBIDDEN, null); + } + + static BookingItem mustBeOfType(final HsBookingItemType hsBookingItemType) { + return new BookingItem(Policy.REQUIRED, hsBookingItemType); + } + } + + static class ParentAsset extends ReferenceValidator { + + ParentAsset(final ReferenceValidator.Policy policy, final HsHostingAssetType parentAssetType) { + super(policy, parentAssetType, HsHostingAssetEntity::getParentAsset, HsHostingAssetEntity::getType); + } + + static ParentAsset mustBeNull() { + return new ParentAsset(Policy.FORBIDDEN, null); + } + + static ParentAsset mustBeOfType(final HsHostingAssetType hostingAssetType) { + return new ParentAsset(Policy.REQUIRED, hostingAssetType); + } + + static ParentAsset mustBeNullOrOfType(final HsHostingAssetType hostingAssetType) { + return new ParentAsset(Policy.OPTIONAL, hostingAssetType); + } + } + + static class AssignedToAsset extends ReferenceValidator { + + AssignedToAsset(final ReferenceValidator.Policy policy, final HsHostingAssetType assignedToAssetType) { + super(policy, assignedToAssetType, HsHostingAssetEntity::getAssignedToAsset, HsHostingAssetEntity::getType); + } + + static AssignedToAsset mustBeNull() { + return new AssignedToAsset(Policy.FORBIDDEN, null); + } + } + + static class AlarmContact extends ReferenceValidator> { + + AlarmContact(final ReferenceValidator.Policy policy) { + super(policy, HsHostingAssetEntity::getAlarmContact); + } + + static AlarmContact isOptional() { + return new AlarmContact(Policy.OPTIONAL); + } + } + } diff --git a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsHostingAssetEntityValidatorRegistry.java b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsHostingAssetEntityValidatorRegistry.java index a1cac8e0..1b9a5241 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsHostingAssetEntityValidatorRegistry.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsHostingAssetEntityValidatorRegistry.java @@ -14,10 +14,11 @@ public class HsHostingAssetEntityValidatorRegistry { private static final Map, HsEntityValidator> validators = new HashMap<>(); static { - register(CLOUD_SERVER, new HsHostingAssetEntityValidator()); + // HOWTO: add (register) new HsHostingAssetType-specific validators + register(CLOUD_SERVER, new HsCloudServerHostingAssetValidator()); register(MANAGED_SERVER, new HsManagedServerHostingAssetValidator()); register(MANAGED_WEBSPACE, new HsManagedWebspaceHostingAssetValidator()); - register(UNIX_USER, new HsHostingAssetEntityValidator()); + register(UNIX_USER, new HsUnixUserHostingAssetValidator()); } private static void register(final Enum type, final HsEntityValidator validator) { 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 index 00050010..362abf38 100644 --- 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 @@ -1,5 +1,10 @@ package net.hostsharing.hsadminng.hs.hosting.asset.validators; +import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType; +import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity; + +import java.util.regex.Pattern; + import static net.hostsharing.hsadminng.hs.validation.BooleanProperty.booleanProperty; import static net.hostsharing.hsadminng.hs.validation.EnumerationProperty.enumerationProperty; import static net.hostsharing.hsadminng.hs.validation.IntegerProperty.integerProperty; @@ -8,6 +13,11 @@ class HsManagedServerHostingAssetValidator extends HsHostingAssetEntityValidator public HsManagedServerHostingAssetValidator() { super( + BookingItem.mustBeOfType(HsBookingItemType.MANAGED_SERVER), + ParentAsset.mustBeNull(), // until we introduce a hosting asset for 'HOST' + AssignedToAsset.mustBeNull(), + AlarmContact.isOptional(), // hostmaster alert address is implicitly added + // monitoring integerProperty("monit_max_cpu_usage").unit("%").min(10).max(100).withDefault(92), integerProperty("monit_max_ram_usage").unit("%").min(10).max(100).withDefault(92), @@ -15,7 +25,6 @@ class HsManagedServerHostingAssetValidator extends HsHostingAssetEntityValidator integerProperty("monit_min_free_ssd").min(1).max(1000).withDefault(5), integerProperty("monit_max_hdd_usage").unit("%").min(10).max(100).withDefault(95), integerProperty("monit_min_free_hdd").min(1).max(4000).withDefault(10), - // stringProperty("monit_alarm_email").unit("GB").optional() TODO.impl: via Contact? // other settings // booleanProperty("fastcgi_small").withDefault(false), TODO.spec: clarify Salt-Grains @@ -45,4 +54,9 @@ class HsManagedServerHostingAssetValidator extends HsHostingAssetEntityValidator booleanProperty("software-imagemagick-ghostscript").withDefault(false) ); } + + @Override + protected Pattern identifierPattern(final HsHostingAssetEntity assetEntity) { + return Pattern.compile("^vm[0-9][0-9][0-9][0-9]$"); + } } 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 index 19c9dc24..bffedf2f 100644 --- 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 @@ -1,29 +1,26 @@ package net.hostsharing.hsadminng.hs.hosting.asset.validators; +import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType; import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity; +import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType; -import java.util.Collection; -import java.util.stream.Stream; -import java.util.Collections; -import java.util.List; -import java.util.stream.Collectors; +import java.util.regex.Pattern; class HsManagedWebspaceHostingAssetValidator extends HsHostingAssetEntityValidator { public HsManagedWebspaceHostingAssetValidator() { + super(BookingItem.mustBeOfType(HsBookingItemType.MANAGED_WEBSPACE), + ParentAsset.mustBeOfType(HsHostingAssetType.MANAGED_SERVER), // the (shared or private) ManagedServer + AssignedToAsset.mustBeNull(), + AlarmContact.isOptional(), // hostmaster alert address is implicitly added + NO_EXTRA_PROPERTIES); } @Override - public List validate(final HsHostingAssetEntity assetEntity) { - return Stream.of(validateIdentifierPattern(assetEntity), super.validate(assetEntity)) - .flatMap(Collection::stream) - .collect(Collectors.toList()); - } - - private static List validateIdentifierPattern(final HsHostingAssetEntity assetEntity) { - final var expectedIdentifierPattern = "^" + assetEntity.getParentAsset().getBookingItem().getProject().getDebitor().getDefaultPrefix() + "[0-9][0-9]$"; - if ( !assetEntity.getIdentifier().matches(expectedIdentifierPattern)) { - return List.of("'identifier' expected to match '"+expectedIdentifierPattern+"', but is '" + assetEntity.getIdentifier() + "'"); - } - return Collections.emptyList(); + protected Pattern identifierPattern(final HsHostingAssetEntity assetEntity) { + final var prefixPattern = + !assetEntity.isLoaded() + ? assetEntity.getParentAsset().getBookingItem().getProject().getDebitor().getDefaultPrefix() + : "[a-z][a-z0-9][a-z0-9]"; + return Pattern.compile("^" + prefixPattern + "[0-9][0-9]$"); } } diff --git a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsUnixUserHostingAssetValidator.java b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsUnixUserHostingAssetValidator.java new file mode 100644 index 00000000..dfe222fc --- /dev/null +++ b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsUnixUserHostingAssetValidator.java @@ -0,0 +1,23 @@ +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 java.util.regex.Pattern; + +class HsUnixUserHostingAssetValidator extends HsHostingAssetEntityValidator { + + HsUnixUserHostingAssetValidator() { + super(BookingItem.mustBeNull(), + ParentAsset.mustBeOfType(HsHostingAssetType.MANAGED_WEBSPACE), + AssignedToAsset.mustBeNull(), + AlarmContact.isOptional(), // TODO.spec: for quota notifications + NO_EXTRA_PROPERTIES); // TODO.spec: yet to be specified + } + + @Override + protected Pattern identifierPattern(final HsHostingAssetEntity assetEntity) { + final var webspaceIdentifier = assetEntity.getParentAsset().getIdentifier(); + return Pattern.compile("^"+webspaceIdentifier+"$|^"+webspaceIdentifier+"-[a-z0-9]+$"); + } +} diff --git a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/README.md b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/README.md new file mode 100644 index 00000000..52e03058 --- /dev/null +++ b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/README.md @@ -0,0 +1,40 @@ +### HsHostingAssetEntity-Validation + +There is just a single `HsHostingAssetEntity` class for all types of hosting assets like Managed-Server, Managed-Webspace, Unix-Users, Databases etc. These are distinguished by `HsHostingAssetType HsHostingAssetEntity.type`. + +For each of these types, a distinct validator has to be +implemented as a subclass of `HsHostingAssetEntityValidator` which needs to be registered (see `HsHostingAssetEntityValidatorRegistry`) for the relevant type(s). + +### Kinds of Validations + +#### Identifier validation + +The identifier of a Hosting-Asset is for example the Webspace-Name like "xyz00" or a Unix-User-Name like "xyz00-test". + +To validate the identifier, vverride the method `identifierPattern(...)` and return a regular expression to validate the identifier against. The regular expression can depend on the actual entity instance. + +#### Reference validation + +References in this context are: +- the related Booking-Item, +- the parent-Hosting-Asset, +- the Assigned-To-Hosting-Asset and +- the Contact. + +The first parameters of the `HsHostingAssetEntityValidator` superclass take rule descriptors for these references. These are all Subclasses fo + +### Validation Order + +The validations are called in a sensible order. E.g. if a property value is not numeric, it makes no sense to check the total sum of such values to be within certain numeric values. And if the related booking item is of wrong type, it makes no sense to validate limits against sub-entities. + +Properties are validated all at once, though. Thus, if multiple properties fail validation, all error messages are returned at once. + +In general, the validation es executed in this order: + +1. the entity itself + 1. its references + 2. its properties +2. the limits of the parent entity (parent asset + booking item) +3. limits against the own own-sub-entities + +This implementation can be found in `HsHostingAssetEntityValidator.validate`. diff --git a/src/test/java/net/hostsharing/hsadminng/hs/booking/item/TestHsBookingItem.java b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/TestHsBookingItem.java index 00c0d706..0779fa2f 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/booking/item/TestHsBookingItem.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/TestHsBookingItem.java @@ -12,13 +12,21 @@ import static net.hostsharing.hsadminng.hs.booking.project.TestHsBookingProject. @UtilityClass public class TestHsBookingItem { - public static final HsBookingItemEntity TEST_BOOKING_ITEM = HsBookingItemEntity.builder() + public static final HsBookingItemEntity TEST_MANAGED_SERVER_BOOKING_ITEM = HsBookingItemEntity.builder() .project(TEST_PROJECT) - .caption("test booking item") + .type(HsBookingItemType.MANAGED_SERVER) + .caption("test project booking item") .resources(Map.ofEntries( entry("someThing", 1), entry("anotherThing", "blue") )) .validity(Range.closedInfinite(LocalDate.of(2020, 1, 15))) .build(); + + public static final HsBookingItemEntity TEST_CLOUD_SERVER_BOOKING_ITEM = HsBookingItemEntity.builder() + .project(TEST_PROJECT) + .type(HsBookingItemType.CLOUD_SERVER) + .caption("test cloud server booking item") + .validity(Range.closedInfinite(LocalDate.of(2020, 1, 15))) + .build(); } 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 44f5327f..0b231bbd 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 @@ -22,6 +22,7 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.transaction.annotation.Transactional; +import java.util.HashMap; import java.util.Map; import java.util.UUID; @@ -220,9 +221,7 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup void parentAssetAgent_canAddSubAsset() { context.define("superuser-alex@hostsharing.net"); - final var givenParentAsset = givenParentAsset(MANAGED_SERVER, "vm1011"); - - context.define("person-FirbySusan@example.com"); + final var givenParentAsset = givenParentAsset(MANAGED_WEBSPACE, "fir01"); final var location = RestAssured // @formatter:off .given() @@ -232,9 +231,9 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup .body(""" { "parentAssetUuid": "%s", - "type": "MANAGED_WEBSPACE", - "identifier": "fir90", - "caption": "some new ManagedWebspace in client's ManagedServer", + "type": "UNIX_USER", + "identifier": "fir01-temp", + "caption": "some new UnixUser in client's ManagedWebspace", "config": {} } """.formatted(givenParentAsset.getUuid())) @@ -246,9 +245,9 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup .contentType(ContentType.JSON) .body("", lenientlyEquals(""" { - "type": "MANAGED_WEBSPACE", - "identifier": "fir90", - "caption": "some new ManagedWebspace in client's ManagedServer", + "type": "UNIX_USER", + "identifier": "fir01-temp", + "caption": "some new UnixUser in client's ManagedWebspace", "config": {} } """)) @@ -265,7 +264,9 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup void propertyValidationsArePerformend_whenAddingAsset() { context.define("superuser-alex@hostsharing.net"); - final var givenBookingItem = givenBookingItem("D-1000111 default project", "some PrivateCloud"); + final var givenBookingItem = givenSomeNewBookingItem("D-1000111 default project", + HsBookingItemType.MANAGED_SERVER, + "some PrivateCloud"); RestAssured // @formatter:off .given() @@ -558,10 +559,22 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup }).assertSuccessful().returnedValue(); } - HsBookingItemEntity givenBookingItem(final String projectCaption, final String bookingItemCaption) { - return bookingItemRepo.findByCaption(bookingItemCaption).stream() - .filter(bi -> bi.getRelatedProject().getCaption().contains(projectCaption)) - .findAny().orElseThrow(); + HsBookingItemEntity givenSomeNewBookingItem(final String projectCaption, final HsBookingItemType bookingItemType, final String bookingItemCaption) { + return jpaAttempt.transacted(() -> { + context.define("superuser-alex@hostsharing.net"); + final var project = projectRepo.findByCaption(projectCaption).getFirst(); + final var resources = switch (bookingItemType) { + case MANAGED_SERVER -> Map.ofEntries(entry("CPUs", 1), entry("RAM", 20), entry("SSD", 25), entry("Traffic", 250)); + default -> new HashMap(); + }; + final var newBookingItem = HsBookingItemEntity.builder() + .project(project) + .type(bookingItemType) + .caption(bookingItemCaption) + .resources(resources) + .build(); + return toCleanup(bookingItemRepo.save(newBookingItem)); + }).assertSuccessful().returnedValue(); } HsHostingAssetEntity givenParentAsset(final HsHostingAssetType assetType, final String assetIdentifier) { @@ -574,16 +587,23 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup @SafeVarargs private HsHostingAssetEntity givenSomeTemporaryHostingAsset(final String identifierSuffix, final HsHostingAssetType hostingAssetType, - final Map.Entry... resources) { + final Map.Entry... config) { return jpaAttempt.transacted(() -> { context.define("superuser-alex@hostsharing.net"); + final var bookingItemType = switch (hostingAssetType) { + case CLOUD_SERVER -> HsBookingItemType.CLOUD_SERVER; + case MANAGED_SERVER -> HsBookingItemType.MANAGED_SERVER; + case MANAGED_WEBSPACE -> HsBookingItemType.MANAGED_WEBSPACE; + default -> null; + }; + final var newBookingItem = givenSomeNewBookingItem("D-1000111 default project", bookingItemType, "temp ManagedServer"); final var newAsset = HsHostingAssetEntity.builder() .uuid(UUID.randomUUID()) - .bookingItem(givenBookingItem("D-1000111 default project", "some ManagedServer")) + .bookingItem(newBookingItem) .type(hostingAssetType) .identifier("vm" + identifierSuffix) .caption("some test-asset") - .config(Map.ofEntries(resources)) + .config(Map.ofEntries(config)) .build(); return assetRepo.save(newAsset); diff --git a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetEntityPatcherUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetEntityPatcherUnitTest.java index 890932b4..96728cca 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetEntityPatcherUnitTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetEntityPatcherUnitTest.java @@ -15,7 +15,7 @@ import java.util.Map; import java.util.UUID; import java.util.stream.Stream; -import static net.hostsharing.hsadminng.hs.booking.item.TestHsBookingItem.TEST_BOOKING_ITEM; +import static net.hostsharing.hsadminng.hs.booking.item.TestHsBookingItem.TEST_CLOUD_SERVER_BOOKING_ITEM; import static net.hostsharing.hsadminng.mapper.PatchMap.entry; import static net.hostsharing.hsadminng.mapper.PatchMap.patchMap; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; @@ -70,7 +70,7 @@ class HsHostingAssetEntityPatcherUnitTest extends PatchUnitTestBase< protected HsHostingAssetEntity newInitialEntity() { final var entity = new HsHostingAssetEntity(); entity.setUuid(INITIAL_BOOKING_ITEM_UUID); - entity.setBookingItem(TEST_BOOKING_ITEM); + entity.setBookingItem(TEST_CLOUD_SERVER_BOOKING_ITEM); entity.getConfig().putAll(KeyValueMap.from(INITIAL_CONFIG)); entity.setCaption(INITIAL_CAPTION); entity.setAlarmContact(givenInitialContact); diff --git a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetEntityUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetEntityUnitTest.java index e45bdb5b..1dd7c0e1 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetEntityUnitTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetEntityUnitTest.java @@ -5,13 +5,13 @@ import org.junit.jupiter.api.Test; import java.util.Map; import static java.util.Map.entry; -import static net.hostsharing.hsadminng.hs.booking.item.TestHsBookingItem.TEST_BOOKING_ITEM; +import static net.hostsharing.hsadminng.hs.booking.item.TestHsBookingItem.TEST_CLOUD_SERVER_BOOKING_ITEM; import static org.assertj.core.api.Assertions.assertThat; class HsHostingAssetEntityUnitTest { final HsHostingAssetEntity givenParentAsset = HsHostingAssetEntity.builder() - .bookingItem(TEST_BOOKING_ITEM) + .bookingItem(TEST_CLOUD_SERVER_BOOKING_ITEM) .type(HsHostingAssetType.MANAGED_SERVER) .identifier("vm1234") .caption("some managed asset") @@ -21,7 +21,7 @@ class HsHostingAssetEntityUnitTest { entry("HDD-storage", 2048))) .build(); final HsHostingAssetEntity givenWebspace = HsHostingAssetEntity.builder() - .bookingItem(TEST_BOOKING_ITEM) + .bookingItem(TEST_CLOUD_SERVER_BOOKING_ITEM) .type(HsHostingAssetType.MANAGED_WEBSPACE) .parentAsset(givenParentAsset) .identifier("xyz00") @@ -58,7 +58,7 @@ class HsHostingAssetEntityUnitTest { void toStringContainsAllPropertiesAndResourcesSortedByKey() { assertThat(givenWebspace.toString()).isEqualTo( - "HsHostingAssetEntity(MANAGED_WEBSPACE, xyz00, some managed webspace, MANAGED_SERVER:vm1234, D-1234500:test project:test booking item, { CPUs: 2, HDD-storage: 2048, SSD-storage: 512 })"); + "HsHostingAssetEntity(MANAGED_WEBSPACE, xyz00, some managed webspace, MANAGED_SERVER:vm1234, D-1234500:test project:test cloud server booking item, { CPUs: 2, HDD-storage: 2048, SSD-storage: 512 })"); assertThat(givenUnixUser.toString()).isEqualTo( "HsHostingAssetEntity(UNIX_USER, xyz00-web, some unix-user, MANAGED_WEBSPACE:xyz00, { HDD-hard-quota: 512, HDD-soft-quota: 256, SSD-hard-quota: 256, SSD-soft-quota: 128 })"); diff --git a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetRepositoryIntegrationTest.java b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetRepositoryIntegrationTest.java index e5bc1605..5ada81b0 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetRepositoryIntegrationTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetRepositoryIntegrationTest.java @@ -90,6 +90,7 @@ class HsHostingAssetRepositoryIntegrationTest extends ContextBasedTestWithCleanu result.assertSuccessful(); assertThat(result.returnedValue()).isNotNull().extracting(HsHostingAssetEntity::getUuid).isNotNull(); assertThatAssetIsPersisted(result.returnedValue()); + assertThat(result.returnedValue().isLoaded()).isFalse(); assertThat(assetRepo.count()).isEqualTo(count + 1); } @@ -413,5 +414,6 @@ class HsHostingAssetRepositoryIntegrationTest extends ContextBasedTestWithCleanu assertThat(actualResult) .extracting(HsHostingAssetEntity::toString) .contains(serverNames); + actualResult.forEach(loadedEntity -> assertThat(loadedEntity.isLoaded()).isTrue()); } } 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 index ee6644e0..fff0fd56 100644 --- 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 @@ -1,12 +1,16 @@ package net.hostsharing.hsadminng.hs.hosting.asset.validators; +import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity; +import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType; import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity; import org.junit.jupiter.api.Test; import java.util.Map; import static java.util.Map.entry; +import static net.hostsharing.hsadminng.hs.booking.item.TestHsBookingItem.TEST_CLOUD_SERVER_BOOKING_ITEM; import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.CLOUD_SERVER; +import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_SERVER; import static org.assertj.core.api.Assertions.assertThat; class HsCloudServerHostingAssetValidatorUnitTest { @@ -28,7 +32,28 @@ class HsCloudServerHostingAssetValidatorUnitTest { final var result = validator.validate(cloudServerHostingAssetEntity); // then - assertThat(result).containsExactly("'CLOUD_SERVER:vm1234.config.RAM' is not expected but is set to '2000'"); + assertThat(result).containsExactlyInAnyOrder( + "'CLOUD_SERVER:vm1234.bookingItem' must not be null but is null", + "'CLOUD_SERVER:vm1234.config.RAM' is not expected but is set to '2000'"); + } + + @Test + void validatesInvalidIdentifier() { + // given + final var cloudServerHostingAssetEntity = HsHostingAssetEntity.builder() + .type(CLOUD_SERVER) + .identifier("xyz99") + .bookingItem(TEST_CLOUD_SERVER_BOOKING_ITEM) + .build(); + final var validator = HsHostingAssetEntityValidatorRegistry.forType(cloudServerHostingAssetEntity.getType()); + + + // when + final var result = validator.validate(cloudServerHostingAssetEntity); + + // then + assertThat(result).containsExactlyInAnyOrder( + "'identifier' expected to match '^vm[0-9][0-9][0-9][0-9]$', but is 'xyz99'"); } @Test @@ -39,4 +64,43 @@ class HsCloudServerHostingAssetValidatorUnitTest { // then assertThat(validator.properties()).map(Map::toString).isEmpty(); } + + @Test + void validatesBookingItemType() { + // given + final var mangedServerHostingAssetEntity = HsHostingAssetEntity.builder() + .type(MANAGED_SERVER) + .identifier("xyz00") + .bookingItem(HsBookingItemEntity.builder().type(HsBookingItemType.CLOUD_SERVER).build()) + .build(); + final var validator = HsHostingAssetEntityValidatorRegistry.forType(mangedServerHostingAssetEntity.getType()); + + // when + final var result = validator.validate(mangedServerHostingAssetEntity); + + // then + assertThat(result).containsExactlyInAnyOrder( + "'MANAGED_SERVER:xyz00.bookingItem' must be of type MANAGED_SERVER but is of type CLOUD_SERVER"); + } + + @Test + void validatesParentAndAssignedToAssetMustNotBeSet() { + // given + final var mangedServerHostingAssetEntity = HsHostingAssetEntity.builder() + .type(CLOUD_SERVER) + .identifier("xyz00") + .parentAsset(HsHostingAssetEntity.builder().build()) + .assignedToAsset(HsHostingAssetEntity.builder().build()) + .bookingItem(HsBookingItemEntity.builder().type(HsBookingItemType.CLOUD_SERVER).build()) + .build(); + final var validator = HsHostingAssetEntityValidatorRegistry.forType(mangedServerHostingAssetEntity.getType()); + + // when + final var result = validator.validate(mangedServerHostingAssetEntity); + + // then + assertThat(result).containsExactlyInAnyOrder( + "'CLOUD_SERVER:xyz00.parentAsset' must be null but is set to D-???????-?:null", + "'CLOUD_SERVER:xyz00.assignedToAsset' must be null but is set to D-???????-?:null"); + } } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsHostingAssetEntityValidatorRegistryUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsHostingAssetEntityValidatorRegistryUnitTest.java new file mode 100644 index 00000000..32c098f3 --- /dev/null +++ b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsHostingAssetEntityValidatorRegistryUnitTest.java @@ -0,0 +1,64 @@ +package net.hostsharing.hsadminng.hs.hosting.asset.validators; + +import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity; +import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType; +import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity; +import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType; +import org.junit.jupiter.api.Test; + +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.catchThrowable; +import static org.assertj.core.api.Assertions.entry; + +class HsHostingAssetEntityValidatorRegistryUnitTest { + + @Test + void forTypeWithUnknownTypeThrowsException() { + // when + final var thrown = catchThrowable(() -> { + HsHostingAssetEntityValidatorRegistry.forType(null); + }); + + // then + assertThat(thrown).hasMessage("no validator found for type null"); + } + + @Test + void typesReturnsAllImplementedTypes() { + // when + final var types = HsHostingAssetEntityValidatorRegistry.types(); + + // then + // TODO.test: when all types are implemented, replace with set of all types: + // assertThat(types).isEqualTo(EnumSet.allOf(HsHostingAssetType.class)); + // also remove "Implemented" from the test method name. + assertThat(types).containsExactlyInAnyOrder( + HsHostingAssetType.CLOUD_SERVER, + HsHostingAssetType.MANAGED_SERVER, + HsHostingAssetType.MANAGED_WEBSPACE, + HsHostingAssetType.UNIX_USER + ); + } + + @Test + void validatedDoesNotThrowAnExceptionForValidEntity() { + final var givenBookingItem = HsBookingItemEntity.builder() + .type(HsBookingItemType.CLOUD_SERVER) + .resources(Map.ofEntries( + entry("CPUs", 4), + entry("RAM", 20), + entry("SSD", 50), + entry("Traffic", 250) + )) + .build(); + final var validEntity = HsHostingAssetEntity.builder() + .type(HsHostingAssetType.CLOUD_SERVER) + .bookingItem(givenBookingItem) + .identifier("vm1234") + .caption("some valid cloud server") + .build(); + HsHostingAssetEntityValidatorRegistry.validated(validEntity); + } +} diff --git a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsHostingAssetEntityValidatorUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsHostingAssetEntityValidatorUnitTest.java index ddceba8e..73776e89 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsHostingAssetEntityValidatorUnitTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsHostingAssetEntityValidatorUnitTest.java @@ -1,5 +1,7 @@ package net.hostsharing.hsadminng.hs.hosting.asset.validators; +import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity; +import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType; import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity; import org.junit.jupiter.api.Test; @@ -16,12 +18,18 @@ class HsHostingAssetEntityValidatorUnitTest { final var managedServerHostingAssetEntity = HsHostingAssetEntity.builder() .type(MANAGED_SERVER) .identifier("vm1234") + .bookingItem(HsBookingItemEntity.builder().type(HsBookingItemType.MANAGED_SERVER).build()) + .parentAsset(HsHostingAssetEntity.builder().type(MANAGED_SERVER).build()) + .assignedToAsset(HsHostingAssetEntity.builder().type(MANAGED_SERVER).build()) .build(); // when final var result = catchThrowable( ()-> HsHostingAssetEntityValidatorRegistry.validated(managedServerHostingAssetEntity)); // then - assertThat(result).isNull(); // all required properties have defaults + assertThat(result.getMessage()).contains( + "'MANAGED_SERVER:vm1234.parentAsset' must be null but is set to D-???????-?:null", + "'MANAGED_SERVER:vm1234.assignedToAsset' must be null but is set to D-???????-?:null" + ); } } 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 index d22ef590..010bbf54 100644 --- 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 @@ -1,5 +1,7 @@ package net.hostsharing.hsadminng.hs.hosting.asset.validators; +import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity; +import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType; import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity; import org.junit.jupiter.api.Test; @@ -17,6 +19,9 @@ class HsManagedServerHostingAssetValidatorUnitTest { final var mangedWebspaceHostingAssetEntity = HsHostingAssetEntity.builder() .type(MANAGED_SERVER) .identifier("vm1234") + .bookingItem(HsBookingItemEntity.builder().type(HsBookingItemType.MANAGED_SERVER).build()) + .parentAsset(HsHostingAssetEntity.builder().build()) + .assignedToAsset(HsHostingAssetEntity.builder().build()) .config(Map.ofEntries( entry("monit_max_hdd_usage", "90"), entry("monit_max_cpu_usage", 2), @@ -30,8 +35,50 @@ class HsManagedServerHostingAssetValidatorUnitTest { // then assertThat(result).containsExactlyInAnyOrder( + "'MANAGED_SERVER:vm1234.parentAsset' must be null but is set to D-???????-?:null", + "'MANAGED_SERVER:vm1234.assignedToAsset' must be null but is set to D-???????-?:null", "'MANAGED_SERVER:vm1234.config.monit_max_cpu_usage' is expected to be >= 10 but is 2", "'MANAGED_SERVER:vm1234.config.monit_max_ram_usage' is expected to be <= 100 but is 101", "'MANAGED_SERVER:vm1234.config.monit_max_hdd_usage' is expected to be of type class java.lang.Integer, but is of type 'String'"); } + + @Test + void validatesInvalidIdentifier() { + // given + final var mangedServerHostingAssetEntity = HsHostingAssetEntity.builder() + .type(MANAGED_SERVER) + .identifier("xyz00") + .bookingItem(HsBookingItemEntity.builder().type(HsBookingItemType.MANAGED_SERVER).build()) + .build(); + final var validator = HsHostingAssetEntityValidatorRegistry.forType(mangedServerHostingAssetEntity.getType()); + + // when + final var result = validator.validate(mangedServerHostingAssetEntity); + + // then + assertThat(result).containsExactlyInAnyOrder( + "'identifier' expected to match '^vm[0-9][0-9][0-9][0-9]$', but is 'xyz00'"); + } + + @Test + void validatesParentAndAssignedToAssetMustNotBeSet() { + // given + final var mangedServerHostingAssetEntity = HsHostingAssetEntity.builder() + .type(MANAGED_SERVER) + .identifier("xyz00") + .parentAsset(HsHostingAssetEntity.builder().build()) + .assignedToAsset(HsHostingAssetEntity.builder().build()) + .bookingItem(HsBookingItemEntity.builder().type(HsBookingItemType.CLOUD_SERVER).build()) + .build(); + final var validator = HsHostingAssetEntityValidatorRegistry.forType(mangedServerHostingAssetEntity.getType()); + + // when + final var result = validator.validate(mangedServerHostingAssetEntity); + + // then + assertThat(result).containsExactlyInAnyOrder( + "'MANAGED_SERVER:xyz00.bookingItem' must be of type MANAGED_SERVER but is of type CLOUD_SERVER", + "'MANAGED_SERVER:xyz00.parentAsset' must be null but is set to D-???????-?:null", + "'MANAGED_SERVER:xyz00.assignedToAsset' must be null but is set to D-???????-?:null"); + } } 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 index d2e74894..7b981b68 100644 --- 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 @@ -28,6 +28,11 @@ class HsManagedWebspaceHostingAssetValidatorUnitTest { entry("SLA-EMail", true) )) .build(); + final HsBookingItemEntity cloudServerBookingItem = managedServerBookingItem.toBuilder() + .type(HsBookingItemType.CLOUD_SERVER) + .caption("Test Cloud-Server") + .build(); + final HsHostingAssetEntity mangedServerAssetEntity = HsHostingAssetEntity.builder() .type(HsHostingAssetType.MANAGED_SERVER) .bookingItem(managedServerBookingItem) @@ -38,13 +43,46 @@ class HsManagedWebspaceHostingAssetValidatorUnitTest { entry("monit_max_ram_usage", 90) )) .build(); + final HsHostingAssetEntity cloudServerAssetEntity = HsHostingAssetEntity.builder() + .type(HsHostingAssetType.CLOUD_SERVER) + .bookingItem(cloudServerBookingItem) + .identifier("vm1234") + .config(Map.ofEntries( + entry("monit_max_ssd_usage", 70), + entry("monit_max_cpu_usage", 80), + entry("monit_max_ram_usage", 90) + )) + .build(); @Test - void validatesIdentifier() { + void acceptsAlienIdentifierPrefixForPreExistingEntity() { // given final var validator = HsHostingAssetEntityValidatorRegistry.forType(MANAGED_WEBSPACE); final var mangedWebspaceHostingAssetEntity = HsHostingAssetEntity.builder() .type(MANAGED_WEBSPACE) + .bookingItem(HsBookingItemEntity.builder() + .type(HsBookingItemType.MANAGED_WEBSPACE) + .resources(Map.ofEntries(entry("SSD", 25), entry("Traffic", 250))) + .build()) + .parentAsset(mangedServerAssetEntity) + .identifier("xyz00") + .isLoaded(true) + .build(); + + // when + final var result = validator.validate(mangedWebspaceHostingAssetEntity); + + // then + assertThat(result).isEmpty(); + } + + @Test + void validatesIdentifierAndReferencedEntities() { + // given + final var validator = HsHostingAssetEntityValidatorRegistry.forType(MANAGED_WEBSPACE); + final var mangedWebspaceHostingAssetEntity = HsHostingAssetEntity.builder() + .type(MANAGED_WEBSPACE) + .bookingItem(HsBookingItemEntity.builder().type(HsBookingItemType.MANAGED_WEBSPACE).build()) .parentAsset(mangedServerAssetEntity) .identifier("xyz00") .build(); @@ -62,6 +100,7 @@ class HsManagedWebspaceHostingAssetValidatorUnitTest { final var validator = HsHostingAssetEntityValidatorRegistry.forType(MANAGED_WEBSPACE); final var mangedWebspaceHostingAssetEntity = HsHostingAssetEntity.builder() .type(MANAGED_WEBSPACE) + .bookingItem(HsBookingItemEntity.builder().type(HsBookingItemType.MANAGED_WEBSPACE).build()) .parentAsset(mangedServerAssetEntity) .identifier("abc00") .config(Map.ofEntries( @@ -82,6 +121,11 @@ class HsManagedWebspaceHostingAssetValidatorUnitTest { final var validator = HsHostingAssetEntityValidatorRegistry.forType(MANAGED_WEBSPACE); final var mangedWebspaceHostingAssetEntity = HsHostingAssetEntity.builder() .type(MANAGED_WEBSPACE) + .bookingItem(HsBookingItemEntity.builder() + .type(HsBookingItemType.MANAGED_WEBSPACE) + .caption("some ManagedWebspace") + .resources(Map.ofEntries(entry("SSD", 25), entry("Traffic", 250))) + .build()) .parentAsset(mangedServerAssetEntity) .identifier("abc00") .build(); @@ -92,4 +136,30 @@ class HsManagedWebspaceHostingAssetValidatorUnitTest { // then assertThat(result).isEmpty(); } + + @Test + void validatesEntityReferences() { + // given + final var validator = HsHostingAssetEntityValidatorRegistry.forType(MANAGED_WEBSPACE); + final var mangedWebspaceHostingAssetEntity = HsHostingAssetEntity.builder() + .type(MANAGED_WEBSPACE) + .bookingItem(HsBookingItemEntity.builder() + .type(HsBookingItemType.MANAGED_SERVER) + .caption("some ManagedServer") + .resources(Map.ofEntries(entry("SSD", 25), entry("Traffic", 250))) + .build()) + .parentAsset(cloudServerAssetEntity) + .assignedToAsset(HsHostingAssetEntity.builder().build()) + .identifier("abc00") + .build(); + + // when + final var result = validator.validate(mangedWebspaceHostingAssetEntity); + + // then + assertThat(result).containsExactly( + "'MANAGED_WEBSPACE:abc00.bookingItem' must be of type MANAGED_WEBSPACE but is of type MANAGED_SERVER", + "'MANAGED_WEBSPACE:abc00.parentAsset' must be of type MANAGED_SERVER but is of type CLOUD_SERVER", + "'MANAGED_WEBSPACE:abc00.assignedToAsset' must be null but is set to D-???????-?:some ManagedServer"); + } } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsUnixUserHostingAssetValidatorUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsUnixUserHostingAssetValidatorUnitTest.java new file mode 100644 index 00000000..afe265b0 --- /dev/null +++ b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsUnixUserHostingAssetValidatorUnitTest.java @@ -0,0 +1,30 @@ +package net.hostsharing.hsadminng.hs.hosting.asset.validators; + +import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity; +import org.junit.jupiter.api.Test; + +import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_WEBSPACE; +import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.UNIX_USER; +import static org.assertj.core.api.Assertions.assertThat; + +class HsUnixUserHostingAssetValidatorUnitTest { + + @Test + void validatesInvalidIdentifier() { + // given + final var unixUserHostingAsset = HsHostingAssetEntity.builder() + .type(UNIX_USER) + .parentAsset(HsHostingAssetEntity.builder().type(MANAGED_WEBSPACE).identifier("abc00").build()) + .identifier("xyz99-temp") + .build(); + final var validator = HsHostingAssetEntityValidatorRegistry.forType(unixUserHostingAsset.getType()); + + + // when + final var result = validator.validate(unixUserHostingAsset); + + // then + assertThat(result).containsExactly( + "'identifier' expected to match '^abc00$|^abc00-[a-z0-9]+$', but is 'xyz99-temp'"); + } +}