diff --git a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetType.java b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetType.java index 32388aa0..6a0846a9 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetType.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetType.java @@ -21,6 +21,9 @@ import static java.util.stream.Collectors.toSet; import static net.hostsharing.hsadminng.hs.hosting.asset.EntityTypeRelation.*; import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.RelationPolicy.OPTIONAL; import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.RelationPolicy.REQUIRED; +import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.RelationType.ASSIGNED_TO_ASSET; +import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.RelationType.BOOKING_ITEM; +import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.RelationType.PARENT_ASSET; public enum HsHostingAssetType implements Node { SAME_TYPE, // pseudo-type for recursive references @@ -135,43 +138,49 @@ public enum HsHostingAssetType implements Node { // TODO.refa: try to get rid of the following similar methods: public RelationPolicy bookingItemPolicy() { - return stream(relations).filter(r -> r.relatedType instanceof HsBookingItemType) - .map(r -> r.relationType) + return stream(relations) + .filter(r -> r.relationType == BOOKING_ITEM) + .map(r -> r.relationPolicy) .reduce(HsHostingAssetType::onlyASingleElementExpectedException) .orElse(RelationPolicy.FORBIDDEN); } public HsBookingItemType bookingItemType() { - return stream(relations).filter(r -> r.relatedType instanceof HsBookingItemType) - .map(r -> HsBookingItemType.valueOf(r.relatedType.toString())) + return stream(relations) + .filter(r -> r.relationType == BOOKING_ITEM) + .map(r -> HsBookingItemType.valueOf(r.relatedType(this).toString())) .reduce(HsHostingAssetType::onlyASingleElementExpectedException) .orElse(null); } public RelationPolicy parentAssetPolicy() { - return stream(relations).filter(r -> r.relatedType instanceof HsHostingAssetType) - .map(r -> r.relationType) + return stream(relations) + .filter(r -> r.relationType == PARENT_ASSET) + .map(r -> r.relationPolicy) .reduce(HsHostingAssetType::onlyASingleElementExpectedException) .orElse(RelationPolicy.FORBIDDEN); } public HsHostingAssetType parentAssetType() { - return stream(relations).filter(r -> r.relatedType instanceof HsHostingAssetType) - .map(r -> HsHostingAssetType.valueOf(r.relatedType.toString())) + return stream(relations) + .filter(r -> r.relationType == PARENT_ASSET) + .map(r -> HsHostingAssetType.valueOf(r.relatedType(this).toString())) .reduce(HsHostingAssetType::onlyASingleElementExpectedException) .orElse(null); } public RelationPolicy assignedToAssetPolicy() { - return stream(relations).filter(r -> r.relatedType == (Object) HsHostingAssetType.class) - .map(r -> r.relationType) + return stream(relations) + .filter(r -> r.relationType == ASSIGNED_TO_ASSET) + .map(r -> r.relationPolicy) .reduce(HsHostingAssetType::onlyASingleElementExpectedException) .orElse(RelationPolicy.FORBIDDEN); } public HsHostingAssetType assignedToAssetType() { - return stream(relations).filter(r -> r.relatedType == (Object) HsHostingAssetType.class) - .map(r -> HsHostingAssetType.valueOf(r.relatedType.toString())) + return stream(relations) + .filter(r -> r.relationType == ASSIGNED_TO_ASSET) + .map(r -> HsHostingAssetType.valueOf(r.relatedType(this).toString())) .reduce(HsHostingAssetType::onlyASingleElementExpectedException) .orElse(null); } @@ -183,14 +192,10 @@ public enum HsHostingAssetType implements Node { @Override public List edges() { return stream(relations) - .map(r -> nodeName() + r.edge + resolveNode(r.relatedType).nodeName()) + .map(r -> nodeName() + r.edge + r.relatedType(this).nodeName()) .toList(); } - private Node resolveNode(final Node node) { - return node == SAME_TYPE ? this : node; - } - @Override public String nodeName() { return "HA_" + name(); @@ -310,28 +315,41 @@ public enum HsHostingAssetType implements Node { public enum RelationPolicy { FORBIDDEN, OPTIONAL, REQUIRED } + + public enum RelationType { + BOOKING_ITEM, + PARENT_ASSET, + ASSIGNED_TO_ASSET + } } @AllArgsConstructor class EntityTypeRelation { - final HsHostingAssetType.RelationPolicy relationType; + + final HsHostingAssetType.RelationPolicy relationPolicy; + final HsHostingAssetType.RelationType relationType; final Function getter; - final T relatedType; + private final T relatedType; final String edge; + public T relatedType(final HsHostingAssetType referringType) { + //noinspection unchecked + return relatedType == HsHostingAssetType.SAME_TYPE ? (T) referringType : relatedType; + } + static EntityTypeRelation requires(final HsBookingItemType bookingItemType) { - return new EntityTypeRelation<>(REQUIRED, HsHostingAssetEntity::getBookingItem, bookingItemType, " *==> "); + return new EntityTypeRelation<>(REQUIRED, BOOKING_ITEM, HsHostingAssetEntity::getBookingItem, bookingItemType, " *==> "); } static EntityTypeRelation optionalParent(final HsHostingAssetType hostingAssetType) { - return new EntityTypeRelation<>(OPTIONAL, HsHostingAssetEntity::getParentAsset, hostingAssetType, " o..> "); + return new EntityTypeRelation<>(OPTIONAL, PARENT_ASSET, HsHostingAssetEntity::getParentAsset, hostingAssetType, " o..> "); } static EntityTypeRelation requiredParent(final HsHostingAssetType hostingAssetType) { - return new EntityTypeRelation<>(REQUIRED, HsHostingAssetEntity::getParentAsset, hostingAssetType, " *==> "); + return new EntityTypeRelation<>(REQUIRED, PARENT_ASSET, HsHostingAssetEntity::getParentAsset, hostingAssetType, " *==> "); } static EntityTypeRelation assignedTo(final HsHostingAssetType hostingAssetType) { - return new EntityTypeRelation<>(REQUIRED, HsHostingAssetEntity::getAssignedToAsset, hostingAssetType, " o..> "); + return new EntityTypeRelation<>(REQUIRED, ASSIGNED_TO_ASSET, HsHostingAssetEntity::getAssignedToAsset, hostingAssetType, " o..> "); } } diff --git a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainDnsSetupHostingAssetValidator.java b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainDnsSetupHostingAssetValidator.java index eea095fb..c263be60 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainDnsSetupHostingAssetValidator.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainDnsSetupHostingAssetValidator.java @@ -60,7 +60,7 @@ class HsDomainDnsSetupHostingAssetValidator extends HsHostingAssetEntityValidato @Override protected Pattern identifierPattern(final HsHostingAssetEntity assetEntity) { - return Pattern.compile("^" + assetEntity.getParentAsset().getIdentifier() + IDENTIFIER_SUFFIX + "$"); + return Pattern.compile("^" + assetEntity.getParentAsset().getIdentifier() + Pattern.quote(IDENTIFIER_SUFFIX) + "$"); } @Override 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 53858c29..187630fb 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 @@ -20,9 +20,6 @@ import java.util.stream.Stream; import static java.util.Arrays.stream; import static java.util.Collections.emptyList; import static java.util.Optional.ofNullable; -import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.RelationPolicy.FORBIDDEN; -import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.RelationPolicy.OPTIONAL; -import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.RelationPolicy.REQUIRED; public abstract class HsHostingAssetEntityValidator extends HsEntityValidator { @@ -34,7 +31,9 @@ public abstract class HsHostingAssetEntityValidator extends HsEntityValidator... properties) { + final HsHostingAssetType assetType, + final AlarmContact alarmContactValidation, + final ValidatableProperty... properties) { super(properties); this.bookingItemReferenceValidation = new ReferenceValidator<>( assetType.bookingItemPolicy(), @@ -73,11 +72,11 @@ public abstract class HsHostingAssetEntityValidator extends HsEntityValidator validateEntityReferencesAndProperties(final HsHostingAssetEntity assetEntity) { return Stream.of( - validateReferencedEntity(assetEntity, "bookingItem", bookingItemReferenceValidation::validate), - validateReferencedEntity(assetEntity, "parentAsset", parentAssetReferenceValidation::validate), - validateReferencedEntity(assetEntity, "assignedToAsset", assignedToAssetReferenceValidation::validate), - validateReferencedEntity(assetEntity, "alarmContact", alarmContactValidation::validate), - validateProperties(assetEntity)) + validateReferencedEntity(assetEntity, "bookingItem", bookingItemReferenceValidation::validate), + validateReferencedEntity(assetEntity, "parentAsset", parentAssetReferenceValidation::validate), + validateReferencedEntity(assetEntity, "assignedToAsset", assignedToAssetReferenceValidation::validate), + validateReferencedEntity(assetEntity, "alarmContact", alarmContactValidation::validate), + validateProperties(assetEntity)) .filter(Objects::nonNull) .flatMap(List::stream) .filter(Objects::nonNull) @@ -97,25 +96,28 @@ public abstract class HsHostingAssetEntityValidator extends HsEntityValidator optionallyValidate(final HsHostingAssetEntity assetEntity) { return assetEntity != null - ? enrich(prefix(assetEntity.toShortString(), "parentAsset"), - HsHostingAssetEntityValidatorRegistry.forType(assetEntity.getType()).validateContext(assetEntity)) + ? enrich( + prefix(assetEntity.toShortString(), "parentAsset"), + HsHostingAssetEntityValidatorRegistry.forType(assetEntity.getType()).validateContext(assetEntity)) : emptyList(); } private static List optionallyValidate(final HsBookingItemEntity bookingItem) { return bookingItem != null - ? enrich(prefix(bookingItem.toShortString(), "bookingItem"), - HsBookingItemEntityValidatorRegistry.forType(bookingItem.getType()).validateContext(bookingItem)) + ? enrich( + prefix(bookingItem.toShortString(), "bookingItem"), + HsBookingItemEntityValidatorRegistry.forType(bookingItem.getType()).validateContext(bookingItem)) : emptyList(); } protected List validateAgainstSubEntities(final HsHostingAssetEntity assetEntity) { - return enrich(prefix(assetEntity.toShortString(), "config"), + return enrich( + prefix(assetEntity.toShortString(), "config"), stream(propertyValidators) - .filter(ValidatableProperty::isTotalsValidator) - .map(prop -> validateMaxTotalValue(assetEntity, prop)) - .filter(Objects::nonNull) - .toList()); + .filter(ValidatableProperty::isTotalsValidator) + .map(prop -> validateMaxTotalValue(assetEntity, prop)) + .filter(Objects::nonNull) + .toList()); } // TODO.test: check, if there are any hosting assets which need this validation at all @@ -140,7 +142,9 @@ public abstract class HsHostingAssetEntityValidator extends HsEntityValidator { private final HsHostingAssetType.RelationPolicy policy; - private final T subEntityType; - private final Function subEntityGetter; - private final Function subEntityTypeGetter; + private final T referencedEntityType; + private final Function referencedEntityGetter; + private final Function referencedEntityTypeGetter; public ReferenceValidator( final HsHostingAssetType.RelationPolicy policy, final T subEntityType, - final Function subEntityGetter, - final Function subEntityTypeGetter) { + final Function referencedEntityGetter, + final Function referencedEntityTypeGetter) { this.policy = policy; - this.subEntityType = subEntityType; - this.subEntityGetter = subEntityGetter; - this.subEntityTypeGetter = subEntityTypeGetter; + this.referencedEntityType = subEntityType; + this.referencedEntityGetter = referencedEntityGetter; + this.referencedEntityTypeGetter = referencedEntityTypeGetter; } public ReferenceValidator( final HsHostingAssetType.RelationPolicy policy, - final Function subEntityGetter) { + final Function referencedEntityGetter) { this.policy = policy; - this.subEntityType = null; - this.subEntityGetter = subEntityGetter; - this.subEntityTypeGetter = e -> null; + this.referencedEntityType = null; + this.referencedEntityGetter = referencedEntityGetter; + this.referencedEntityTypeGetter = e -> null; } List validate(final HsHostingAssetEntity assetEntity, final String referenceFieldName) { - final var subEntity = subEntityGetter.apply(assetEntity); - if (policy == REQUIRED && subEntity == null) { - return List.of(referenceFieldName + "' must not be null but is null"); - } - if ((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 (policy != OPTIONAL && subEntityType != null && subItemType != subEntityType) { - return List.of(referenceFieldName + "' must be of type " + subEntityType + " but is of type " + subItemType); + final var actualEntity = referencedEntityGetter.apply(assetEntity); + final var actualEntityType = actualEntity != null ? referencedEntityTypeGetter.apply(actualEntity) : null; + + switch (policy) { + case REQUIRED: + if (actualEntityType != referencedEntityType) { + return List.of(actualEntityType == null + ? referenceFieldName + "' must be of type " + referencedEntityType + " but is null" + : referenceFieldName + "' must be of type " + referencedEntityType + " but is of type " + actualEntityType); + } + break; + case OPTIONAL: + if (actualEntityType != null && actualEntityType != referencedEntityType) { + return List.of(referenceFieldName + "' must be null or of type " + referencedEntityType + " but is of type " + + actualEntityType); + } + break; + case FORBIDDEN: + if (actualEntityType != null) { + return List.of(referenceFieldName + "' must be null but is of type " + actualEntityType); + } + break; } return emptyList(); } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetControllerRestTest.java b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetControllerRestTest.java index 8cd2eb3f..eed85585 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetControllerRestTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetControllerRestTest.java @@ -186,6 +186,24 @@ public class HsHostingAssetControllerRestTest { } ] """), + DOMAIN_SETUP( + List.of( + HsHostingAssetEntity.builder() + .type(HsHostingAssetType.DOMAIN_SETUP) + .identifier("example.org") + .caption("some fake Domain-Setup") + .build()), + """ + [ + { + "type": "DOMAIN_SETUP", + "identifier": "example.org", + "caption": "some fake Domain-Setup", + "alarmContact": null, + "config": {} + } + ] + """), DOMAIN_DNS_SETUP( List.of( HsHostingAssetEntity.builder() 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 69fe01bb..8361c5f4 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 @@ -33,7 +33,7 @@ class HsCloudServerHostingAssetValidatorUnitTest { // then assertThat(result).containsExactlyInAnyOrder( - "'CLOUD_SERVER:vm1234.bookingItem' must not be null but is null", + "'CLOUD_SERVER:vm1234.bookingItem' must be of type CLOUD_SERVER but is null", "'CLOUD_SERVER:vm1234.config.RAM' is not expected but is set to '2000'"); } @@ -84,14 +84,14 @@ class HsCloudServerHostingAssetValidatorUnitTest { } @Test - void validatesParentAndAssignedToAssetMustNotBeSet() { + void rejectsInvalidReferencedEntities() { // given final var mangedServerHostingAssetEntity = HsHostingAssetEntity.builder() .type(CLOUD_SERVER) - .identifier("xyz00") - .parentAsset(HsHostingAssetEntity.builder().build()) - .assignedToAsset(HsHostingAssetEntity.builder().build()) + .identifier("vm1234") .bookingItem(HsBookingItemEntity.builder().type(HsBookingItemType.CLOUD_SERVER).build()) + .parentAsset(HsHostingAssetEntity.builder().type(MANAGED_SERVER).build()) + .assignedToAsset(HsHostingAssetEntity.builder().type(CLOUD_SERVER).build()) .build(); final var validator = HsHostingAssetEntityValidatorRegistry.forType(mangedServerHostingAssetEntity.getType()); @@ -100,7 +100,7 @@ class HsCloudServerHostingAssetValidatorUnitTest { // 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"); + "'CLOUD_SERVER:vm1234.parentAsset' must be null but is of type MANAGED_SERVER", + "'CLOUD_SERVER:vm1234.assignedToAsset' must be null but is of type CLOUD_SERVER"); } } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainDnsSetupHostingAssetValidatorUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainDnsSetupHostingAssetValidatorUnitTest.java index 6a4e1026..681196ae 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainDnsSetupHostingAssetValidatorUnitTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainDnsSetupHostingAssetValidatorUnitTest.java @@ -95,7 +95,7 @@ class HsDomainDnsSetupHostingAssetValidatorUnitTest { // then assertThat(result).containsExactly( - "'identifier' expected to match '^example.org|DNS$', but is 'example.org'" + "'identifier' expected to match '^example.org\\Q|DNS\\E$', but is 'example.org'" ); } @@ -113,12 +113,12 @@ class HsDomainDnsSetupHostingAssetValidatorUnitTest { } @Test - void validatesReferencedEntities() { + void rejectsInvalidReferencedEntities() { // given final var mangedServerHostingAssetEntity = validEntityBuilder() - .parentAsset(HsHostingAssetEntity.builder().build()) - .assignedToAsset(HsHostingAssetEntity.builder().build()) .bookingItem(HsBookingItemEntity.builder().type(HsBookingItemType.CLOUD_SERVER).build()) + .parentAsset(null) + .assignedToAsset(HsHostingAssetEntity.builder().type(DOMAIN_SETUP).build()) .build(); final var validator = HsHostingAssetEntityValidatorRegistry.forType(mangedServerHostingAssetEntity.getType()); @@ -127,9 +127,9 @@ class HsDomainDnsSetupHostingAssetValidatorUnitTest { // then assertThat(result).containsExactlyInAnyOrder( - "'DOMAIN_DNS_SETUP:example.org|DNS.bookingItem' must be null but is set to D-???????-?:null", - "'DOMAIN_DNS_SETUP:example.org|DNS.parentAsset' must be of type DOMAIN_SETUP but is of type null", - "'DOMAIN_DNS_SETUP:example.org|DNS.assignedToAsset' must be null but is set to D-???????-?:null"); + "'DOMAIN_DNS_SETUP:example.org|DNS.bookingItem' must be null but is of type CLOUD_SERVER", + "'DOMAIN_DNS_SETUP:example.org|DNS.parentAsset' must be of type DOMAIN_SETUP but is null", + "'DOMAIN_DNS_SETUP:example.org|DNS.assignedToAsset' must be null but is of type DOMAIN_SETUP"); } @Test diff --git a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainSetupHostingAssetValidatorUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainSetupHostingAssetValidatorUnitTest.java new file mode 100644 index 00000000..c9b99784 --- /dev/null +++ b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainSetupHostingAssetValidatorUnitTest.java @@ -0,0 +1,112 @@ +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.HsHostingAssetEntity.HsHostingAssetEntityBuilder; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; + +import java.util.Map; + +import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.CLOUD_SERVER; +import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.DOMAIN_SETUP; +import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_SERVER; +import static org.assertj.core.api.Assertions.assertThat; + +class HsDomainSetupHostingAssetValidatorUnitTest { + + static HsHostingAssetEntityBuilder validEntityBuilder() { + return HsHostingAssetEntity.builder() + .type(DOMAIN_SETUP) + .identifier("example.org"); + } + + enum InvalidDomainNameIdentifier { + EMPTY(""), + TOO_LONG("abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456890123456789.de"), + DASH_AT_BEGINNING("-example.com"), + DOT_AT_BEGINNING(".example.com"), + DOT_AT_END("example.com."); + + final String domainName; + + InvalidDomainNameIdentifier(final String domainName) { + this.domainName = domainName; + } + } + + @ParameterizedTest + @EnumSource(InvalidDomainNameIdentifier.class) + void rejectsInvalidIdentifier(final InvalidDomainNameIdentifier testCase) { + // given + final var givenEntity = validEntityBuilder().identifier(testCase.domainName).build(); + final var validator = HsHostingAssetEntityValidatorRegistry.forType(givenEntity.getType()); + + // when + final var result = validator.validateEntity(givenEntity); + + // then + assertThat(result).containsExactly( + "'identifier' expected to match '^((?!-)[A-Za-z0-9-]{1,63}(?