better error handling + requiresExactlyOneOf implemented

This commit is contained in:
Michael Hoennig 2024-07-20 13:17:46 +02:00
parent 7daf57513e
commit b012225c8b
20 changed files with 182 additions and 71 deletions

View File

@ -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'

View File

@ -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 {

View File

@ -22,6 +22,10 @@ public class HsBookingItemEntityValidator extends HsEntityValidator<HsBookingIte
@Override
public List<String> 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));
}

View File

@ -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

View File

@ -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(),

View File

@ -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())

View File

@ -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()

View File

@ -68,7 +68,7 @@ public class HsOfficeMembershipEntity implements RbacObject, Stringifyable {
private static Stringify<HsOfficeMembershipEntity> 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);

View File

@ -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<P extends 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<T> type;
@ -40,6 +42,8 @@ public abstract class ValidatableProperty<P extends ValidatableProperty<?, ?>, T
private final String[] keyOrder;
private Boolean required;
private Set<String> requiresExactlyOneOf;
private Set<String> requiresAtMaxOneOf;
private T defaultValue;
@JsonIgnore
@ -100,9 +104,19 @@ protected void setDeferredInit(final Function<ValidatableProperty<?, ?>[], T[]>
return self();
}
public ValidatableProperty<P, T> 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<ValidatableProperty<?, ?>[], T[]>
final var result = new ArrayList<String>();
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<String> 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<String> 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<String> result, final T propValue, final PropertiesProvider propProvider);
public void verifyConsistency(final Map.Entry<? extends Enum<?>, ?> 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(...)" );
}
}

View File

@ -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

View File

@ -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 } )");
}
}

View File

@ -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");

View File

@ -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"

View File

@ -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"

View File

@ -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}");
}

View File

@ -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",

View File

@ -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 {

View File

@ -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

View File

@ -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));

View File

@ -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;

1 packet_component_id packet_id quantity basecomponent_code created cancelled
110 1339235 19959 1 MULTI 2023-10-27
111 1341088 1061 0 SLAOFFIC2H 2023-12-14
112 1341089 1061 0 SLAOFFIC8H 2023-12-14
113 1341090 1061 0 256000 STORAGE 2023-12-14
114 1341091 1061 0 SLAMAIL4H 2023-12-14
115 1341092 1061 0 SLAMAIL2H 2023-12-14
116 1341093 1061 0 SLAPLAT2H 2023-12-14
118 1341095 1061 0 SLAPLAT4H 2023-12-14
119 1341096 1061 1 SLAPGSQL8H 2023-12-14
120 1341097 1061 2 CPU 2023-12-14
121 1341098 1061 76800 0 QUOTA 2023-12-14
122 1341099 1061 0 SLAMAIL8H 2023-12-14
123 1341100 1061 1 SLABASIC 2023-12-14
124 1341101 1061 1 SLAMARIA8H 2023-12-14