HostingAsset-Hierarchie spec in enum HsHostingAssetType and generates PlantUML #72

Merged
hsh-michaelhoennig merged 9 commits from add-mermaid-graph-generator-for-hosting-asset-type-structure into master 2024-07-09 14:32:14 +02:00
11 changed files with 262 additions and 95 deletions
Showing only changes of commit 89cd2ec6e2 - Show all commits

View File

@ -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.EntityTypeRelation.*;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.RelationPolicy.OPTIONAL; 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.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 { public enum HsHostingAssetType implements Node {
SAME_TYPE, // pseudo-type for recursive references 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: // TODO.refa: try to get rid of the following similar methods:
public RelationPolicy bookingItemPolicy() { public RelationPolicy bookingItemPolicy() {
return stream(relations).filter(r -> r.relatedType instanceof HsBookingItemType) return stream(relations)
.map(r -> r.relationType) .filter(r -> r.relationType == BOOKING_ITEM)
.map(r -> r.relationPolicy)
.reduce(HsHostingAssetType::onlyASingleElementExpectedException) .reduce(HsHostingAssetType::onlyASingleElementExpectedException)
.orElse(RelationPolicy.FORBIDDEN); .orElse(RelationPolicy.FORBIDDEN);
} }
public HsBookingItemType bookingItemType() { public HsBookingItemType bookingItemType() {
return stream(relations).filter(r -> r.relatedType instanceof HsBookingItemType) return stream(relations)
.map(r -> HsBookingItemType.valueOf(r.relatedType.toString())) .filter(r -> r.relationType == BOOKING_ITEM)
.map(r -> HsBookingItemType.valueOf(r.relatedType(this).toString()))
.reduce(HsHostingAssetType::onlyASingleElementExpectedException) .reduce(HsHostingAssetType::onlyASingleElementExpectedException)
.orElse(null); .orElse(null);
} }
public RelationPolicy parentAssetPolicy() { public RelationPolicy parentAssetPolicy() {
return stream(relations).filter(r -> r.relatedType instanceof HsHostingAssetType) return stream(relations)
.map(r -> r.relationType) .filter(r -> r.relationType == PARENT_ASSET)
.map(r -> r.relationPolicy)
.reduce(HsHostingAssetType::onlyASingleElementExpectedException) .reduce(HsHostingAssetType::onlyASingleElementExpectedException)
.orElse(RelationPolicy.FORBIDDEN); .orElse(RelationPolicy.FORBIDDEN);
} }
public HsHostingAssetType parentAssetType() { public HsHostingAssetType parentAssetType() {
return stream(relations).filter(r -> r.relatedType instanceof HsHostingAssetType) return stream(relations)
.map(r -> HsHostingAssetType.valueOf(r.relatedType.toString())) .filter(r -> r.relationType == PARENT_ASSET)
.map(r -> HsHostingAssetType.valueOf(r.relatedType(this).toString()))
.reduce(HsHostingAssetType::onlyASingleElementExpectedException) .reduce(HsHostingAssetType::onlyASingleElementExpectedException)
.orElse(null); .orElse(null);
} }
public RelationPolicy assignedToAssetPolicy() { public RelationPolicy assignedToAssetPolicy() {
return stream(relations).filter(r -> r.relatedType == (Object) HsHostingAssetType.class) return stream(relations)
.map(r -> r.relationType) .filter(r -> r.relationType == ASSIGNED_TO_ASSET)
.map(r -> r.relationPolicy)
.reduce(HsHostingAssetType::onlyASingleElementExpectedException) .reduce(HsHostingAssetType::onlyASingleElementExpectedException)
.orElse(RelationPolicy.FORBIDDEN); .orElse(RelationPolicy.FORBIDDEN);
} }
public HsHostingAssetType assignedToAssetType() { public HsHostingAssetType assignedToAssetType() {
return stream(relations).filter(r -> r.relatedType == (Object) HsHostingAssetType.class) return stream(relations)
.map(r -> HsHostingAssetType.valueOf(r.relatedType.toString())) .filter(r -> r.relationType == ASSIGNED_TO_ASSET)
.map(r -> HsHostingAssetType.valueOf(r.relatedType(this).toString()))
.reduce(HsHostingAssetType::onlyASingleElementExpectedException) .reduce(HsHostingAssetType::onlyASingleElementExpectedException)
.orElse(null); .orElse(null);
} }
@ -183,14 +192,10 @@ public enum HsHostingAssetType implements Node {
@Override @Override
public List<String> edges() { public List<String> edges() {
return stream(relations) return stream(relations)
.map(r -> nodeName() + r.edge + resolveNode(r.relatedType).nodeName()) .map(r -> nodeName() + r.edge + r.relatedType(this).nodeName())
.toList(); .toList();
} }
private Node resolveNode(final Node node) {
return node == SAME_TYPE ? this : node;
}
@Override @Override
public String nodeName() { public String nodeName() {
return "HA_" + name(); return "HA_" + name();
@ -310,28 +315,41 @@ public enum HsHostingAssetType implements Node {
public enum RelationPolicy { public enum RelationPolicy {
FORBIDDEN, OPTIONAL, REQUIRED FORBIDDEN, OPTIONAL, REQUIRED
} }
public enum RelationType {
BOOKING_ITEM,
PARENT_ASSET,
ASSIGNED_TO_ASSET
}
} }
@AllArgsConstructor @AllArgsConstructor
class EntityTypeRelation<E, T extends Node> { class EntityTypeRelation<E, T extends Node> {
final HsHostingAssetType.RelationPolicy relationType;
final HsHostingAssetType.RelationPolicy relationPolicy;
final HsHostingAssetType.RelationType relationType;
final Function<HsHostingAssetEntity, E> getter; final Function<HsHostingAssetEntity, E> getter;
final T relatedType; private final T relatedType;
final String edge; final String edge;
public T relatedType(final HsHostingAssetType referringType) {
//noinspection unchecked
return relatedType == HsHostingAssetType.SAME_TYPE ? (T) referringType : relatedType;
}
static EntityTypeRelation<HsBookingItemEntity, HsBookingItemType> requires(final HsBookingItemType bookingItemType) { static EntityTypeRelation<HsBookingItemEntity, HsBookingItemType> requires(final HsBookingItemType bookingItemType) {
return new EntityTypeRelation<>(REQUIRED, HsHostingAssetEntity::getBookingItem, bookingItemType, " *==> "); return new EntityTypeRelation<>(REQUIRED, BOOKING_ITEM, HsHostingAssetEntity::getBookingItem, bookingItemType, " *==> ");
} }
static EntityTypeRelation<HsHostingAssetEntity, HsHostingAssetType> optionalParent(final HsHostingAssetType hostingAssetType) { static EntityTypeRelation<HsHostingAssetEntity, HsHostingAssetType> optionalParent(final HsHostingAssetType hostingAssetType) {
return new EntityTypeRelation<>(OPTIONAL, HsHostingAssetEntity::getParentAsset, hostingAssetType, " o..> "); return new EntityTypeRelation<>(OPTIONAL, PARENT_ASSET, HsHostingAssetEntity::getParentAsset, hostingAssetType, " o..> ");
} }
static EntityTypeRelation<HsHostingAssetEntity, HsHostingAssetType> requiredParent(final HsHostingAssetType hostingAssetType) { static EntityTypeRelation<HsHostingAssetEntity, HsHostingAssetType> requiredParent(final HsHostingAssetType hostingAssetType) {
return new EntityTypeRelation<>(REQUIRED, HsHostingAssetEntity::getParentAsset, hostingAssetType, " *==> "); return new EntityTypeRelation<>(REQUIRED, PARENT_ASSET, HsHostingAssetEntity::getParentAsset, hostingAssetType, " *==> ");
} }
static EntityTypeRelation<HsHostingAssetEntity, HsHostingAssetType> assignedTo(final HsHostingAssetType hostingAssetType) { static EntityTypeRelation<HsHostingAssetEntity, HsHostingAssetType> assignedTo(final HsHostingAssetType hostingAssetType) {
return new EntityTypeRelation<>(REQUIRED, HsHostingAssetEntity::getAssignedToAsset, hostingAssetType, " o..> "); return new EntityTypeRelation<>(REQUIRED, ASSIGNED_TO_ASSET, HsHostingAssetEntity::getAssignedToAsset, hostingAssetType, " o..> ");
} }
} }

View File

@ -60,7 +60,7 @@ class HsDomainDnsSetupHostingAssetValidator extends HsHostingAssetEntityValidato
@Override @Override
protected Pattern identifierPattern(final HsHostingAssetEntity assetEntity) { 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 @Override

View File

@ -20,9 +20,6 @@ import java.util.stream.Stream;
import static java.util.Arrays.stream; import static java.util.Arrays.stream;
import static java.util.Collections.emptyList; import static java.util.Collections.emptyList;
import static java.util.Optional.ofNullable; 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<HsHostingAssetEntity> { public abstract class HsHostingAssetEntityValidator extends HsEntityValidator<HsHostingAssetEntity> {
@ -34,7 +31,9 @@ public abstract class HsHostingAssetEntityValidator extends HsEntityValidator<Hs
private final HsHostingAssetEntityValidator.AlarmContact alarmContactValidation; private final HsHostingAssetEntityValidator.AlarmContact alarmContactValidation;
HsHostingAssetEntityValidator( HsHostingAssetEntityValidator(
final HsHostingAssetType assetType, final AlarmContact alarmContactValidation, final ValidatableProperty<?, ?>... properties) { final HsHostingAssetType assetType,
final AlarmContact alarmContactValidation,
final ValidatableProperty<?, ?>... properties) {
super(properties); super(properties);
this.bookingItemReferenceValidation = new ReferenceValidator<>( this.bookingItemReferenceValidation = new ReferenceValidator<>(
assetType.bookingItemPolicy(), assetType.bookingItemPolicy(),
@ -73,11 +72,11 @@ public abstract class HsHostingAssetEntityValidator extends HsEntityValidator<Hs
private List<String> validateEntityReferencesAndProperties(final HsHostingAssetEntity assetEntity) { private List<String> validateEntityReferencesAndProperties(final HsHostingAssetEntity assetEntity) {
return Stream.of( return Stream.of(
validateReferencedEntity(assetEntity, "bookingItem", bookingItemReferenceValidation::validate), validateReferencedEntity(assetEntity, "bookingItem", bookingItemReferenceValidation::validate),
validateReferencedEntity(assetEntity, "parentAsset", parentAssetReferenceValidation::validate), validateReferencedEntity(assetEntity, "parentAsset", parentAssetReferenceValidation::validate),
validateReferencedEntity(assetEntity, "assignedToAsset", assignedToAssetReferenceValidation::validate), validateReferencedEntity(assetEntity, "assignedToAsset", assignedToAssetReferenceValidation::validate),
validateReferencedEntity(assetEntity, "alarmContact", alarmContactValidation::validate), validateReferencedEntity(assetEntity, "alarmContact", alarmContactValidation::validate),
validateProperties(assetEntity)) validateProperties(assetEntity))
.filter(Objects::nonNull) .filter(Objects::nonNull)
.flatMap(List::stream) .flatMap(List::stream)
.filter(Objects::nonNull) .filter(Objects::nonNull)
@ -97,25 +96,28 @@ public abstract class HsHostingAssetEntityValidator extends HsEntityValidator<Hs
private static List<String> optionallyValidate(final HsHostingAssetEntity assetEntity) { private static List<String> optionallyValidate(final HsHostingAssetEntity assetEntity) {
return assetEntity != null return assetEntity != null
? enrich(prefix(assetEntity.toShortString(), "parentAsset"), ? enrich(
HsHostingAssetEntityValidatorRegistry.forType(assetEntity.getType()).validateContext(assetEntity)) prefix(assetEntity.toShortString(), "parentAsset"),
HsHostingAssetEntityValidatorRegistry.forType(assetEntity.getType()).validateContext(assetEntity))
: emptyList(); : emptyList();
} }
private static List<String> optionallyValidate(final HsBookingItemEntity bookingItem) { private static List<String> optionallyValidate(final HsBookingItemEntity bookingItem) {
return bookingItem != null return bookingItem != null
? enrich(prefix(bookingItem.toShortString(), "bookingItem"), ? enrich(
HsBookingItemEntityValidatorRegistry.forType(bookingItem.getType()).validateContext(bookingItem)) prefix(bookingItem.toShortString(), "bookingItem"),
HsBookingItemEntityValidatorRegistry.forType(bookingItem.getType()).validateContext(bookingItem))
: emptyList(); : emptyList();
} }
protected List<String> validateAgainstSubEntities(final HsHostingAssetEntity assetEntity) { protected List<String> validateAgainstSubEntities(final HsHostingAssetEntity assetEntity) {
return enrich(prefix(assetEntity.toShortString(), "config"), return enrich(
prefix(assetEntity.toShortString(), "config"),
stream(propertyValidators) stream(propertyValidators)
.filter(ValidatableProperty::isTotalsValidator) .filter(ValidatableProperty::isTotalsValidator)
.map(prop -> validateMaxTotalValue(assetEntity, prop)) .map(prop -> validateMaxTotalValue(assetEntity, prop))
.filter(Objects::nonNull) .filter(Objects::nonNull)
.toList()); .toList());
} }
// TODO.test: check, if there are any hosting assets which need this validation at all // 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<Hs
final var expectedIdentifierPattern = identifierPattern(assetEntity); final var expectedIdentifierPattern = identifierPattern(assetEntity);
if (assetEntity.getIdentifier() == null || if (assetEntity.getIdentifier() == null ||
!expectedIdentifierPattern.matcher(assetEntity.getIdentifier()).matches()) { !expectedIdentifierPattern.matcher(assetEntity.getIdentifier()).matches()) {
return List.of("'identifier' expected to match '"+expectedIdentifierPattern+"', but is '" + assetEntity.getIdentifier() + "'"); return List.of(
"'identifier' expected to match '" + expectedIdentifierPattern + "', but is '" + assetEntity.getIdentifier()
+ "'");
} }
return Collections.emptyList(); return Collections.emptyList();
} }
@ -150,42 +154,54 @@ public abstract class HsHostingAssetEntityValidator extends HsEntityValidator<Hs
static class ReferenceValidator<S, T> { static class ReferenceValidator<S, T> {
private final HsHostingAssetType.RelationPolicy policy; private final HsHostingAssetType.RelationPolicy policy;
private final T subEntityType; private final T referencedEntityType;
private final Function<HsHostingAssetEntity, S> subEntityGetter; private final Function<HsHostingAssetEntity, S> referencedEntityGetter;
private final Function<S,T> subEntityTypeGetter; private final Function<S, T> referencedEntityTypeGetter;
public ReferenceValidator( public ReferenceValidator(
final HsHostingAssetType.RelationPolicy policy, final HsHostingAssetType.RelationPolicy policy,
final T subEntityType, final T subEntityType,
final Function<HsHostingAssetEntity, S> subEntityGetter, final Function<HsHostingAssetEntity, S> referencedEntityGetter,
final Function<S, T> subEntityTypeGetter) { final Function<S, T> referencedEntityTypeGetter) {
this.policy = policy; this.policy = policy;
this.subEntityType = subEntityType; this.referencedEntityType = subEntityType;
this.subEntityGetter = subEntityGetter; this.referencedEntityGetter = referencedEntityGetter;
this.subEntityTypeGetter = subEntityTypeGetter; this.referencedEntityTypeGetter = referencedEntityTypeGetter;
} }
public ReferenceValidator( public ReferenceValidator(
final HsHostingAssetType.RelationPolicy policy, final HsHostingAssetType.RelationPolicy policy,
final Function<HsHostingAssetEntity, S> subEntityGetter) { final Function<HsHostingAssetEntity, S> referencedEntityGetter) {
this.policy = policy; this.policy = policy;
this.subEntityType = null; this.referencedEntityType = null;
this.subEntityGetter = subEntityGetter; this.referencedEntityGetter = referencedEntityGetter;
this.subEntityTypeGetter = e -> null; this.referencedEntityTypeGetter = e -> null;
} }
List<String> validate(final HsHostingAssetEntity assetEntity, final String referenceFieldName) { List<String> validate(final HsHostingAssetEntity assetEntity, final String referenceFieldName) {
final var subEntity = subEntityGetter.apply(assetEntity); final var actualEntity = referencedEntityGetter.apply(assetEntity);
if (policy == REQUIRED && subEntity == null) { final var actualEntityType = actualEntity != null ? referencedEntityTypeGetter.apply(actualEntity) : null;
return List.of(referenceFieldName + "' must not be null but is null");
} switch (policy) {
if ((policy == FORBIDDEN) && (subEntity != null)) { case REQUIRED:
return List.of(referenceFieldName + "' must be null but is set to "+ assetEntity.getBookingItem().toShortString()); if (actualEntityType != referencedEntityType) {
} return List.of(actualEntityType == null
final var subItemType = subEntity != null ? subEntityTypeGetter.apply(subEntity) : null; ? referenceFieldName + "' must be of type " + referencedEntityType + " but is null"
if (policy != OPTIONAL && subEntityType != null && subItemType != subEntityType) { : referenceFieldName + "' must be of type " + referencedEntityType + " but is of type " + actualEntityType);
return List.of(referenceFieldName + "' must be of type " + subEntityType + " but is of type " + subItemType); }
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(); return emptyList();
} }

View File

@ -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( DOMAIN_DNS_SETUP(
List.of( List.of(
HsHostingAssetEntity.builder() HsHostingAssetEntity.builder()

View File

@ -33,7 +33,7 @@ class HsCloudServerHostingAssetValidatorUnitTest {
// then // then
assertThat(result).containsExactlyInAnyOrder( 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'"); "'CLOUD_SERVER:vm1234.config.RAM' is not expected but is set to '2000'");
} }
@ -84,14 +84,14 @@ class HsCloudServerHostingAssetValidatorUnitTest {
} }
@Test @Test
void validatesParentAndAssignedToAssetMustNotBeSet() { void rejectsInvalidReferencedEntities() {
// given // given
final var mangedServerHostingAssetEntity = HsHostingAssetEntity.builder() final var mangedServerHostingAssetEntity = HsHostingAssetEntity.builder()
.type(CLOUD_SERVER) .type(CLOUD_SERVER)
.identifier("xyz00") .identifier("vm1234")
.parentAsset(HsHostingAssetEntity.builder().build())
.assignedToAsset(HsHostingAssetEntity.builder().build())
.bookingItem(HsBookingItemEntity.builder().type(HsBookingItemType.CLOUD_SERVER).build()) .bookingItem(HsBookingItemEntity.builder().type(HsBookingItemType.CLOUD_SERVER).build())
.parentAsset(HsHostingAssetEntity.builder().type(MANAGED_SERVER).build())
.assignedToAsset(HsHostingAssetEntity.builder().type(CLOUD_SERVER).build())
.build(); .build();
final var validator = HsHostingAssetEntityValidatorRegistry.forType(mangedServerHostingAssetEntity.getType()); final var validator = HsHostingAssetEntityValidatorRegistry.forType(mangedServerHostingAssetEntity.getType());
@ -100,7 +100,7 @@ class HsCloudServerHostingAssetValidatorUnitTest {
// then // then
assertThat(result).containsExactlyInAnyOrder( assertThat(result).containsExactlyInAnyOrder(
"'CLOUD_SERVER:xyz00.parentAsset' must be null but is set to D-???????-?:null", "'CLOUD_SERVER:vm1234.parentAsset' must be null but is of type MANAGED_SERVER",
"'CLOUD_SERVER:xyz00.assignedToAsset' must be null but is set to D-???????-?:null"); "'CLOUD_SERVER:vm1234.assignedToAsset' must be null but is of type CLOUD_SERVER");
} }
} }

View File

@ -95,7 +95,7 @@ class HsDomainDnsSetupHostingAssetValidatorUnitTest {
// then // then
assertThat(result).containsExactly( 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 @Test
void validatesReferencedEntities() { void rejectsInvalidReferencedEntities() {
// given // given
final var mangedServerHostingAssetEntity = validEntityBuilder() final var mangedServerHostingAssetEntity = validEntityBuilder()
.parentAsset(HsHostingAssetEntity.builder().build())
.assignedToAsset(HsHostingAssetEntity.builder().build())
.bookingItem(HsBookingItemEntity.builder().type(HsBookingItemType.CLOUD_SERVER).build()) .bookingItem(HsBookingItemEntity.builder().type(HsBookingItemType.CLOUD_SERVER).build())
.parentAsset(null)
.assignedToAsset(HsHostingAssetEntity.builder().type(DOMAIN_SETUP).build())
.build(); .build();
final var validator = HsHostingAssetEntityValidatorRegistry.forType(mangedServerHostingAssetEntity.getType()); final var validator = HsHostingAssetEntityValidatorRegistry.forType(mangedServerHostingAssetEntity.getType());
@ -127,9 +127,9 @@ class HsDomainDnsSetupHostingAssetValidatorUnitTest {
// then // then
assertThat(result).containsExactlyInAnyOrder( 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.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 of type null", "'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 set to D-???????-?:null"); "'DOMAIN_DNS_SETUP:example.org|DNS.assignedToAsset' must be null but is of type DOMAIN_SETUP");
} }
@Test @Test

View File

@ -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}(?<!-)\\.)+[A-Za-z]{2,6}', but is '"+testCase.domainName+"'"
);
}
enum ValidDomainNameIdentifier {
SIMPLE("exampe.org"),
MAX_LENGTH("abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz01234568901.de"),
WITH_DASH("example-test.com"),
SUBDOMAIN("test.example.com");
final String domainName;
ValidDomainNameIdentifier(final String domainName) {
this.domainName = domainName;
}
}
@ParameterizedTest
@EnumSource(ValidDomainNameIdentifier.class)
void acceptsValidIdentifier(final ValidDomainNameIdentifier 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).isEmpty();
}
@Test
void containsNoProperties() {
// when
final var validator = HsHostingAssetEntityValidatorRegistry.forType(CLOUD_SERVER);
// then
assertThat(validator.properties()).map(Map::toString).isEmpty();
}
@Test
void validatesReferencedEntities() {
// given
final var mangedServerHostingAssetEntity = validEntityBuilder()
.parentAsset(HsHostingAssetEntity.builder().type(CLOUD_SERVER).build())
.assignedToAsset(HsHostingAssetEntity.builder().type(MANAGED_SERVER).build())
.bookingItem(HsBookingItemEntity.builder().type(HsBookingItemType.CLOUD_SERVER).build())
.build();
final var validator = HsHostingAssetEntityValidatorRegistry.forType(mangedServerHostingAssetEntity.getType());
// when
final var result = validator.validateEntity(mangedServerHostingAssetEntity);
// then
assertThat(result).containsExactlyInAnyOrder(
"'DOMAIN_SETUP:example.org.bookingItem' must be null but is of type CLOUD_SERVER",
"'DOMAIN_SETUP:example.org.parentAsset' must be null or of type DOMAIN_SETUP but is of type CLOUD_SERVER",
"'DOMAIN_SETUP:example.org.assignedToAsset' must be null but is of type MANAGED_SERVER");
}
}

View File

@ -107,8 +107,8 @@ class HsEMailAliasHostingAssetValidatorUnitTest {
// then // then
assertThat(result).containsExactlyInAnyOrder( assertThat(result).containsExactlyInAnyOrder(
"'EMAIL_ALIAS:abc00-office.bookingItem' must be null but is set to D-1234500:test project:test project booking item", "'EMAIL_ALIAS:abc00-office.bookingItem' must be null but is of type MANAGED_SERVER",
"'EMAIL_ALIAS:abc00-office.parentAsset' must be of type MANAGED_WEBSPACE but is of type MANAGED_SERVER", "'EMAIL_ALIAS:abc00-office.parentAsset' must be of type MANAGED_WEBSPACE but is of type MANAGED_SERVER",
"'EMAIL_ALIAS:abc00-office.assignedToAsset' must be null but is set to D-1234500:test project:test project booking item"); "'EMAIL_ALIAS:abc00-office.assignedToAsset' must be null but is of type MANAGED_SERVER");
} }
} }

View File

@ -10,6 +10,7 @@ import java.util.Map;
import static java.util.Map.entry; 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.booking.item.TestHsBookingItem.TEST_CLOUD_SERVER_BOOKING_ITEM;
import static net.hostsharing.hsadminng.hs.booking.item.TestHsBookingItem.TEST_MANAGED_SERVER_BOOKING_ITEM; import static net.hostsharing.hsadminng.hs.booking.item.TestHsBookingItem.TEST_MANAGED_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 net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_SERVER;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -22,8 +23,8 @@ class HsManagedServerHostingAssetValidatorUnitTest {
.type(MANAGED_SERVER) .type(MANAGED_SERVER)
.identifier("vm1234") .identifier("vm1234")
.bookingItem(TEST_MANAGED_SERVER_BOOKING_ITEM) .bookingItem(TEST_MANAGED_SERVER_BOOKING_ITEM)
.parentAsset(HsHostingAssetEntity.builder().build()) .parentAsset(HsHostingAssetEntity.builder().type(CLOUD_SERVER).build())
.assignedToAsset(HsHostingAssetEntity.builder().build()) .assignedToAsset(HsHostingAssetEntity.builder().type(CLOUD_SERVER).build())
.config(Map.ofEntries( .config(Map.ofEntries(
entry("monit_max_hdd_usage", "90"), entry("monit_max_hdd_usage", "90"),
entry("monit_max_cpu_usage", 2), entry("monit_max_cpu_usage", 2),
@ -37,8 +38,8 @@ class HsManagedServerHostingAssetValidatorUnitTest {
// then // then
assertThat(result).containsExactlyInAnyOrder( assertThat(result).containsExactlyInAnyOrder(
"'MANAGED_SERVER:vm1234.parentAsset' must be null but is set to D-1234500:test project:test project booking item", "'MANAGED_SERVER:vm1234.parentAsset' must be null but is of type CLOUD_SERVER",
"'MANAGED_SERVER:vm1234.assignedToAsset' must be null but is set to D-1234500:test project:test project booking item", "'MANAGED_SERVER:vm1234.assignedToAsset' must be null but is of type CLOUD_SERVER",
"'MANAGED_SERVER:vm1234.config.monit_max_cpu_usage' is expected to be at least 10 but is 2", "'MANAGED_SERVER:vm1234.config.monit_max_cpu_usage' is expected to be at least 10 but is 2",
"'MANAGED_SERVER:vm1234.config.monit_max_ram_usage' is expected to be at most 100 but is 101", "'MANAGED_SERVER:vm1234.config.monit_max_ram_usage' is expected to be at most 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'"); "'MANAGED_SERVER:vm1234.config.monit_max_hdd_usage' is expected to be of type class java.lang.Integer, but is of type 'String'");
@ -63,14 +64,14 @@ class HsManagedServerHostingAssetValidatorUnitTest {
} }
@Test @Test
void validatesParentAndAssignedToAssetMustNotBeSet() { void rejectsInvalidReferencedEntities() {
// given // given
final var mangedServerHostingAssetEntity = HsHostingAssetEntity.builder() final var mangedServerHostingAssetEntity = HsHostingAssetEntity.builder()
.type(MANAGED_SERVER) .type(MANAGED_SERVER)
.identifier("xyz00") .identifier("xyz00")
.parentAsset(HsHostingAssetEntity.builder().build())
.assignedToAsset(HsHostingAssetEntity.builder().build())
.bookingItem(TEST_CLOUD_SERVER_BOOKING_ITEM) .bookingItem(TEST_CLOUD_SERVER_BOOKING_ITEM)
.parentAsset(HsHostingAssetEntity.builder().type(CLOUD_SERVER).build())
.assignedToAsset(HsHostingAssetEntity.builder().type(MANAGED_SERVER).build())
.build(); .build();
final var validator = HsHostingAssetEntityValidatorRegistry.forType(mangedServerHostingAssetEntity.getType()); final var validator = HsHostingAssetEntityValidatorRegistry.forType(mangedServerHostingAssetEntity.getType());
@ -80,7 +81,7 @@ class HsManagedServerHostingAssetValidatorUnitTest {
// then // then
assertThat(result).containsExactlyInAnyOrder( assertThat(result).containsExactlyInAnyOrder(
"'MANAGED_SERVER:xyz00.bookingItem' must be of type MANAGED_SERVER but is of type CLOUD_SERVER", "'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-1234500:test project:test cloud server booking item", "'MANAGED_SERVER:xyz00.parentAsset' must be null but is of type CLOUD_SERVER",
"'MANAGED_SERVER:xyz00.assignedToAsset' must be null but is set to D-1234500:test project:test cloud server booking item"); "'MANAGED_SERVER:xyz00.assignedToAsset' must be null but is of type MANAGED_SERVER");
} }
} }

View File

@ -10,6 +10,7 @@ import java.util.Map;
import java.util.stream.Stream; import java.util.stream.Stream;
import static java.util.Map.entry; 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_WEBSPACE; import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_WEBSPACE;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static net.hostsharing.hsadminng.hs.booking.project.TestHsBookingProject.TEST_PROJECT; import static net.hostsharing.hsadminng.hs.booking.project.TestHsBookingProject.TEST_PROJECT;
@ -142,7 +143,7 @@ class HsManagedWebspaceHostingAssetValidatorUnitTest {
} }
@Test @Test
void validatesEntityReferences() { void rejectsInvalidEntityReferences() {
// given // given
final var validator = HsHostingAssetEntityValidatorRegistry.forType(MANAGED_WEBSPACE); final var validator = HsHostingAssetEntityValidatorRegistry.forType(MANAGED_WEBSPACE);
final var mangedWebspaceHostingAssetEntity = HsHostingAssetEntity.builder() final var mangedWebspaceHostingAssetEntity = HsHostingAssetEntity.builder()
@ -153,7 +154,7 @@ class HsManagedWebspaceHostingAssetValidatorUnitTest {
.resources(Map.ofEntries(entry("SSD", 25), entry("Traffic", 250))) .resources(Map.ofEntries(entry("SSD", 25), entry("Traffic", 250)))
.build()) .build())
.parentAsset(cloudServerAssetEntity) .parentAsset(cloudServerAssetEntity)
.assignedToAsset(HsHostingAssetEntity.builder().build()) .assignedToAsset(HsHostingAssetEntity.builder().type(CLOUD_SERVER).build())
.identifier("abc00") .identifier("abc00")
.build(); .build();
@ -163,7 +164,7 @@ class HsManagedWebspaceHostingAssetValidatorUnitTest {
// then // then
assertThat(result).containsExactly( assertThat(result).containsExactly(
"'MANAGED_WEBSPACE:abc00.bookingItem' must be of type MANAGED_WEBSPACE but is of type MANAGED_SERVER", "'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.parentAsset' must be null or of type MANAGED_SERVER but is of type CLOUD_SERVER",
"'MANAGED_WEBSPACE:abc00.assignedToAsset' must be null but is set to D-???????-?:some ManagedServer"); "'MANAGED_WEBSPACE:abc00.assignedToAsset' must be null but is of type CLOUD_SERVER");
} }
} }

View File

@ -8,6 +8,7 @@ import net.hostsharing.hsadminng.rbac.rbacgrant.RbacGrantsDiagramService;
import net.hostsharing.hsadminng.rbac.rbacrole.RbacRoleEntity; import net.hostsharing.hsadminng.rbac.rbacrole.RbacRoleEntity;
import net.hostsharing.hsadminng.rbac.rbacrole.RbacRoleRepository; import net.hostsharing.hsadminng.rbac.rbacrole.RbacRoleRepository;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.TestInfo; import org.junit.jupiter.api.TestInfo;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -170,7 +171,7 @@ public abstract class ContextBasedTestWithCleanup extends ContextBasedTest {
this.testInfo = testInfo; this.testInfo = testInfo;
} }
//@AfterEach @AfterEach
void cleanupAndCheckCleanup(final TestInfo testInfo) { void cleanupAndCheckCleanup(final TestInfo testInfo) {
// If the whole test method has its own transaction, cleanup makes no sense. // If the whole test method has its own transaction, cleanup makes no sense.
// If that transaction even failed, cleaunup would cause an exception. // If that transaction even failed, cleaunup would cause an exception.