diff --git a/src/main/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemEntity.java b/src/main/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemEntity.java index 94b80984..ba1d2a7e 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemEntity.java @@ -11,6 +11,7 @@ import lombok.NoArgsConstructor; import lombok.Setter; import net.hostsharing.hsadminng.hs.booking.project.HsBookingProjectEntity; import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity; +import net.hostsharing.hsadminng.hs.validation.PropertiesProvider; import net.hostsharing.hsadminng.mapper.PatchableMapWrapper; import net.hostsharing.hsadminng.rbac.rbacdef.RbacView; import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL; @@ -42,6 +43,7 @@ import java.util.List; import java.util.Map; import java.util.UUID; +import static java.util.Collections.emptyMap; import static java.util.Optional.ofNullable; import static net.hostsharing.hsadminng.mapper.PostgresDateRange.lowerInclusiveFromPostgresDateRange; import static net.hostsharing.hsadminng.mapper.PostgresDateRange.toPostgresDateRange; @@ -68,7 +70,7 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify; @Setter @NoArgsConstructor @AllArgsConstructor -public class HsBookingItemEntity implements Stringifyable, RbacObject { +public class HsBookingItemEntity implements Stringifyable, RbacObject, PropertiesProvider { private static Stringify stringify = stringify(HsBookingItemEntity.class) .withProp(HsBookingItemEntity::getProject) @@ -146,6 +148,23 @@ public class HsBookingItemEntity implements Stringifyable, RbacObject { return upperInclusiveFromPostgresDateRange(getValidity()); } + @Override + public Map directProps() { + return resources; + } + + @Override + public Object getContextValue(final String propName) { + final var v = resources.get(propName); + if (v!= null) { + return v; + } + if (parentItem!=null) { + return parentItem.getResources().get(propName); + } + return emptyMap(); + } + @Override public String toString() { return stringify.apply(this); 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 ee07e981..315de471 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 @@ -29,7 +29,7 @@ public class HsBookingItemEntityValidator extends HsEntityValidator validateProperties(final HsBookingItemEntity bookingItem) { - return enrich(prefix(bookingItem.toShortString(), "resources"), validateProperties(bookingItem.getResources())); + return enrich(prefix(bookingItem.toShortString(), "resources"), super.validateProperties(bookingItem)); } private static List optionallyValidate(final HsBookingItemEntity bookingItem) { diff --git a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetEntity.java b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetEntity.java index ff7bfd33..ae181921 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetEntity.java @@ -9,6 +9,7 @@ import lombok.NoArgsConstructor; import lombok.Setter; import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity; import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity; +import net.hostsharing.hsadminng.hs.validation.PropertiesProvider; import net.hostsharing.hsadminng.mapper.PatchableMapWrapper; import net.hostsharing.hsadminng.rbac.rbacdef.RbacView; import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL; @@ -39,6 +40,7 @@ import java.util.List; import java.util.Map; import java.util.UUID; +import static java.util.Collections.emptyMap; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Column.dependsOnColumn; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.ColumnValue.usingDefaultCase; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.GLOBAL; @@ -63,7 +65,7 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify; @Setter @NoArgsConstructor @AllArgsConstructor -public class HsHostingAssetEntity implements Stringifyable, RbacObject { +public class HsHostingAssetEntity implements Stringifyable, RbacObject, PropertiesProvider { private static Stringify stringify = stringify(HsHostingAssetEntity.class) .withProp(HsHostingAssetEntity::getType) @@ -122,7 +124,7 @@ public class HsHostingAssetEntity implements Stringifyable, RbacObject { private PatchableMapWrapper configWrapper; @Transient - private boolean isLoaded = false; + private boolean isLoaded; @PostLoad public void markAsLoaded() { @@ -137,6 +139,28 @@ public class HsHostingAssetEntity implements Stringifyable, RbacObject { PatchableMapWrapper.of(configWrapper, (newWrapper) -> {configWrapper = newWrapper; }, config).assign(newConfig); } + @Override + public Map directProps() { + return config; + } + + @Override + public Object getContextValue(final String propName) { + final var v = config.get(propName); + if (v!= null) { + return v; + } + + if (bookingItem!=null) { + return bookingItem.getResources().get(propName); + } + if (parentAsset!=null && parentAsset.getBookingItem()!=null) { + return parentAsset.getBookingItem().getResources().get(propName); + } + return emptyMap(); + } + + @Override public String toString() { return stringify.apply(this); diff --git a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsHostingAssetEntityValidator.java b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsHostingAssetEntityValidator.java index 8afbf99a..05bcee97 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsHostingAssetEntityValidator.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsHostingAssetEntityValidator.java @@ -76,7 +76,7 @@ public abstract class HsHostingAssetEntityValidator extends HsEntityValidator validateProperties(final HsHostingAssetEntity assetEntity) { - return enrich(prefix(assetEntity.toShortString(), "config"), validateProperties(assetEntity.getConfig())); + return enrich(prefix(assetEntity.toShortString(), "config"), super.validateProperties(assetEntity)); } private static List optionallyValidate(final HsHostingAssetEntity assetEntity) { diff --git a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsUnixUserHostingAssetValidator.java b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsUnixUserHostingAssetValidator.java index 75076cc7..d0f0ed27 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsUnixUserHostingAssetValidator.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsUnixUserHostingAssetValidator.java @@ -18,14 +18,14 @@ class HsUnixUserHostingAssetValidator extends HsHostingAssetEntityValidator { AlarmContact.isOptional(), integerProperty("SSD hard quota").unit("GB").maxFrom("SSD").optional(), - integerProperty("SSD soft quota").unit("GB").minFrom("SSD hard quota").optional(), + integerProperty("SSD soft quota").unit("GB").maxFrom("SSD hard quota").optional(), integerProperty("HDD hard quota").unit("GB").maxFrom("HDD").optional(), - integerProperty("HDD soft quota").unit("GB").minFrom("HDD hard quota").optional(), + integerProperty("HDD soft quota").unit("GB").maxFrom("HDD hard quota").optional(), enumerationProperty("shell") .values("/bin/false", "/bin/bash", "/bin/csh", "/bin/dash", "/usr/bin/tcsh", "/usr/bin/zsh", "/usr/bin/passwd") .withDefault("/bin/false"), stringProperty("homedir").readOnly(), - stringProperty("totpKey").matchesRegEx("^0x\\([0-9A-Fa-f][0-9A-Fa-f]\\)+$").minLength(12).maxLength(32).writeOnly().optional(), + stringProperty("totpKey").matchesRegEx("^0x([0-9A-Fa-f]{2})+$").minLength(20).maxLength(256).writeOnly().optional(), stringProperty("password").minLength(8).maxLength(40).writeOnly()); // FIXME: spec } diff --git a/src/main/java/net/hostsharing/hsadminng/hs/validation/BooleanProperty.java b/src/main/java/net/hostsharing/hsadminng/hs/validation/BooleanProperty.java index 9d664683..9a1286b0 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/validation/BooleanProperty.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/validation/BooleanProperty.java @@ -29,9 +29,9 @@ public class BooleanProperty extends ValidatableProperty { } @Override - protected void validate(final ArrayList result, final Boolean propValue, final Map props) { + protected void validate(final ArrayList result, final Boolean propValue, final PropertiesProvider propProvider) { if (falseIf != null && propValue) { - final Object referencedValue = props.get(falseIf.getKey()); + final Object referencedValue = propProvider.directProps().get(falseIf.getKey()); if (Objects.equals(referencedValue, falseIf.getValue())) { result.add(propertyName + "' is expected to be false because " + falseIf.getKey() + "=" + referencedValue + " but is " + propValue); diff --git a/src/main/java/net/hostsharing/hsadminng/hs/validation/EnumerationProperty.java b/src/main/java/net/hostsharing/hsadminng/hs/validation/EnumerationProperty.java index 262fc1ac..93ebf242 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/validation/EnumerationProperty.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/validation/EnumerationProperty.java @@ -5,7 +5,6 @@ import net.hostsharing.hsadminng.mapper.Array; import java.util.ArrayList; import java.util.Arrays; -import java.util.Map; import static java.util.Arrays.stream; @@ -51,7 +50,7 @@ public class EnumerationProperty extends ValidatableProperty { } @Override - protected void validate(final ArrayList result, final String propValue, final Map props) { + protected void validate(final ArrayList result, final String propValue, final PropertiesProvider propProvider) { if (stream(values).noneMatch(v -> v.equals(propValue))) { result.add(propertyName + "' is expected to be one of " + Arrays.toString(values) + " but is '" + propValue + "'"); } diff --git a/src/main/java/net/hostsharing/hsadminng/hs/validation/HsEntityValidator.java b/src/main/java/net/hostsharing/hsadminng/hs/validation/HsEntityValidator.java index 7f472583..7dfced5c 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/validation/HsEntityValidator.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/validation/HsEntityValidator.java @@ -38,10 +38,11 @@ public abstract class HsEntityValidator { .toList(); } - protected ArrayList validateProperties(final Map properties) { + protected ArrayList validateProperties(final PropertiesProvider propsProvider) { final var result = new ArrayList(); // verify that all actually given properties are specified + final var properties = propsProvider.directProps(); properties.keySet().forEach( givenPropName -> { if (stream(propertyValidators).map(pv -> pv.propertyName).noneMatch(propName -> propName.equals(givenPropName))) { result.add(givenPropName + "' is not expected but is set to '" + properties.get(givenPropName) + "'"); @@ -50,7 +51,7 @@ public abstract class HsEntityValidator { // run all property validators stream(propertyValidators).forEach(pv -> { - result.addAll(pv.validate(properties)); + result.addAll(pv.validate(propsProvider)); }); return result; diff --git a/src/main/java/net/hostsharing/hsadminng/hs/validation/IntegerProperty.java b/src/main/java/net/hostsharing/hsadminng/hs/validation/IntegerProperty.java index 75d3e79e..14f97193 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/validation/IntegerProperty.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/validation/IntegerProperty.java @@ -2,9 +2,9 @@ package net.hostsharing.hsadminng.hs.validation; import lombok.Setter; import net.hostsharing.hsadminng.mapper.Array; +import org.apache.commons.lang3.Validate; import java.util.ArrayList; -import java.util.Map; @Setter public class IntegerProperty extends ValidatableProperty { @@ -29,6 +29,12 @@ public class IntegerProperty extends ValidatableProperty { super(Integer.class, propertyName, KEY_ORDER); } + @Override + public void deferredInit(final ValidatableProperty[] allProperties) { + Validate.isTrue(min == null || minFrom == null, "min and minFrom are exclusive, but both are given"); + Validate.isTrue(max == null || maxFrom == null, "max and maxFrom are exclusive, but both are given"); + } + public IntegerProperty minFrom(final String propertyName) { minFrom = propertyName; return this; @@ -49,20 +55,34 @@ public class IntegerProperty extends ValidatableProperty { } @Override - protected void validate(final ArrayList result, final Integer propValue, final Map props) { - if (min != null && propValue < min) { - result.add(propertyName + "' is expected to be >= " + min + " but is " + propValue); - } - if (max != null && propValue > max) { - result.add(propertyName + "' is expected to be <= " + max + " but is " + propValue); - } + protected void validate(final ArrayList result, final Integer propValue, final PropertiesProvider propProvider) { + validateMin(result, propertyName, propValue, min); + validateMax(result, propertyName, propValue, max); if (step != null && propValue % step != 0) { result.add(propertyName + "' is expected to be multiple of " + step + " but is " + propValue); } + if (minFrom != null) { + validateMin(result, propertyName, propValue, propProvider.getContextValue(minFrom, Integer.class)); + } + if (maxFrom != null) { + validateMax(result, propertyName, propValue, propProvider.getContextValue(maxFrom, Integer.class, 0)); + } } @Override protected String simpleTypeName() { return "integer"; } + + private static void validateMin(final ArrayList result, final String propertyName, final Integer propValue, final Integer min) { + if (min != null && propValue < min) { + result.add(propertyName + "' is expected to be at least " + min + " but is " + propValue); + } + } + + private static void validateMax(final ArrayList result, final String propertyName, final Integer propValue, final Integer max) { + if (max != null && propValue > max) { + result.add(propertyName + "' is expected to be at most " + max + " but is " + propValue); + } + } } diff --git a/src/main/java/net/hostsharing/hsadminng/hs/validation/StringProperty.java b/src/main/java/net/hostsharing/hsadminng/hs/validation/StringProperty.java index 2755eabb..ba837287 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/validation/StringProperty.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/validation/StringProperty.java @@ -4,7 +4,6 @@ import lombok.Setter; import net.hostsharing.hsadminng.mapper.Array; import java.util.ArrayList; -import java.util.Map; import java.util.regex.Pattern; @@ -57,7 +56,7 @@ public class StringProperty extends ValidatableProperty { } @Override - protected void validate(final ArrayList result, final String propValue, final Map props) { + protected void validate(final ArrayList result, final String propValue, final PropertiesProvider propProvider) { if (minLength != null && propValue.length() { if (regExPattern != null && !regExPattern.matcher(propValue).matches()) { result.add(propertyName + "' is expected to be match " + regExPattern + " but '" + propValue+ "' does not match"); } + if (readOnly && propValue != null) { + result.add(propertyName + "' is readonly but given as '" + propValue+ "'"); + } } @Override 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 3b0bb099..021fc90b 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/validation/ValidatableProperty.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/validation/ValidatableProperty.java @@ -116,8 +116,9 @@ public abstract class ValidatableProperty { return this; } - public final List validate(final Map props) { + public final List validate(final PropertiesProvider propsProvider) { final var result = new ArrayList(); + final var props = propsProvider.directProps(); final var propValue = props.get(propertyName); if (propValue == null) { if (required) { @@ -127,7 +128,7 @@ public abstract class ValidatableProperty { if (propValue != null){ if ( type.isInstance(propValue)) { //noinspection unchecked - validate(result, (T) propValue, props); + validate(result, (T) propValue, propsProvider); } else { result.add(propertyName + "' is expected to be of type " + type + ", " + "but is of type '" + propValue.getClass().getSimpleName() + "'"); @@ -136,7 +137,7 @@ public abstract class ValidatableProperty { return result; } - protected abstract void validate(final ArrayList result, final T propValue, final Map props); + protected abstract void validate(final ArrayList result, final T propValue, final PropertiesProvider propProvider); public void verifyConsistency(final Map.Entry, ?> typeDef) { if (required == null ) { diff --git a/src/main/java/net/hostsharing/hsadminng/mapper/Array.java b/src/main/java/net/hostsharing/hsadminng/mapper/Array.java index 39588f11..86a4766a 100644 --- a/src/main/java/net/hostsharing/hsadminng/mapper/Array.java +++ b/src/main/java/net/hostsharing/hsadminng/mapper/Array.java @@ -1,5 +1,6 @@ package net.hostsharing.hsadminng.mapper; + import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -43,4 +44,8 @@ public class Array { .toArray(String[]::new); return joined; } + + public static T[] emptyArray() { + return of(); + } } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/booking/item/TestHsBookingItem.java b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/TestHsBookingItem.java index 039e6fb1..b2b43df9 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/booking/item/TestHsBookingItem.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/TestHsBookingItem.java @@ -37,7 +37,7 @@ public class TestHsBookingItem { .type(HsBookingItemType.MANAGED_WEBSPACE) .caption("test managed webspace item") .resources(Map.ofEntries( - entry("SSD", 25), + entry("SSD", 50), entry("Traffic", 250) )) .validity(Range.closedInfinite(LocalDate.of(2020, 1, 15))) diff --git a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetControllerAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetControllerAcceptanceTest.java index 0b231bbd..391b4d3e 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetControllerAcceptanceTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetControllerAcceptanceTest.java @@ -292,8 +292,8 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup "statusPhrase": "Bad Request", "message": "[ <<<'MANAGED_SERVER:vm1400.config.extra' is not expected but is set to '42', - <<<'MANAGED_SERVER:vm1400.config.monit_max_cpu_usage' is expected to be <= 100 but is 101, - <<<'MANAGED_SERVER:vm1400.config.monit_max_ssd_usage' is expected to be >= 10 but is 0 + <<<'MANAGED_SERVER:vm1400.config.monit_max_cpu_usage' is expected to be at most 100 but is 101, + <<<'MANAGED_SERVER:vm1400.config.monit_max_ssd_usage' is expected to be at least 10 but is 0 <<<]" } """.replaceAll(" +<<<", ""))); // @formatter:on diff --git a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsManagedServerHostingAssetValidatorUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsManagedServerHostingAssetValidatorUnitTest.java index 010bbf54..2eb7f581 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsManagedServerHostingAssetValidatorUnitTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsManagedServerHostingAssetValidatorUnitTest.java @@ -37,8 +37,8 @@ class HsManagedServerHostingAssetValidatorUnitTest { assertThat(result).containsExactlyInAnyOrder( "'MANAGED_SERVER:vm1234.parentAsset' must be null but is set to D-???????-?:null", "'MANAGED_SERVER:vm1234.assignedToAsset' must be null but is set to D-???????-?:null", - "'MANAGED_SERVER:vm1234.config.monit_max_cpu_usage' is expected to be >= 10 but is 2", - "'MANAGED_SERVER:vm1234.config.monit_max_ram_usage' is expected to be <= 100 but is 101", + "'MANAGED_SERVER:vm1234.config.monit_max_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_hdd_usage' is expected to be of type class java.lang.Integer, but is of type 'String'"); } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsUnixUserHostingAssetValidatorUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsUnixUserHostingAssetValidatorUnitTest.java index 3e9b8271..49019beb 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsUnixUserHostingAssetValidatorUnitTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsUnixUserHostingAssetValidatorUnitTest.java @@ -36,6 +36,12 @@ class HsUnixUserHostingAssetValidatorUnitTest { .parentAsset(TEST_MANAGED_WEBSPACE_HOSTING_ASSET) .identifier("abc00-temp") .caption("some valid test UnixUser") + .config(Map.ofEntries( + entry("SSD hard quota", 50), + entry("SSD soft quota", 40), + entry("totpKey", "0x123456789abcdef01234"), + entry("password", "Hallo Computer, lass mich rein!") + )) .build(); final var validator = HsHostingAssetEntityValidatorRegistry.forType(unixUserHostingAsset.getType()); @@ -55,13 +61,14 @@ class HsUnixUserHostingAssetValidatorUnitTest { .identifier("abc00-temp") .caption("some test UnixUser with invalid properties") .config(Map.ofEntries( - entry("SSD hard quota", 1000), - entry("SSD soft quota", 2000), - entry("HDD hard quota", 1000), - entry("HDD soft quota", 2000), + entry("SSD hard quota", 100), + entry("SSD soft quota", 200), + entry("HDD hard quota", 100), + entry("HDD soft quota", 200), + entry("shell", "/is/invalid"), entry("homedir", "/is/read-only"), entry("totpKey", "should be a hex number"), - entry("password", "should be a hex number") + entry("password", "short") )) .build(); final var validator = HsHostingAssetEntityValidatorRegistry.forType(unixUserHostingAsset.getType()); @@ -70,7 +77,16 @@ class HsUnixUserHostingAssetValidatorUnitTest { final var result = validator.validate(unixUserHostingAsset); // then - assertThat(result).isEmpty(); + assertThat(result).containsExactlyInAnyOrder( + "'UNIX_USER:abc00-temp.config.SSD hard quota' is expected to be at most 50 but is 100", + "'UNIX_USER:abc00-temp.config.SSD soft quota' is expected to be at most 100 but is 200", + "'UNIX_USER:abc00-temp.config.HDD hard quota' is expected to be at most 0 but is 100", + "'UNIX_USER:abc00-temp.config.HDD soft quota' is expected to be at most 100 but is 200", + "'UNIX_USER:abc00-temp.config.shell' is expected to be one of [/bin/false, /bin/bash, /bin/csh, /bin/dash, /usr/bin/tcsh, /usr/bin/zsh, /usr/bin/passwd] but is '/is/invalid'", + "'UNIX_USER:abc00-temp.config.homedir' is readonly but given as '/is/read-only'", + "'UNIX_USER:abc00-temp.config.totpKey' is expected to be match ^0x([0-9A-Fa-f]{2})+$ but 'should be a hex number' does not match", + "'UNIX_USER:abc00-temp.config.password' length is expected to be at min 8 but length of 'short' is 5" + ); } @Test