diff --git a/.aliases b/.aliases index 7b729c98..86762cdc 100644 --- a/.aliases +++ b/.aliases @@ -43,17 +43,24 @@ postgresAutodoc () { alias postgres-autodoc=postgresAutodoc function importLegacyData() { - set target=$1 - source .tc-environment - - if [ -f .environment ]; then - source .environment - fi + export target=$1 + if [ -z "$target" ]; then + echo "importLegacyData needs target argument, but none was given" >&2 + else + source .tc-environment - echo "using environment (with ending ';' for use in IntelliJ IDEA):" - set | grep ^HSADMINNG_ | sed 's/$/;/' + if [ -f .environment ]; then + source .environment + fi - ./gradlew $target --rerun + echo "using environment (with ending ';' for use in IntelliJ IDEA):" + echo "--- BEGIN: ---" + set | grep ^HSADMINNG_ | sed 's/$/;/' + echo "---- END. ----\n" + + echo ./gradlew $target --rerun + ./gradlew $target --rerun + fi } alias gw-importOfficeData='importLegacyData importOfficeData' alias gw-importHostingAssets='importLegacyData importHostingAssets' diff --git a/build.gradle b/build.gradle index 63f4a996..41ceaed8 100644 --- a/build.gradle +++ b/build.gradle @@ -318,7 +318,7 @@ jacocoTestCoverageVerification { tasks.register('importOfficeData', Test) { useJUnitPlatform { - includeTags 'import' + includeTags 'importOfficeData' } group 'verification' @@ -327,6 +327,16 @@ tasks.register('importOfficeData', Test) { mustRunAfter spotlessJava } +tasks.register('importHostingAssets', Test) { + useJUnitPlatform { + includeTags 'importHostingAssets' + } + + group 'verification' + description 'run the import jobs as tests' + + mustRunAfter spotlessJava +} // pitest mutation testing pitest { diff --git a/src/main/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsBookingItemEntityValidator.java b/src/main/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsBookingItemEntityValidator.java index 82a20e54..7b596ad5 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsBookingItemEntityValidator.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsBookingItemEntityValidator.java @@ -22,6 +22,10 @@ public class HsBookingItemEntityValidator extends HsEntityValidator validateEntity(final HsBookingItemEntity bookingItem) { + // TODO.impl: HsBookingItemType could do this similar to HsHostingAssetType + if ( bookingItem.getParentItem() == null && bookingItem.getProject() == null) { + return List.of(bookingItem + ".'parentItem' or .'project' expected to be set, but both are null"); + } return enrich(prefix(bookingItem.toShortString(), "resources"), super.validateProperties(bookingItem)); } diff --git a/src/main/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsCloudServerBookingItemValidator.java b/src/main/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsCloudServerBookingItemValidator.java index 618c0b7b..b11ba248 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsCloudServerBookingItemValidator.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsCloudServerBookingItemValidator.java @@ -13,10 +13,10 @@ class HsCloudServerBookingItemValidator extends HsBookingItemEntityValidator { integerProperty("CPU") .min( 1) .max( 32) .required(), integerProperty("RAM").unit("GB") .min( 1) .max( 8192) .required(), - integerProperty("SSD").unit("GB") .min( 0) .max( 1000) .step(25).required(), - integerProperty("HDD").unit("GB") .min( 0) .max( 4000) .step(250).withDefault(0), - integerProperty("Traffic").unit("GB") .min(250) .max(10000) .step(250).required(), - integerProperty("Bandwidth").unit("GB") .min(250) .max(10000) .step(250).optional(), // TODO.spec + integerProperty("SSD").unit("GB") .min( 0) .max( 1000) .step(25).requiresExactlyOneOf("SDD", "HDD").withDefault(0), + integerProperty("HDD").unit("GB") .min( 0) .max( 4000) .step(250).requiresExactlyOneOf("SSD", "HDD").withDefault(0), + integerProperty("Traffic").unit("GB") .min(250) .max(10000) .step(250).requiresAtMaxOneOf("Bandwidth", "Traffic"), + integerProperty("Bandwidth").unit("GB") .min(250) .max(10000) .step(250).requiresAtMaxOneOf("Bandwidth", "Traffic"), // TODO.spec enumerationProperty("SLA-Infrastructure").values("BASIC", "EXT8H", "EXT4H", "EXT2H").optional() // @formatter:on diff --git a/src/main/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsManagedServerBookingItemValidator.java b/src/main/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsManagedServerBookingItemValidator.java index 1f701e20..db453252 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsManagedServerBookingItemValidator.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsManagedServerBookingItemValidator.java @@ -12,10 +12,10 @@ class HsManagedServerBookingItemValidator extends HsBookingItemEntityValidator { super( integerProperty("CPU").min(1).max(32).required(), integerProperty("RAM").unit("GB").min(1).max(128).required(), - integerProperty("SSD").unit("GB").min(25).max(2000).step(25).required().asTotalLimit().withThreshold(200), - integerProperty("HDD").unit("GB").min(0).max(10000).step(250).withDefault(0).asTotalLimit().withThreshold(200), - integerProperty("Traffic").unit("GB").min(250).max(64000).step(250).required().asTotalLimit().withThreshold(200), - integerProperty("Bandwidth").unit("GB").min(250).max(64000).step(250).optional().asTotalLimit().withThreshold(200), // TODO.spec + integerProperty("SSD").unit("GB").min(25).max(2000).step(25).requiresExactlyOneOf("SSD", "HDD").withDefault(0).asTotalLimit().withThreshold(200), + integerProperty("HDD").unit("GB").min(0).max(10000).step(250).requiresExactlyOneOf("SSD", "HDD").withDefault(0).asTotalLimit().withThreshold(200), + integerProperty("Traffic").unit("GB").min(250).max(64000).step(250).requiresAtMaxOneOf("Bandwidth", "Traffic").asTotalLimit().withThreshold(200), + integerProperty("Bandwidth").unit("GB").min(250).max(64000).step(250).requiresAtMaxOneOf("Bandwidth", "Traffic").asTotalLimit().withThreshold(200), // TODO.spec enumerationProperty("SLA-Platform").values("BASIC", "EXT8H", "EXT4H", "EXT2H").withDefault("BASIC"), booleanProperty("SLA-EMail").falseIf("SLA-Platform", "BASIC").withDefault(false), booleanProperty("SLA-Maria").falseIf("SLA-Platform", "BASIC").optional(), diff --git a/src/main/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsManagedWebspaceBookingItemValidator.java b/src/main/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsManagedWebspaceBookingItemValidator.java index 45538d95..ffa2b525 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsManagedWebspaceBookingItemValidator.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsManagedWebspaceBookingItemValidator.java @@ -25,8 +25,8 @@ class HsManagedWebspaceBookingItemValidator extends HsBookingItemEntityValidator super( integerProperty("SSD").unit("GB").min(1).max(2000).step(1).required(), integerProperty("HDD").unit("GB").min(0).max(10000).step(10).optional(), - integerProperty("Traffic").unit("GB").min(10).max(64000).step(10).required(), - integerProperty("Bandwidth").unit("GB").min(10).max(1000).step(10).optional(), // TODO.spec + integerProperty("Traffic").unit("GB").min(10).max(64000).step(10).requiresAtMaxOneOf("Bandwidth", "Traffic"), + integerProperty("Bandwidth").unit("GB").min(10).max(1000).step(10).requiresAtMaxOneOf("Bandwidth", "Traffic"), // TODO.spec integerProperty("Multi").min(1).max(100).step(1).withDefault(1) .eachComprising( 25, unixUsers()) .eachComprising( 5, databaseUsers()) diff --git a/src/main/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsPrivateCloudBookingItemValidator.java b/src/main/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsPrivateCloudBookingItemValidator.java index 95aebb84..18fdedd0 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsPrivateCloudBookingItemValidator.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsPrivateCloudBookingItemValidator.java @@ -9,10 +9,10 @@ class HsPrivateCloudBookingItemValidator extends HsBookingItemEntityValidator { // @formatter:off integerProperty("CPU") .min( 1).max( 128).required().asTotalLimit(), integerProperty("RAM").unit("GB") .min( 1).max( 512).required().asTotalLimit(), - integerProperty("SSD").unit("GB") .min( 25).max( 4000).step(25).required().asTotalLimit(), - integerProperty("HDD").unit("GB") .min( 0).max(16000).step(250).withDefault(0).asTotalLimit(), - integerProperty("Traffic").unit("GB") .min(250).max(64000).step(250).required().asTotalLimit(), - integerProperty("Bandwidth").unit("GB") .min(250).max(64000).step(250).optional().asTotalLimit(), // TODO.spec + integerProperty("SSD").unit("GB") .min( 25).max( 4000).step(25).requiresExactlyOneOf("SSD", "HDD").withDefault(0).asTotalLimit(), + integerProperty("HDD").unit("GB") .min( 0).max(16000).step(250).requiresExactlyOneOf("SSD", "HDD").withDefault(0).asTotalLimit(), + integerProperty("Traffic").unit("GB") .min(250).max(64000).step(250).requiresAtMaxOneOf("Bandwidth", "Traffic").asTotalLimit(), + integerProperty("Bandwidth").unit("GB") .min(250).max(64000).step(250).requiresAtMaxOneOf("Bandwidth", "Traffic").asTotalLimit(), // TODO.spec // Alternatively we could specify it similarly to "Multi" option but exclusively counting: // integerProperty("Resource-Points") .min(4).max(100).required() diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipEntity.java b/src/main/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipEntity.java index 67050ccc..f71408a7 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipEntity.java @@ -68,7 +68,7 @@ public class HsOfficeMembershipEntity implements RbacObject, Stringifyable { private static Stringify stringify = stringify(HsOfficeMembershipEntity.class) .withProp(e -> MEMBER_NUMBER_TAG + e.getMemberNumber()) - .withProp(e -> e.getPartner().toShortString()) + .withProp(HsOfficeMembershipEntity::getPartner) .withProp(e -> e.getValidity().asString()) .withProp(HsOfficeMembershipEntity::getStatus) .quotedValues(false); diff --git a/src/main/java/net/hostsharing/hsadminng/hs/validation/ValidatableProperty.java b/src/main/java/net/hostsharing/hsadminng/hs/validation/ValidatableProperty.java index 01daf6aa..b6bb5ef2 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/validation/ValidatableProperty.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/validation/ValidatableProperty.java @@ -13,10 +13,12 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.Set; import java.util.function.Function; import static java.lang.Boolean.FALSE; @@ -30,7 +32,7 @@ import static org.apache.commons.lang3.ObjectUtils.isArray; public abstract class ValidatableProperty

, T> { protected static final String[] KEY_ORDER_HEAD = Array.of("propertyName"); - protected static final String[] KEY_ORDER_TAIL = Array.of("required", "defaultValue", "readOnly", "writeOnly", "computed", "isTotalsValidator", "thresholdPercentage"); + protected static final String[] KEY_ORDER_TAIL = Array.of("required", "requiresExactlyOneOf", "requiresAtMaxOneOf", "defaultValue", "readOnly", "writeOnly", "computed", "isTotalsValidator", "thresholdPercentage"); protected static final String[] KEY_ORDER = Array.join(KEY_ORDER_HEAD, KEY_ORDER_TAIL); final Class type; @@ -40,6 +42,8 @@ public abstract class ValidatableProperty

, T private final String[] keyOrder; private Boolean required; + private Set requiresExactlyOneOf; + private Set requiresAtMaxOneOf; private T defaultValue; @JsonIgnore @@ -100,9 +104,19 @@ protected void setDeferredInit(final Function[], T[]> return self(); } - public ValidatableProperty optional() { + public P optional() { required = FALSE; - return this; + return self(); + } + + public P requiresExactlyOneOf(final String... propNames) { + requiresExactlyOneOf = new LinkedHashSet<>(List.of(propNames)); + return self(); + } + + public P requiresAtMaxOneOf(final String... propNames) { + requiresAtMaxOneOf = new LinkedHashSet<>(List.of(propNames)); + return self(); } public P withDefault(final T value) { @@ -172,28 +186,59 @@ protected void setDeferredInit(final Function[], T[]> final var result = new ArrayList(); final var props = propsProvider.directProps(); final var propValue = props.get(propertyName); + if (propValue == null) { - if (required) { + if (required == TRUE) { result.add(propertyName + "' is required but missing"); } + validateRequiresExactlyOneOf(result, propsProvider); } if (propValue != null){ + validateRequiresAtMaxOneOf(result, propsProvider); + if ( type.isInstance(propValue)) { //noinspection unchecked validate(result, (T) propValue, propsProvider); } else { result.add(propertyName + "' is expected to be of type " + type.getSimpleName() + ", " + - "but is of type " + propValue.getClass().getSimpleName() + ""); + "but is of type " + propValue.getClass().getSimpleName()); } } return result; } + private void validateRequiresExactlyOneOf(final ArrayList result, final PropertiesProvider propsProvider) { + if (requiresExactlyOneOf != null ) { + final var allPropNames = propsProvider.directProps().keySet(); + final var entriesWithValue = allPropNames.stream() + .filter(name -> requiresExactlyOneOf.contains(name)) + .count(); + if (entriesWithValue == 0) { + result.add(propertyName + "' is required once in group " + requiresExactlyOneOf + " but missing"); + } else if (entriesWithValue > 1) { + result.add(propertyName + "' is required once in group " + requiresExactlyOneOf + " but multiple properties are set"); + } + } + } + + private void validateRequiresAtMaxOneOf(final ArrayList result, final PropertiesProvider propsProvider) { + if (requiresAtMaxOneOf != null) { + final var allPropNames = propsProvider.directProps().keySet(); + final var entriesWithValue = allPropNames.stream() + .filter(name -> requiresAtMaxOneOf.contains(name)) + .count(); + if (entriesWithValue > 1) { + result.add(propertyName + "' is required at max once in group " + requiresAtMaxOneOf + + " but multiple properties are set"); + } + } + } + protected abstract void validate(final List result, final T propValue, final PropertiesProvider propProvider); public void verifyConsistency(final Map.Entry, ?> typeDef) { - if (required == null ) { - throw new IllegalStateException(typeDef.getKey() + "[" + propertyName + "] not fully initialized, please call either .required() or .optional()" ); + if (required == null && requiresExactlyOneOf == null && requiresAtMaxOneOf == null) { + throw new IllegalStateException(typeDef.getKey() + "[" + propertyName + "] not fully initialized, please call either .required(), .optional(), .withDefault(...), .requiresExactlyOneOf(...) or .requiresAtMaxOneOf(...)" ); } } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemEntityUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemEntityUnitTest.java index 8b97edd3..627eabc2 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemEntityUnitTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemEntityUnitTest.java @@ -53,7 +53,7 @@ class HsBookingItemEntityUnitTest { void toStringContainsAllPropertiesAndResourcesSortedByKey() { final var result = givenBookingItem.toString(); - assertThat(result).isEqualToIgnoringWhitespace("HsBookingItemEntity(D-1234500:test project, CLOUD_SERVER, [2020-01-01,2031-01-01), some caption, { \"CPUs\": 2, \"HDD-storage\": 2048, \"SSD-storage\": 512 })"); + assertThat(result).isEqualToIgnoringWhitespace("HsBookingItemEntity(D-1234500:test project, CLOUD_SERVER, [2020-01-01,2031-01-01), some caption, { \"CPU\": 2, \"HDD-storage\": 2048, \"SSD-storage\": 512 })"); } @Test diff --git a/src/test/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemRepositoryIntegrationTest.java b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemRepositoryIntegrationTest.java index f42ca6e2..eedfe603 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemRepositoryIntegrationTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemRepositoryIntegrationTest.java @@ -171,8 +171,8 @@ class HsBookingItemRepositoryIntegrationTest extends ContextBasedTestWithCleanup allTheseBookingItemsAreReturned( result, "HsBookingItemEntity(D-1000212:D-1000212 default project, MANAGED_WEBSPACE, [2022-10-01,), separate ManagedWebspace, { Daemons: 0, Multi: 1, SSD: 100, Traffic: 50 } )", - "HsBookingItemEntity(D-1000212:D-1000212 default project, MANAGED_SERVER, [2022-10-01,), separate ManagedServer, { CPUs: 2, RAM: 8, SSD: 500, Traffic: 500 } )", - "HsBookingItemEntity(D-1000212:D-1000212 default project, PRIVATE_CLOUD, [2024-04-01,), some PrivateCloud, { CPUs: 10, HDD: 10000, RAM: 32, SSD: 4000, Traffic: 2000 } )"); + "HsBookingItemEntity(D-1000212:D-1000212 default project, MANAGED_SERVER, [2022-10-01,), separate ManagedServer, { CPU: 2, RAM: 8, SSD: 500, Traffic: 500 } )", + "HsBookingItemEntity(D-1000212:D-1000212 default project, PRIVATE_CLOUD, [2024-04-01,), some PrivateCloud, { CPU: 10, HDD: 10000, RAM: 32, SSD: 4000, Traffic: 2000 } )"); assertThat(result.stream().filter(bi -> bi.getRelatedHostingAsset()!=null).findAny()) .as("at least one relatedProject expected, but none found => fetching relatedProject does not work") .isNotEmpty(); @@ -194,8 +194,8 @@ class HsBookingItemRepositoryIntegrationTest extends ContextBasedTestWithCleanup exactlyTheseBookingItemsAreReturned( result, "HsBookingItemEntity(D-1000111:D-1000111 default project, MANAGED_WEBSPACE, [2022-10-01,), separate ManagedWebspace, { Daemons: 0, Multi: 1, SSD: 100, Traffic: 50 } )", - "HsBookingItemEntity(D-1000111:D-1000111 default project, MANAGED_SERVER, [2022-10-01,), separate ManagedServer, { CPUs: 2, RAM: 8, SSD: 500, Traffic: 500 } )", - "HsBookingItemEntity(D-1000111:D-1000111 default project, PRIVATE_CLOUD, [2024-04-01,), some PrivateCloud, { CPUs: 10, HDD: 10000, RAM: 32, SSD: 4000, Traffic: 2000 } )"); + "HsBookingItemEntity(D-1000111:D-1000111 default project, MANAGED_SERVER, [2022-10-01,), separate ManagedServer, { CPU: 2, RAM: 8, SSD: 500, Traffic: 500 } )", + "HsBookingItemEntity(D-1000111:D-1000111 default project, PRIVATE_CLOUD, [2024-04-01,), some PrivateCloud, { CPU: 10, HDD: 10000, RAM: 32, SSD: 4000, Traffic: 2000 } )"); } } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsBookingItemEntityValidatorUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsBookingItemEntityValidatorUnitTest.java index e784edec..c8383dc9 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsBookingItemEntityValidatorUnitTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsBookingItemEntityValidatorUnitTest.java @@ -38,7 +38,7 @@ class HsBookingItemEntityValidatorUnitTest { // then assertThat(result).isInstanceOf(ValidationException.class) .hasMessageContaining( - "'D-12345:test project:Test-Server.resources.CPUs' is required but missing", + "'D-12345:test project:Test-Server.resources.CPU' is required but missing", "'D-12345:test project:Test-Server.resources.RAM' is required but missing", "'D-12345:test project:Test-Server.resources.SSD' is required but missing", "'D-12345:test project:Test-Server.resources.Traffic' is required but missing"); diff --git a/src/test/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsCloudServerBookingItemValidatorUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsCloudServerBookingItemValidatorUnitTest.java index 441244f0..2490b002 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsCloudServerBookingItemValidatorUnitTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsCloudServerBookingItemValidatorUnitTest.java @@ -56,11 +56,12 @@ class HsCloudServerBookingItemValidatorUnitTest { // then assertThat(validator.properties()).map(Map::toString).containsExactlyInAnyOrder( "{type=boolean, propertyName=active, defaultValue=true}", - "{type=integer, propertyName=CPUs, min=1, max=32, required=true}", - "{type=integer, propertyName=RAM, unit=GB, min=1, max=128, required=true}", - "{type=integer, propertyName=SSD, unit=GB, min=0, max=1000, step=25, required=true}", - "{type=integer, propertyName=HDD, unit=GB, min=0, max=4000, step=250, defaultValue=0}", - "{type=integer, propertyName=Traffic, unit=GB, min=250, max=10000, step=250, required=true}", + "{type=integer, propertyName=CPU, min=1, max=32, required=true}", + "{type=integer, propertyName=RAM, unit=GB, min=1, max=8192, required=true}", + "{type=integer, propertyName=SSD, unit=GB, min=0, max=1000, step=25, requiresExactlyOneOf=[SDD, HDD], defaultValue=0}", + "{type=integer, propertyName=HDD, unit=GB, min=0, max=4000, step=250, requiresExactlyOneOf=[SSD, HDD], defaultValue=0}", + "{type=integer, propertyName=Traffic, unit=GB, min=250, max=10000, step=250, requiresAtMaxOneOf=[Bandwidth, Traffic]}", + "{type=integer, propertyName=Bandwidth, unit=GB, min=250, max=10000, step=250, requiresAtMaxOneOf=[Bandwidth, Traffic]}", "{type=enumeration, propertyName=SLA-Infrastructure, values=[BASIC, EXT8H, EXT4H, EXT2H]}"); } @@ -110,7 +111,7 @@ class HsCloudServerBookingItemValidatorUnitTest { // then assertThat(result).containsExactlyInAnyOrder( - "'D-12345:Test-Project:Test Cloud.resources.CPUs' maximum total is 4, but actual total CPUs is 5", + "'D-12345:Test-Project:Test Cloud.resources.CPU' maximum total is 4, but actual total CPU is 5", "'D-12345:Test-Project:Test Cloud.resources.RAM' maximum total is 20 GB, but actual total RAM is 30 GB", "'D-12345:Test-Project:Test Cloud.resources.SSD' maximum total is 100 GB, but actual total SSD is 150 GB", "'D-12345:Test-Project:Test Cloud.resources.Traffic' maximum total is 5000 GB, but actual total Traffic is 5500 GB" diff --git a/src/test/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsManagedServerBookingItemValidatorUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsManagedServerBookingItemValidatorUnitTest.java index 6a4cadf6..933f45fb 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsManagedServerBookingItemValidatorUnitTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsManagedServerBookingItemValidatorUnitTest.java @@ -63,11 +63,12 @@ class HsManagedServerBookingItemValidatorUnitTest { // then assertThat(validator.properties()).map(Map::toString).containsExactlyInAnyOrder( - "{type=integer, propertyName=CPUs, min=1, max=32, required=true}", + "{type=integer, propertyName=CPU, min=1, max=32, required=true}", "{type=integer, propertyName=RAM, unit=GB, min=1, max=128, required=true}", - "{type=integer, propertyName=SSD, unit=GB, min=25, max=1000, step=25, required=true, isTotalsValidator=true, thresholdPercentage=200}", - "{type=integer, propertyName=HDD, unit=GB, min=0, max=4000, step=250, defaultValue=0, isTotalsValidator=true, thresholdPercentage=200}", - "{type=integer, propertyName=Traffic, unit=GB, min=250, max=10000, step=250, required=true, isTotalsValidator=true, thresholdPercentage=200}", + "{type=integer, propertyName=SSD, unit=GB, min=25, max=2000, step=25, requiresExactlyOneOf=[SSD, HDD], defaultValue=0, isTotalsValidator=true, thresholdPercentage=200}", + "{type=integer, propertyName=HDD, unit=GB, min=0, max=10000, step=250, requiresExactlyOneOf=[SSD, HDD], defaultValue=0, isTotalsValidator=true, thresholdPercentage=200}", + "{type=integer, propertyName=Traffic, unit=GB, min=250, max=64000, step=250, requiresAtMaxOneOf=[Bandwidth, Traffic], isTotalsValidator=true, thresholdPercentage=200}", + "{type=integer, propertyName=Bandwidth, unit=GB, min=250, max=64000, step=250, requiresAtMaxOneOf=[Bandwidth, Traffic], isTotalsValidator=true, thresholdPercentage=200}", "{type=enumeration, propertyName=SLA-Platform, values=[BASIC, EXT8H, EXT4H, EXT2H], defaultValue=BASIC}", "{type=boolean, propertyName=SLA-EMail}", // TODO.impl: falseIf-validation is missing in output "{type=boolean, propertyName=SLA-Maria}", @@ -120,7 +121,7 @@ class HsManagedServerBookingItemValidatorUnitTest { // then assertThat(result).containsExactlyInAnyOrder( - "'D-12345:Test-Project:null.resources.CPUs' maximum total is 4, but actual total CPUs is 5", + "'D-12345:Test-Project:null.resources.CPU' maximum total is 4, but actual total CPU is 5", "'D-12345:Test-Project:null.resources.RAM' maximum total is 20 GB, but actual total RAM is 30 GB", "'D-12345:Test-Project:null.resources.SSD' maximum total is 100 GB, but actual total SSD is 150 GB", "'D-12345:Test-Project:null.resources.Traffic' maximum total is 5000 GB, but actual total Traffic is 5500 GB" diff --git a/src/test/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsManagedWebspaceBookingItemValidatorUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsManagedWebspaceBookingItemValidatorUnitTest.java index 8cfbf681..4e7dc561 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsManagedWebspaceBookingItemValidatorUnitTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsManagedWebspaceBookingItemValidatorUnitTest.java @@ -41,7 +41,7 @@ class HsManagedWebspaceBookingItemValidatorUnitTest { // then assertThat(result).containsExactlyInAnyOrder( - "'D-12345:Test-Project:Test Managed-Webspace.resources.CPUs' is not expected but is set to '2'", + "'D-12345:Test-Project:Test Managed-Webspace.resources.CPU' is not expected but is set to '2'", "'D-12345:Test-Project:Test Managed-Webspace.resources.RAM' is not expected but is set to '25'", "'D-12345:Test-Project:Test Managed-Webspace.resources.SSD' is required but missing", "'D-12345:Test-Project:Test Managed-Webspace.resources.SLA-EMail' is not expected but is set to 'true'" @@ -55,11 +55,12 @@ class HsManagedWebspaceBookingItemValidatorUnitTest { // then assertThat(validator.properties()).map(Map::toString).containsExactlyInAnyOrder( - "{type=integer, propertyName=SSD, unit=GB, min=1, max=100, step=1, required=true}", - "{type=integer, propertyName=HDD, unit=GB, min=0, max=250, step=10}", - "{type=integer, propertyName=Traffic, unit=GB, min=10, max=1000, step=10, required=true}", + "{type=integer, propertyName=SSD, unit=GB, min=1, max=2000, step=1, required=true}", + "{type=integer, propertyName=HDD, unit=GB, min=0, max=10000, step=10}", + "{type=integer, propertyName=Traffic, unit=GB, min=10, max=64000, step=10, requiresAtMaxOneOf=[Bandwidth, Traffic]}", + "{type=integer, propertyName=Bandwidth, unit=GB, min=10, max=1000, step=10, requiresAtMaxOneOf=[Bandwidth, Traffic]}", "{type=integer, propertyName=Multi, min=1, max=100, step=1, defaultValue=1}", - "{type=integer, propertyName=Daemons, min=0, max=10, defaultValue=0}", + "{type=integer, propertyName=Daemons, min=0, max=16, defaultValue=0}", "{type=boolean, propertyName=Online Office Server}", "{type=enumeration, propertyName=SLA-Platform, values=[BASIC, EXT24H], defaultValue=BASIC}"); } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsPrivateCloudBookingItemValidatorUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsPrivateCloudBookingItemValidatorUnitTest.java index 93ab8e4c..95bfcd2b 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsPrivateCloudBookingItemValidatorUnitTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsPrivateCloudBookingItemValidatorUnitTest.java @@ -124,7 +124,7 @@ class HsPrivateCloudBookingItemValidatorUnitTest { // then assertThat(result).containsExactlyInAnyOrder( - "'D-12345:Test-Project:myPC.resources.CPUs' maximum total is 4, but actual total CPUs is 5", + "'D-12345:Test-Project:myPC.resources.CPU' maximum total is 4, but actual total CPU is 5", "'D-12345:Test-Project:myPC.resources.RAM' maximum total is 20 GB, but actual total RAM is 30 GB", "'D-12345:Test-Project:myPC.resources.SSD' maximum total is 100 GB, but actual total SSD is 150 GB", "'D-12345:Test-Project:myPC.resources.Traffic' maximum total is 5000 GB, but actual total Traffic is 5500 GB", diff --git a/src/test/java/net/hostsharing/hsadminng/hs/migration/CsvDataImport.java b/src/test/java/net/hostsharing/hsadminng/hs/migration/CsvDataImport.java index 084be3ca..c818d8b4 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/migration/CsvDataImport.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/migration/CsvDataImport.java @@ -207,6 +207,14 @@ public class CsvDataImport extends ContextBasedTest { em.createNativeQuery("delete from tx_context where true").executeUpdate(); }).assertSuccessful(); } + + void logError(final Runnable assertion) { + try { + assertion.run(); + } catch (final AssertionError exc) { + System.err.println(exc); + } + } } class Columns { diff --git a/src/test/java/net/hostsharing/hsadminng/hs/migration/ImportHostingAssets.java b/src/test/java/net/hostsharing/hsadminng/hs/migration/ImportHostingAssets.java index 074a33fb..49ca2221 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/migration/ImportHostingAssets.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/migration/ImportHostingAssets.java @@ -75,7 +75,7 @@ import static org.assertj.core.api.Assumptions.assumeThat; * * gw-importHostingAssets # comes from .aliases file and uses .environment */ -@Tag("import") +@Tag("importHostingAssets") @DataJpaTest(properties = { "spring.datasource.url=${HSADMINNG_POSTGRES_JDBC_URL:jdbc:tc:postgresql:15.5-bookworm:///spring_boot_testcontainers}", "spring.datasource.username=${HSADMINNG_POSTGRES_ADMIN_USERNAME:ADMIN}", @@ -251,7 +251,7 @@ public class ImportHostingAssets extends ImportOfficeData { 3000630=HsBookingItemEntity(D-1000000:hsh default project, MANAGED_WEBSPACE, [2001-06-01,), BI hsh00, { "HDD": 10, "Multi": 25, "SLA-Platform": "EXT24H", "SSD": 16, "Traffic": 50}), 3000968=HsBookingItemEntity(D-1015200:rar default project, MANAGED_SERVER, [2013-04-01,), BI vm1061, { "CPU": 6, "HDD": 250, "RAM": 14, "SLA-EMail": true, "SLA-Maria": true, "SLA-Office": true, "SLA-PgSQL": true, "SLA-Platform": "EXT4H", "SLA-Web": true, "SSD": 375, "Traffic": 250}), 3000978=HsBookingItemEntity(D-1000000:hsh default project, MANAGED_SERVER, [2013-04-01,), BI vm1050, { "CPU": 4, "HDD": 250, "RAM": 32, "SLA-EMail": true, "SLA-Maria": true, "SLA-Office": true, "SLA-PgSQL": true, "SLA-Platform": "EXT4H", "SLA-Web": true, "SSD": 150, "Traffic": 250}), - 3001061=HsBookingItemEntity(D-1000300:mim default project, MANAGED_SERVER, [2013-08-19,), BI vm1068, { "CPU": 2, "RAM": 4, "SLA-EMail": true, "SLA-Maria": true, "SLA-Office": true, "SLA-PgSQL": true, "SLA-Platform": "EXT2H", "SLA-Web": true, "SSD": 75, "Traffic": 250}), + 3001061=HsBookingItemEntity(D-1000300:mim default project, MANAGED_SERVER, [2013-08-19,), BI vm1068, { "CPU": 2, "HDD": 250, "RAM": 4, "SLA-EMail": true, "SLA-Maria": true, "SLA-Office": true, "SLA-PgSQL": true, "SLA-Platform": "EXT2H", "SLA-Web": true, "Traffic": 250}), 3001094=HsBookingItemEntity(D-1000300:mim default project, MANAGED_WEBSPACE, [2013-09-10,), BI lug00, { "Multi": 5, "SLA-Platform": "EXT24H", "SSD": 1, "Traffic": 10}), 3001112=HsBookingItemEntity(D-1000300:mim default project, MANAGED_WEBSPACE, [2013-09-17,), BI mim00, { "Multi": 5, "SLA-Platform": "EXT24H", "SSD": 3, "Traffic": 20}), 3001447=HsBookingItemEntity(D-1000000:hsh default project, MANAGED_SERVER, [2014-11-28,), BI vm1093, { "CPU": 6, "HDD": 500, "RAM": 16, "SLA-EMail": true, "SLA-Maria": true, "SLA-Office": true, "SLA-PgSQL": true, "SLA-Platform": "EXT4H", "SLA-Web": true, "SSD": 300, "Traffic": 250}), @@ -261,6 +261,32 @@ public class ImportHostingAssets extends ImportOfficeData { """); } + @Test + @Order(11400) + void validateBookingItems() { + bookingItems.forEach((id, bi) -> { + try { + HsBookingItemEntityValidatorRegistry.validated(bi); + } catch (final Exception exc) { + System.err.println("validation failed for id:" + id + "( " + bi + "): " + exc.getMessage()); + } + }); + } + + @Test + @Order(11410) + void validateHostingAssets() { + hostingAssets.forEach((id, ha) -> { + try { + new HostingAssetEntitySaveProcessor(ha) + .preprocessEntity() + .validateEntity(); + } catch (final Exception exc) { + System.err.println("validation failed for id:" + id + "( " + ha + "): " + exc.getMessage()); + } + }); + } + @Test @Order(19000) @Commit @@ -368,11 +394,11 @@ public class ImportHostingAssets extends ImportOfficeData { .validity(toPostgresDateRange(created, cancelled)) .build(); bookingItems.put(PACKET_ID_OFFSET + packet_id, bookingItem); - final var haType = determineHaType(basepacket_code); - assertThat(!free || haType == MANAGED_WEBSPACE || bookingItem.getRelatedProject().getDebitor().getDefaultPrefix().equals("hsh")) + + logError(() -> assertThat(!free || haType == MANAGED_WEBSPACE || bookingItem.getRelatedProject().getDebitor().getDefaultPrefix().equals("hsh")) .as("packet.free only supported for Hostsharing-Assets and ManagedWebspace in customer-ManagedServer, but is set for " + packet_name) - .isTrue(); + .isTrue()); final var asset = HsHostingAssetEntity.builder() .isLoaded(haType == MANAGED_WEBSPACE) // this turns off identifier validation to accept former default prefixes diff --git a/src/test/java/net/hostsharing/hsadminng/hs/migration/ImportOfficeData.java b/src/test/java/net/hostsharing/hsadminng/hs/migration/ImportOfficeData.java index 960fcbc9..c5507a6d 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/migration/ImportOfficeData.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/migration/ImportOfficeData.java @@ -37,6 +37,7 @@ import static java.util.Optional.ofNullable; import static net.hostsharing.hsadminng.mapper.PostgresDateRange.toPostgresDateRange; import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank; +import static org.assertj.core.api.Assertions.as; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assumptions.assumeThat; import static org.assertj.core.api.Fail.fail; @@ -77,7 +78,7 @@ import static org.assertj.core.api.Fail.fail; * * gw-importOfficeTables # comes from .aliases file and uses .environment */ -@Tag("import") +@Tag("importOfficeData") @DataJpaTest(properties = { "spring.datasource.url=${HSADMINNG_POSTGRES_JDBC_URL:jdbc:tc:postgresql:15.5-bookworm:///spring_boot_testcontainers}", "spring.datasource.username=${HSADMINNG_POSTGRES_ADMIN_USERNAME:ADMIN}", @@ -111,6 +112,7 @@ public class ImportOfficeData extends CsvDataImport { 512167, // 11139, partner without contractual contact 512170, // 11142, partner without contractual contact 511725, // 10764, partner without contractual contact + // 512171, // 11143, partner without partner contact -- exc -1 ); @@ -498,15 +500,16 @@ public class ImportOfficeData extends CsvDataImport { @Test @Order(2000) + // @Disabled // FIXME void verifyAllPartnersHavePersons() { partners.forEach((id, p) -> { final var partnerRel = p.getPartnerRel(); assertThat(partnerRel).describedAs("partner " + id + " without partnerRel").isNotNull(); if ( id != 199 ) { - assertThat(partnerRel.getContact()).describedAs("partner " + id + " without partnerRel.contact").isNotNull(); - assertThat(partnerRel.getContact().getCaption()).describedAs("partner " + id + " without valid partnerRel.contact").isNotNull(); - assertThat(partnerRel.getHolder()).describedAs("partner " + id + " without partnerRel.relHolder").isNotNull(); - assertThat(partnerRel.getHolder().getPersonType()).describedAs("partner " + id + " without valid partnerRel.relHolder").isNotNull(); + logError( () -> assertThat(partnerRel.getContact()).describedAs("partner " + id + " without partnerRel.contact").isNotNull()); + logError( () -> assertThat(partnerRel.getContact().getCaption()).describedAs("partner " + id + " without valid partnerRel.contact").isNotNull()); + logError( () -> assertThat(partnerRel.getHolder()).describedAs("partner " + id + " without partnerRel.relHolder").isNotNull()); + logError( () -> assertThat(partnerRel.getHolder().getPersonType()).describedAs("partner " + id + " without valid partnerRel.relHolder").isNotNull()); } }); } @@ -729,7 +732,8 @@ public class ImportOfficeData extends CsvDataImport { .map(this::trimAll) .map(row -> new Record(columns, row)) .forEach(rec -> { - if (IGNORE_BUSINESS_PARTNERS.contains(rec.getInteger("bp_id"))) { + final Integer bpId = rec.getInteger("bp_id"); + if (IGNORE_BUSINESS_PARTNERS.contains(bpId)) { return; } @@ -747,7 +751,7 @@ public class ImportOfficeData extends CsvDataImport { .details(HsOfficePartnerDetailsEntity.builder().build()) .partnerRel(partnerRel) .build(); - partners.put(rec.getInteger("bp_id"), partner); + partners.put(bpId, partner); final var debitorRel = addRelation( HsOfficeRelationType.DEBITOR, partnerRel.getHolder(), // partner person @@ -765,7 +769,7 @@ public class ImportOfficeData extends CsvDataImport { .vatBusiness("GROSS".equals(rec.getString("indicator_vat"))) // TODO: remove .vatId(rec.getString("uid_vat")) .build(); - debitors.put(rec.getInteger("bp_id"), debitor); + debitors.put(bpId, debitor); if (isNotBlank(rec.getString("member_since"))) { assertThat(rec.getInteger("member_id")).isEqualTo(partner.getPartnerNumber()); @@ -781,7 +785,7 @@ public class ImportOfficeData extends CsvDataImport { ? HsOfficeMembershipStatus.ACTIVE : HsOfficeMembershipStatus.UNKNOWN) .build(); - memberships.put(rec.getInteger("bp_id"), membership); + memberships.put(bpId, membership); } }); } @@ -795,6 +799,10 @@ public class ImportOfficeData extends CsvDataImport { .map(row -> new Record(columns, row)) .forEach(rec -> { final var bpId = rec.getInteger("bp_id"); + if (IGNORE_BUSINESS_PARTNERS.contains(bpId)) { + return; + } + final var member = ofNullable(memberships.get(bpId)) .orElseGet(() -> createOnDemandMembership(bpId)); diff --git a/src/test/resources/migration/hosting/packet_component.csv b/src/test/resources/migration/hosting/packet_component.csv index 233fc289..74004918 100644 --- a/src/test/resources/migration/hosting/packet_component.csv +++ b/src/test/resources/migration/hosting/packet_component.csv @@ -110,7 +110,7 @@ packet_component_id;packet_id;quantity;basecomponent_code;created;cancelled 1339235;19959;1;MULTI;2023-10-27; 1341088;1061;0;SLAOFFIC2H;2023-12-14; 1341089;1061;0;SLAOFFIC8H;2023-12-14; -1341090;1061;0;STORAGE;2023-12-14; +1341090;1061;256000;STORAGE;2023-12-14; 1341091;1061;0;SLAMAIL4H;2023-12-14; 1341092;1061;0;SLAMAIL2H;2023-12-14; 1341093;1061;0;SLAPLAT2H;2023-12-14; @@ -118,7 +118,7 @@ packet_component_id;packet_id;quantity;basecomponent_code;created;cancelled 1341095;1061;0;SLAPLAT4H;2023-12-14; 1341096;1061;1;SLAPGSQL8H;2023-12-14; 1341097;1061;2;CPU;2023-12-14; -1341098;1061;76800;QUOTA;2023-12-14; +1341098;1061;0;QUOTA;2023-12-14; 1341099;1061;0;SLAMAIL8H;2023-12-14; 1341100;1061;1;SLABASIC;2023-12-14; 1341101;1061;1;SLAMARIA8H;2023-12-14;