Compare commits

...

11 Commits

66 changed files with 1595 additions and 679 deletions

View File

@ -9,7 +9,7 @@ import org.springframework.web.context.request.WebRequest;
import java.time.LocalDateTime;
@Getter
class CustomErrorResponse {
public class CustomErrorResponse {
static ResponseEntity<CustomErrorResponse> errorResponse(
final WebRequest request,

View File

@ -1,5 +1,6 @@
package net.hostsharing.hsadminng.errors;
import net.hostsharing.hsadminng.hs.validation.MultiValidationException;
import org.iban4j.Iban4jException;
import org.springframework.core.NestedExceptionUtils;
import org.springframework.dao.DataIntegrityViolationException;
@ -75,7 +76,8 @@ public class RestResponseEntityExceptionHandler
@ExceptionHandler({ Iban4jException.class, ValidationException.class })
protected ResponseEntity<CustomErrorResponse> handleIbanAndBicExceptions(
final Throwable exc, final WebRequest request) {
final var message = line(NestedExceptionUtils.getMostSpecificCause(exc).getMessage(), 0);
final String fullMessage = NestedExceptionUtils.getMostSpecificCause(exc).getMessage();
final var message = exc instanceof MultiValidationException ? fullMessage : line(fullMessage, 0);
return errorResponse(request, HttpStatus.BAD_REQUEST, message);
}

View File

@ -13,11 +13,13 @@ import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import java.util.List;
import java.util.UUID;
import java.util.function.BiConsumer;
import static net.hostsharing.hsadminng.hs.booking.item.validators.HsBookingItemEntityValidators.valid;
import static net.hostsharing.hsadminng.hs.booking.item.validators.HsBookingItemEntityValidator.validated;
import static net.hostsharing.hsadminng.mapper.PostgresDateRange.toPostgresDateRange;
@RestController
@ -32,6 +34,9 @@ public class HsBookingItemController implements HsBookingItemsApi {
@Autowired
private HsBookingItemRepository bookingItemRepo;
@PersistenceContext
private EntityManager em;
@Override
@Transactional(readOnly = true)
public ResponseEntity<List<HsBookingItemResource>> listBookingItemsByProjectUuid(
@ -57,7 +62,7 @@ public class HsBookingItemController implements HsBookingItemsApi {
final var entityToSave = mapper.map(body, HsBookingItemEntity.class, RESOURCE_TO_ENTITY_POSTMAPPER);
final var saved = bookingItemRepo.save(valid(entityToSave));
final var saved = validated(bookingItemRepo.save(entityToSave));
final var uri =
MvcUriComponentsBuilder.fromController(getClass())
@ -78,6 +83,7 @@ public class HsBookingItemController implements HsBookingItemsApi {
context.define(currentUser, assumedRoles);
final var result = bookingItemRepo.findByUuid(bookingItemUuid);
result.ifPresent(entity -> em.detach(entity));
return result
.map(bookingItemEntity -> ResponseEntity.ok(
mapper.map(bookingItemEntity, HsBookingItemResource.class, ENTITY_TO_RESOURCE_POSTMAPPER)))
@ -112,7 +118,7 @@ public class HsBookingItemController implements HsBookingItemsApi {
new HsBookingItemEntityPatcher(current).apply(body);
final var saved = bookingItemRepo.save(valid(current));
final var saved = bookingItemRepo.save(validated(current));
final var mapped = mapper.map(saved, HsBookingItemResource.class, ENTITY_TO_RESOURCE_POSTMAPPER);
return ResponseEntity.ok(mapped);
}

View File

@ -10,7 +10,7 @@ import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import net.hostsharing.hsadminng.hs.booking.project.HsBookingProjectEntity;
import net.hostsharing.hsadminng.hs.validation.Validatable;
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
import net.hostsharing.hsadminng.mapper.PatchableMapWrapper;
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView;
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL;
@ -19,6 +19,7 @@ import net.hostsharing.hsadminng.stringify.Stringify;
import net.hostsharing.hsadminng.stringify.Stringifyable;
import org.hibernate.annotations.Type;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
@ -27,12 +28,14 @@ import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;
import jakarta.persistence.Transient;
import jakarta.persistence.Version;
import java.io.IOException;
import java.time.LocalDate;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
@ -62,7 +65,7 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class HsBookingItemEntity implements Stringifyable, RbacObject, Validatable<HsBookingItemEntity, HsBookingItemType> {
public class HsBookingItemEntity implements Stringifyable, RbacObject {
private static Stringify<HsBookingItemEntity> stringify = stringify(HsBookingItemEntity.class)
.withProp(HsBookingItemEntity::getProject)
@ -85,6 +88,7 @@ public class HsBookingItemEntity implements Stringifyable, RbacObject, Validatab
@ManyToOne
@JoinColumn(name = "parentitemuuid")
// @Transient
private HsBookingItemEntity parentItem;
@Column(name = "type")
@ -105,6 +109,14 @@ public class HsBookingItemEntity implements Stringifyable, RbacObject, Validatab
@Column(columnDefinition = "resources")
private Map<String, Object> resources = new HashMap<>();
@OneToMany(cascade = CascadeType.REFRESH, orphanRemoval = true)
@JoinColumn(name="parentitemuuid", referencedColumnName="uuid")
private List<HsBookingItemEntity> subBookingItems;
@OneToMany(cascade = CascadeType.REFRESH, orphanRemoval = true)
@JoinColumn(name="bookingitemuuid", referencedColumnName="uuid")
private List<HsHostingAssetEntity> subHostingAssets;
@Transient
private PatchableMapWrapper<Object> resourcesWrapper;
@ -150,16 +162,6 @@ public class HsBookingItemEntity implements Stringifyable, RbacObject, Validatab
return parentItem == null ? null : parentItem.relatedProject();
}
@Override
public String getPropertiesName() {
return "resources";
}
@Override
public Map<String, Object> getProperties() {
return resources;
}
public HsBookingProjectEntity getRelatedProject() {
return project != null ? project : parentItem.getRelatedProject();
}

View File

@ -0,0 +1,83 @@
package net.hostsharing.hsadminng.hs.booking.item.validators;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType;
import net.hostsharing.hsadminng.hs.validation.HsEntityValidator;
import net.hostsharing.hsadminng.hs.validation.ValidatableProperty;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Stream;
import static java.util.Arrays.stream;
import static java.util.Collections.emptyList;
import static java.util.Optional.ofNullable;
public class HsBookingItemEntityValidator extends HsEntityValidator<HsBookingItemEntity> {
public static Set<Enum<HsBookingItemType>> types() {
return HsBookingItemEntityValidatorRegistry.types();
}
public static List<String> doValidate(final HsBookingItemEntity bookingItem) {
return HsBookingItemEntityValidatorRegistry.doValidate(bookingItem);
}
public static HsBookingItemEntity validated(final HsBookingItemEntity entityToSave) {
return HsBookingItemEntityValidatorRegistry.validated(entityToSave);
}
public HsBookingItemEntityValidator(final ValidatableProperty<?>... properties) {
super(properties);
}
public List<String> validate(final HsBookingItemEntity bookingItem) {
return sequentiallyValidate(
() -> enrich(prefix(bookingItem.toShortString(), "resources"), validateProperties(bookingItem.getResources())),
() -> enrich(prefix(bookingItem.toShortString(), "parentItem"), optionallyValidate(bookingItem.getParentItem())),
() -> validateAgainstSubEntities(bookingItem)
);
}
private static List<String> optionallyValidate(final HsBookingItemEntity bookingItem) {
return bookingItem != null ? HsBookingItemEntityValidator.doValidate(bookingItem) : emptyList();
}
protected List<String> validateAgainstSubEntities(final HsBookingItemEntity bookingItem) {
return Stream.concat(
stream(propertyValidators)
.map(propDef -> propDef.validateTotals(bookingItem))
.flatMap(Collection::stream),
stream(propertyValidators)
.filter(ValidatableProperty::isTotalsValidator)
.map(prop -> validateMaxTotalValue(bookingItem, prop))
).filter(Objects::nonNull).toList();
}
// FIXME: convert into generic shape like multi-options validator
private static String validateMaxTotalValue(
final HsBookingItemEntity bookingItem,
final ValidatableProperty<?> propDef) {
final var propName = propDef.propertyName();
final var propUnit = ofNullable(propDef.unit()).map(u -> " " + u).orElse("");
final var totalValue = ofNullable(bookingItem.getSubBookingItems()).orElse(emptyList())
.stream()
.map(subItem -> propDef.getValue(subItem.getResources()))
.map(HsBookingItemEntityValidator::toNonNullInteger)
.reduce(0, Integer::sum);
final var maxValue = getNonNullIntegerValue(propDef, bookingItem.getResources());
if (propDef.thresholdPercentage() != null ) {
return totalValue > (maxValue * propDef.thresholdPercentage() / 100)
? "total %s is %d%s exceeds max total %s %d%s, which is above threshold of %d%%"
.formatted(propName, totalValue, propUnit, propName, maxValue, propUnit, propDef.thresholdPercentage())
: null;
} else {
return totalValue > maxValue
? "total %s is %d%s exceeds max total %s %d%s"
.formatted(propName, totalValue, propUnit, propName, maxValue, propUnit)
: null;
}
}
}

View File

@ -1,50 +1,61 @@
package net.hostsharing.hsadminng.hs.booking.item.validators;
import lombok.experimental.UtilityClass;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType;
import net.hostsharing.hsadminng.hs.validation.HsEntityValidator;
import net.hostsharing.hsadminng.hs.validation.MultiValidationException;
import net.hostsharing.hsadminng.hs.validation.ValidatableProperty;
import jakarta.validation.ValidationException;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Stream;
import static java.util.Arrays.stream;
import static java.util.Collections.emptyList;
import static java.util.Optional.ofNullable;
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.CLOUD_SERVER;
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.MANAGED_SERVER;
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.MANAGED_WEBSPACE;
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.PRIVATE_CLOUD;
@UtilityClass
public class HsBookingItemEntityValidators {
public class HsBookingItemEntityValidatorRegistry {
private static final Map<Enum<HsBookingItemType>, HsEntityValidator<HsBookingItemEntity, HsBookingItemType>> validators = new HashMap<>();
private static final Map<Enum<HsBookingItemType>, HsEntityValidator<HsBookingItemEntity>> validators = new HashMap<>();
static {
register(PRIVATE_CLOUD, new HsPrivateCloudBookingItemValidator());
register(CLOUD_SERVER, new HsCloudServerBookingItemValidator());
register(MANAGED_SERVER, new HsManagedServerBookingItemValidator());
register(MANAGED_WEBSPACE, new HsManagedWebspaceBookingItemValidator());
}
private static void register(final Enum<HsBookingItemType> type, final HsEntityValidator<HsBookingItemEntity, HsBookingItemType> validator) {
private static void register(final Enum<HsBookingItemType> type, final HsEntityValidator<HsBookingItemEntity> validator) {
stream(validator.propertyValidators).forEach( entry -> {
entry.verifyConsistency(Map.entry(type, validator));
});
validators.put(type, validator);
}
public static HsEntityValidator<HsBookingItemEntity, HsBookingItemType> forType(final Enum<HsBookingItemType> type) {
public static HsEntityValidator<HsBookingItemEntity> forType(final Enum<HsBookingItemType> type) {
if ( validators.containsKey(type)) {
return validators.get(type);
}
throw new IllegalArgumentException("no validator found for type " + type);
}
public static Set<Enum<HsBookingItemType>> types() {
return validators.keySet();
}
public static HsBookingItemEntity valid(final HsBookingItemEntity entityToSave) {
final var violations = HsBookingItemEntityValidators.forType(entityToSave.getType()).validate(entityToSave);
if (!violations.isEmpty()) {
throw new ValidationException(violations.toString());
public static List<String> doValidate(final HsBookingItemEntity bookingItem) {
return HsBookingItemEntityValidatorRegistry.forType(bookingItem.getType()).validate(bookingItem);
}
public static HsBookingItemEntity validated(final HsBookingItemEntity entityToSave) {
MultiValidationException.throwInvalid(doValidate(entityToSave));
return entityToSave;
}
}

View File

@ -1,20 +1,20 @@
package net.hostsharing.hsadminng.hs.booking.item.validators;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType;
import net.hostsharing.hsadminng.hs.validation.HsEntityValidator;
import static net.hostsharing.hsadminng.hs.validation.EnumerationPropertyValidator.enumerationProperty;
import static net.hostsharing.hsadminng.hs.validation.IntegerPropertyValidator.integerProperty;
import java.util.List;
class HsCloudServerBookingItemValidator extends HsEntityValidator<HsBookingItemEntity, HsBookingItemType> {
import static net.hostsharing.hsadminng.hs.validation.EnumerationProperty.enumerationProperty;
import static net.hostsharing.hsadminng.hs.validation.IntegerProperty.integerProperty;
class HsCloudServerBookingItemValidator extends HsBookingItemEntityValidator {
HsCloudServerBookingItemValidator() {
super(
integerProperty("CPUs").min(1).max(32).required(),
integerProperty("RAM").unit("GB").min(1).max(128).required(),
integerProperty("SSD").unit("GB").min(25).max(1000).step(25).required(),
integerProperty("HDD").unit("GB").min(0).max(4000).step(250).optional(),
integerProperty("HDD").unit("GB").min(0).max(4000).step(250).withDefault(0),
integerProperty("Traffic").unit("GB").min(250).max(10000).step(250).required(),
enumerationProperty("SLA-Infrastructure").values("BASIC", "EXT8H", "EXT4H", "EXT2H").optional()
);

View File

@ -1,24 +1,22 @@
package net.hostsharing.hsadminng.hs.booking.item.validators;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType;
import net.hostsharing.hsadminng.hs.validation.HsEntityValidator;
import static net.hostsharing.hsadminng.hs.validation.BooleanPropertyValidator.booleanProperty;
import static net.hostsharing.hsadminng.hs.validation.EnumerationPropertyValidator.enumerationProperty;
import static net.hostsharing.hsadminng.hs.validation.IntegerPropertyValidator.integerProperty;
class HsManagedServerBookingItemValidator extends HsEntityValidator<HsBookingItemEntity, HsBookingItemType> {
import static net.hostsharing.hsadminng.hs.validation.BooleanProperty.booleanProperty;
import static net.hostsharing.hsadminng.hs.validation.EnumerationProperty.enumerationProperty;
import static net.hostsharing.hsadminng.hs.validation.IntegerProperty.integerProperty;
class HsManagedServerBookingItemValidator extends HsBookingItemEntityValidator {
HsManagedServerBookingItemValidator() {
super(
integerProperty("CPUs").min(1).max(32).required(),
integerProperty("RAM").unit("GB").min(1).max(128).required(),
integerProperty("SSD").unit("GB").min(25).max(1000).step(25).required(),
integerProperty("HDD").unit("GB").min(0).max(4000).step(250).optional(),
integerProperty("Traffic").unit("GB").min(250).max(10000).step(250).required(),
enumerationProperty("SLA-Platform").values("BASIC", "EXT8H", "EXT4H", "EXT2H").optional(),
booleanProperty("SLA-EMail").falseIf("SLA-Platform", "BASIC").optional(),
integerProperty("SSD").unit("GB").min(25).max(1000).step(25).required().asTotalLimit().withThreshold(200),
integerProperty("HDD").unit("GB").min(0).max(4000).step(250).withDefault(0).asTotalLimit().withThreshold(200),
integerProperty("Traffic").unit("GB").min(250).max(10000).step(250).required().asTotalLimit().withThreshold(200),
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(),
booleanProperty("SLA-PgSQL").falseIf("SLA-Platform", "BASIC").optional(),
booleanProperty("SLA-Office").falseIf("SLA-Platform", "BASIC").optional(),

View File

@ -1,24 +1,98 @@
package net.hostsharing.hsadminng.hs.booking.item.validators;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType;
import net.hostsharing.hsadminng.hs.validation.HsEntityValidator;
import net.hostsharing.hsadminng.hs.validation.IntegerProperty;
import org.apache.commons.lang3.function.TriFunction;
import java.util.List;
import static net.hostsharing.hsadminng.hs.validation.BooleanPropertyValidator.booleanProperty;
import static net.hostsharing.hsadminng.hs.validation.EnumerationPropertyValidator.enumerationProperty;
import static net.hostsharing.hsadminng.hs.validation.IntegerPropertyValidator.integerProperty;
import static java.util.Collections.emptyList;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.DOMAIN_EMAIL_SETUP;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.EMAIL_ADDRESS;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MARIADB_DATABASE;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MARIADB_USER;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.PGSQL_DATABASE;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.PGSQL_USER;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.UNIX_USER;
import static net.hostsharing.hsadminng.hs.validation.BooleanProperty.booleanProperty;
import static net.hostsharing.hsadminng.hs.validation.EnumerationProperty.enumerationProperty;
import static net.hostsharing.hsadminng.hs.validation.IntegerProperty.integerProperty;
class HsManagedWebspaceBookingItemValidator extends HsEntityValidator<HsBookingItemEntity, HsBookingItemType> {
class HsManagedWebspaceBookingItemValidator extends HsBookingItemEntityValidator {
public HsManagedWebspaceBookingItemValidator() {
super(
integerProperty("SSD").unit("GB").min(1).max(100).step(1).required(),
integerProperty("HDD").unit("GB").min(0).max(250).step(10).optional(),
integerProperty("Traffic").unit("GB").min(10).max(1000).step(10).required(),
enumerationProperty("SLA-Platform").values("BASIC", "EXT24H").optional(),
integerProperty("Daemons").min(0).max(10).optional(),
booleanProperty("Online Office Server").optional()
integerProperty("Multi").min(1).max(100).step(1).withDefault(1)
.eachComprising( 25, unixUsers())
.eachComprising( 5, databaseUsers())
.eachComprising( 5, databases())
.eachComprising(250, eMailAddresses()),
integerProperty("Daemons").min(0).max(10).withDefault(0),
booleanProperty("Online Office Server").optional(),
enumerationProperty("SLA-Platform").values("BASIC", "EXT24H").withDefault("BASIC")
);
}
private static TriFunction<HsBookingItemEntity, IntegerProperty, Integer, List<String>> unixUsers() {
return (final HsBookingItemEntity entity, final IntegerProperty prop, final Integer factor) -> {
final var unixUserCount = entity.getSubHostingAssets().stream()
.flatMap(ha -> ha.getSubHostingAssets().stream())
.filter(ha -> ha.getType() == UNIX_USER)
.count();
final long limitingValue = prop.getValue(entity.getResources());
if (unixUserCount > factor*limitingValue) {
return List.of(prop.propertyName() + "=" + limitingValue + " allows at maximum " + limitingValue*factor + " unix users, but " + unixUserCount + " found");
}
return emptyList();
};
}
private static TriFunction<HsBookingItemEntity, IntegerProperty, Integer, List<String>> databaseUsers() {
return (final HsBookingItemEntity entity, final IntegerProperty prop, final Integer factor) -> {
final var unixUserCount = entity.getSubHostingAssets().stream()
.flatMap(ha -> ha.getSubHostingAssets().stream())
.filter(bi -> bi.getType() == PGSQL_USER || bi.getType() == MARIADB_USER )
.count();
final long limitingValue = prop.getValue(entity.getResources());
if (unixUserCount > factor*limitingValue) {
return List.of(prop.propertyName() + "=" + limitingValue + " allows at maximum " + limitingValue*factor + " database users, but " + unixUserCount + " found");
}
return emptyList();
};
}
private static TriFunction<HsBookingItemEntity, IntegerProperty, Integer, List<String>> databases() {
return (final HsBookingItemEntity entity, final IntegerProperty prop, final Integer factor) -> {
final var unixUserCount = entity.getSubHostingAssets().stream()
.flatMap(ha -> ha.getSubHostingAssets().stream())
.filter(bi -> bi.getType()==PGSQL_USER || bi.getType()==MARIADB_USER )
.flatMap(domainEMailSetup -> domainEMailSetup.getSubHostingAssets().stream()
.filter(ha -> ha.getType()==PGSQL_DATABASE || ha.getType()==MARIADB_DATABASE))
.count();
final long limitingValue = prop.getValue(entity.getResources());
if (unixUserCount > factor*limitingValue) {
return List.of(prop.propertyName() + "=" + limitingValue + " allows at maximum " + limitingValue*factor + " databases, but " + unixUserCount + " found");
}
return emptyList();
};
}
private static TriFunction<HsBookingItemEntity, IntegerProperty, Integer, List<String>> eMailAddresses() {
return (final HsBookingItemEntity entity, final IntegerProperty prop, final Integer factor) -> {
final var unixUserCount = entity.getSubHostingAssets().stream()
.flatMap(ha -> ha.getSubHostingAssets().stream())
.filter(bi -> bi.getType() == DOMAIN_EMAIL_SETUP)
.flatMap(domainEMailSetup -> domainEMailSetup.getSubHostingAssets().stream()
.filter(ha -> ha.getType()==EMAIL_ADDRESS))
.count();
final long limitingValue = prop.getValue(entity.getResources());
if (unixUserCount > factor*limitingValue) {
return List.of(prop.propertyName() + "=" + limitingValue + " allows at maximum " + limitingValue*factor + " databases, but " + unixUserCount + " found");
}
return emptyList();
};
}
}

View File

@ -0,0 +1,18 @@
package net.hostsharing.hsadminng.hs.booking.item.validators;
import static net.hostsharing.hsadminng.hs.validation.EnumerationProperty.enumerationProperty;
import static net.hostsharing.hsadminng.hs.validation.IntegerProperty.integerProperty;
class HsPrivateCloudBookingItemValidator extends HsBookingItemEntityValidator {
HsPrivateCloudBookingItemValidator() {
super(
integerProperty("CPUs").min(4).max(128).required().asTotalLimit(),
integerProperty("RAM").unit("GB").min(4).max(512).required().asTotalLimit(),
integerProperty("SSD").unit("GB").min(100).max(4000).step(25).required().asTotalLimit(),
integerProperty("HDD").unit("GB").min(0).max(16000).step(25).withDefault(0).asTotalLimit(),
integerProperty("Traffic").unit("GB").min(1000).max(40000).step(250).required().asTotalLimit(),
enumerationProperty("SLA-Infrastructure").values("BASIC", "EXT8H", "EXT4H", "EXT2H").withDefault("BASIC")
);
}
}

View File

@ -20,7 +20,7 @@ import java.util.List;
import java.util.UUID;
import java.util.function.BiConsumer;
import static net.hostsharing.hsadminng.hs.hosting.asset.validators.HsHostingAssetEntityValidators.valid;
import static net.hostsharing.hsadminng.hs.hosting.asset.validators.HsHostingAssetEntityValidator.validated;
@RestController
public class HsHostingAssetController implements HsHostingAssetsApi {
@ -62,7 +62,7 @@ public class HsHostingAssetController implements HsHostingAssetsApi {
final var entityToSave = mapper.map(body, HsHostingAssetEntity.class, RESOURCE_TO_ENTITY_POSTMAPPER);
final var saved = assetRepo.save(valid(entityToSave));
final var saved = validated(assetRepo.save(entityToSave));
final var uri =
MvcUriComponentsBuilder.fromController(getClass())
@ -117,7 +117,7 @@ public class HsHostingAssetController implements HsHostingAssetsApi {
new HsHostingAssetEntityPatcher(current).apply(body);
final var saved = assetRepo.save(valid(current));
final var saved = validated(assetRepo.save(current));
final var mapped = mapper.map(saved, HsHostingAssetResource.class);
return ResponseEntity.ok(mapped);
}

View File

@ -8,7 +8,6 @@ import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
import net.hostsharing.hsadminng.hs.validation.Validatable;
import net.hostsharing.hsadminng.mapper.PatchableMapWrapper;
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView;
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL;
@ -17,6 +16,7 @@ import net.hostsharing.hsadminng.stringify.Stringify;
import net.hostsharing.hsadminng.stringify.Stringifyable;
import org.hibernate.annotations.Type;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
@ -25,11 +25,13 @@ import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;
import jakarta.persistence.Transient;
import jakarta.persistence.Version;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
@ -56,7 +58,7 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class HsHostingAssetEntity implements Stringifyable, RbacObject, Validatable<HsHostingAssetEntity, HsHostingAssetType> {
public class HsHostingAssetEntity implements Stringifyable, RbacObject {
private static Stringify<HsHostingAssetEntity> stringify = stringify(HsHostingAssetEntity.class)
.withProp(HsHostingAssetEntity::getType)
@ -91,6 +93,10 @@ public class HsHostingAssetEntity implements Stringifyable, RbacObject, Validata
@Enumerated(EnumType.STRING)
private HsHostingAssetType type;
@OneToMany(cascade = CascadeType.REFRESH, orphanRemoval = true)
@JoinColumn(name="parentassetuuid", referencedColumnName="uuid")
private List<HsHostingAssetEntity> subHostingAssets;
@Column(name = "identifier")
private String identifier; // vm1234, xyz00, example.org, xyz00_abc
@ -114,16 +120,6 @@ public class HsHostingAssetEntity implements Stringifyable, RbacObject, Validata
PatchableMapWrapper.of(configWrapper, (newWrapper) -> {configWrapper = newWrapper; }, config).assign(newConfg);
}
@Override
public String getPropertiesName() {
return "config";
}
@Override
public Map<String, Object> getProperties() {
return config;
}
@Override
public String toString() {
return stringify.apply(this);

View File

@ -1,6 +1,6 @@
package net.hostsharing.hsadminng.hs.hosting.asset;
import net.hostsharing.hsadminng.hs.hosting.asset.validators.HsHostingAssetEntityValidators;
import net.hostsharing.hsadminng.hs.hosting.asset.validators.HsHostingAssetEntityValidator;
import net.hostsharing.hsadminng.hs.hosting.generated.api.v1.api.HsHostingAssetPropsApi;
import net.hostsharing.hsadminng.hs.hosting.generated.api.v1.model.HsHostingAssetTypeResource;
import org.springframework.http.ResponseEntity;
@ -15,7 +15,7 @@ public class HsHostingAssetPropsController implements HsHostingAssetPropsApi {
@Override
public ResponseEntity<List<String>> listAssetTypes() {
final var resource = HsHostingAssetEntityValidators.types().stream()
final var resource = HsHostingAssetEntityValidator.types().stream()
.map(Enum::name)
.toList();
return ResponseEntity.ok(resource);
@ -25,7 +25,7 @@ public class HsHostingAssetPropsController implements HsHostingAssetPropsApi {
public ResponseEntity<List<Object>> listAssetTypeProps(
final HsHostingAssetTypeResource assetType) {
final var propValidators = HsHostingAssetEntityValidators.forType(HsHostingAssetType.of(assetType));
final var propValidators = HsHostingAssetEntityValidator.forType(HsHostingAssetType.of(assetType));
final List<Map<String, Object>> resource = propValidators.properties();
return ResponseEntity.ok(toListOfObjects(resource));
}

View File

@ -0,0 +1,106 @@
package net.hostsharing.hsadminng.hs.hosting.asset.validators;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
import net.hostsharing.hsadminng.hs.booking.item.validators.HsBookingItemEntityValidator;
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType;
import net.hostsharing.hsadminng.hs.validation.HsEntityValidator;
import net.hostsharing.hsadminng.hs.validation.MultiValidationException;
import net.hostsharing.hsadminng.hs.validation.ValidatableProperty;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import static java.util.Arrays.stream;
import static java.util.Collections.emptyList;
import static java.util.Optional.ofNullable;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.*;
public class HsHostingAssetEntityValidator extends HsEntityValidator<HsHostingAssetEntity> {
private static final Map<Enum<HsHostingAssetType>, HsEntityValidator<HsHostingAssetEntity>> validators = new HashMap<>();
static {
register(CLOUD_SERVER, new HsHostingAssetEntityValidator());
register(MANAGED_SERVER, new HsManagedServerHostingAssetValidator());
register(MANAGED_WEBSPACE, new HsManagedWebspaceHostingAssetValidator());
register(UNIX_USER, new HsHostingAssetEntityValidator());
}
private static void register(final Enum<HsHostingAssetType> type, final HsEntityValidator<HsHostingAssetEntity> validator) {
stream(validator.propertyValidators).forEach( entry -> {
entry.verifyConsistency(Map.entry(type, validator));
});
validators.put(type, validator);
}
public static HsEntityValidator<HsHostingAssetEntity> forType(final Enum<HsHostingAssetType> type) {
if ( validators.containsKey(type)) {
return validators.get(type);
}
throw new IllegalArgumentException("no validator found for type " + type);
}
public static Set<Enum<HsHostingAssetType>> types() {
return validators.keySet();
}
public static List<String> doValidate(final HsHostingAssetEntity hostingAsset) {
return HsHostingAssetEntityValidator.forType(hostingAsset.getType()).validate(hostingAsset);
}
public static HsHostingAssetEntity validated(final HsHostingAssetEntity entityToSave) {
MultiValidationException.throwInvalid(doValidate(entityToSave));
return entityToSave;
}
public HsHostingAssetEntityValidator(final ValidatableProperty<?>... properties) {
super(properties);
}
@Override
public List<String> validate(final HsHostingAssetEntity assetEntity) {
return sequentiallyValidate(
() -> enrich(prefix(assetEntity.toShortString(), "config"), validateProperties(assetEntity.getConfig())),
() -> enrich(prefix(assetEntity.toShortString(), "bookingItem"), optionallyValidate(assetEntity.getBookingItem())),
() -> enrich(prefix(assetEntity.toShortString(), "parentAsset"), optionallyValidate(assetEntity.getParentAsset())),
() -> validateSubEntities(assetEntity)
);
}
private static List<String> optionallyValidate(final HsHostingAssetEntity assetEntity) {
return assetEntity != null ? forType(assetEntity.getType()).validate(assetEntity) : emptyList();
}
private static List<String> optionallyValidate(final HsBookingItemEntity bookingItem) {
return bookingItem != null ? HsBookingItemEntityValidator.doValidate(bookingItem) : emptyList();
}
protected List<String> validateSubEntities(final HsHostingAssetEntity assetEntity) {
return stream(propertyValidators)
.filter(ValidatableProperty::isTotalsValidator)
.map(prop -> validateMaxTotalValue(assetEntity, prop))
.filter(Objects::nonNull)
.toList();
}
private String validateMaxTotalValue(
final HsHostingAssetEntity hostingAsset,
final ValidatableProperty<?> propDef) {
final var propName = propDef.propertyName();
final var propUnit = ofNullable(propDef.unit()).map(u -> " " + u).orElse("");
final var totalValue = ofNullable(hostingAsset.getSubHostingAssets()).orElse(emptyList())
.stream()
.map(subItem -> propDef.getValue(subItem.getConfig()))
.map(HsEntityValidator::toNonNullInteger)
.reduce(0, Integer::sum);
final var maxValue = getNonNullIntegerValue(propDef, hostingAsset.getConfig());
return totalValue > maxValue
? "total %s is %d%s exceeds max total %s %d%s".formatted(
propName, totalValue, propUnit, propName, maxValue, propUnit)
: null;
}
}

View File

@ -1,51 +0,0 @@
package net.hostsharing.hsadminng.hs.hosting.asset.validators;
import lombok.experimental.UtilityClass;
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType;
import net.hostsharing.hsadminng.hs.validation.HsEntityValidator;
import jakarta.validation.ValidationException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import static java.util.Arrays.stream;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.CLOUD_SERVER;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_SERVER;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_WEBSPACE;
@UtilityClass
public class HsHostingAssetEntityValidators {
private static final Map<Enum<HsHostingAssetType>, HsEntityValidator<HsHostingAssetEntity, HsHostingAssetType>> validators = new HashMap<>();
static {
register(CLOUD_SERVER, new HsEntityValidator<>());
register(MANAGED_SERVER, new HsManagedServerHostingAssetValidator());
register(MANAGED_WEBSPACE, new HsManagedWebspaceHostingAssetValidator());
}
private static void register(final Enum<HsHostingAssetType> type, final HsEntityValidator<HsHostingAssetEntity, HsHostingAssetType> validator) {
stream(validator.propertyValidators).forEach( entry -> {
entry.verifyConsistency(Map.entry(type, validator));
});
validators.put(type, validator);
}
public static HsEntityValidator<HsHostingAssetEntity, HsHostingAssetType> forType(final Enum<HsHostingAssetType> type) {
return validators.get(type);
}
public static Set<Enum<HsHostingAssetType>> types() {
return validators.keySet();
}
public static HsHostingAssetEntity valid(final HsHostingAssetEntity entityToSave) {
final var violations = HsHostingAssetEntityValidators.forType(entityToSave.getType()).validate(entityToSave);
if (!violations.isEmpty()) {
throw new ValidationException(violations.toString());
}
return entityToSave;
}
}

View File

@ -1,12 +1,8 @@
package net.hostsharing.hsadminng.hs.hosting.asset.validators;
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType;
import net.hostsharing.hsadminng.hs.validation.HsEntityValidator;
import static net.hostsharing.hsadminng.hs.validation.IntegerProperty.integerProperty;
import static net.hostsharing.hsadminng.hs.validation.IntegerPropertyValidator.integerProperty;
class HsManagedServerHostingAssetValidator extends HsEntityValidator<HsHostingAssetEntity, HsHostingAssetType> {
class HsManagedServerHostingAssetValidator extends HsHostingAssetEntityValidator {
public HsManagedServerHostingAssetValidator() {
super(

View File

@ -1,28 +1,29 @@
package net.hostsharing.hsadminng.hs.hosting.asset.validators;
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType;
import net.hostsharing.hsadminng.hs.validation.HsEntityValidator;
import java.util.Collection;
import java.util.stream.Stream;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
class HsManagedWebspaceHostingAssetValidator extends HsEntityValidator<HsHostingAssetEntity, HsHostingAssetType> {
class HsManagedWebspaceHostingAssetValidator extends HsHostingAssetEntityValidator {
public HsManagedWebspaceHostingAssetValidator() {
}
@Override
public List<String> validate(final HsHostingAssetEntity assetEntity) {
final var result = super.validate(assetEntity);
validateIdentifierPattern(result, assetEntity);
return result;
return Stream.of(validateIdentifierPattern(assetEntity), super.validate(assetEntity))
.flatMap(Collection::stream)
.collect(Collectors.toList());
}
private static void validateIdentifierPattern(final List<String> result, final HsHostingAssetEntity assetEntity) {
private static List<String> validateIdentifierPattern(final HsHostingAssetEntity assetEntity) {
final var expectedIdentifierPattern = "^" + assetEntity.getParentAsset().getBookingItem().getProject().getDebitor().getDefaultPrefix() + "[0-9][0-9]$";
if ( !assetEntity.getIdentifier().matches(expectedIdentifierPattern)) {
result.add("'identifier' expected to match '"+expectedIdentifierPattern+"', but is '" + assetEntity.getIdentifier() + "'");
return List.of("'identifier' expected to match '"+expectedIdentifierPattern+"', but is '" + assetEntity.getIdentifier() + "'");
}
return Collections.emptyList();
}
}

View File

@ -3,6 +3,7 @@ package net.hostsharing.hsadminng.hs.office.coopassets;
import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficeCoopAssetsApi;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.*;
import net.hostsharing.hsadminng.hs.validation.MultiValidationException;
import net.hostsharing.hsadminng.mapper.Mapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.format.annotation.DateTimeFormat;
@ -97,9 +98,7 @@ public class HsOfficeCoopAssetsTransactionController implements HsOfficeCoopAsse
validateDebitTransaction(requestBody, violations);
validateCreditTransaction(requestBody, violations);
validateAssetValue(requestBody, violations);
if (violations.size() > 0) {
throw new ValidationException("[" + join(", ", violations) + "]");
}
MultiValidationException.throwInvalid(violations);
}
private static void validateDebitTransaction(

View File

@ -5,6 +5,7 @@ import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficeCoopSharesApi;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeCoopSharesTransactionInsertResource;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeCoopSharesTransactionResource;
import net.hostsharing.hsadminng.hs.validation.MultiValidationException;
import net.hostsharing.hsadminng.mapper.Mapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.format.annotation.DateTimeFormat;
@ -99,9 +100,7 @@ public class HsOfficeCoopSharesTransactionController implements HsOfficeCoopShar
validateSubscriptionTransaction(requestBody, violations);
validateCancellationTransaction(requestBody, violations);
validateshareCount(requestBody, violations);
if (violations.size() > 0) {
throw new ValidationException("[" + join(", ", violations) + "]");
}
MultiValidationException.throwInvalid(violations);
}
private static void validateSubscriptionTransaction(

View File

@ -0,0 +1,46 @@
package net.hostsharing.hsadminng.hs.validation;
import lombok.Setter;
import net.hostsharing.hsadminng.mapper.Array;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Map;
import java.util.Objects;
@Setter
public class BooleanProperty extends ValidatableProperty<Boolean> {
private static final String[] KEY_ORDER = Array.join(ValidatableProperty.KEY_ORDER_HEAD, ValidatableProperty.KEY_ORDER_TAIL);
private Map.Entry<String, String> falseIf;
private BooleanProperty(final String propertyName) {
super(Boolean.class, propertyName, KEY_ORDER);
}
public static BooleanProperty booleanProperty(final String propertyName) {
return new BooleanProperty(propertyName);
}
public ValidatableProperty<Boolean> falseIf(final String refPropertyName, final String refPropertyValue) {
this.falseIf = new AbstractMap.SimpleImmutableEntry<>(refPropertyName, refPropertyValue);
return this;
}
@Override
protected void validate(final ArrayList<String> result, final Boolean propValue, final Map<String, Object> props) {
if (falseIf != null && propValue) {
final Object referencedValue = props.get(falseIf.getKey());
if (Objects.equals(referencedValue, falseIf.getValue())) {
result.add(propertyName + " is expected to be false because " +
falseIf.getKey() + "=" + referencedValue + " but is " + propValue);
}
}
}
@Override
protected String simpleTypeName() {
return "boolean";
}
}

View File

@ -1,42 +0,0 @@
package net.hostsharing.hsadminng.hs.validation;
import lombok.Setter;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Map;
import java.util.Objects;
@Setter
public class BooleanPropertyValidator extends HsPropertyValidator<Boolean> {
private Map.Entry<String, String> falseIf;
private BooleanPropertyValidator(final String propertyName) {
super(Boolean.class, propertyName);
}
public static BooleanPropertyValidator booleanProperty(final String propertyName) {
return new BooleanPropertyValidator(propertyName);
}
public HsPropertyValidator<Boolean> falseIf(final String refPropertyName, final String refPropertyValue) {
this.falseIf = new AbstractMap.SimpleImmutableEntry<>(refPropertyName, refPropertyValue);
return this;
}
@Override
protected void validate(final ArrayList<String> result, final String propertiesName, final Boolean propValue, final Map<String, Object> props) {
if (falseIf != null && !Objects.equals(props.get(falseIf.getKey()), falseIf.getValue())) {
if (propValue) {
result.add("'"+propertiesName+"." + propertyName + "' is expected to be false because " +
propertiesName+"." + falseIf.getKey()+ "=" + falseIf.getValue() + " but is " + propValue);
}
}
}
@Override
protected String simpleTypeName() {
return "boolean";
}
}

View File

@ -0,0 +1,44 @@
package net.hostsharing.hsadminng.hs.validation;
import lombok.Setter;
import net.hostsharing.hsadminng.mapper.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Map;
@Setter
public class EnumerationProperty extends ValidatableProperty<String> {
private static final String[] KEY_ORDER = Array.join(
ValidatableProperty.KEY_ORDER_HEAD,
Array.of("values"),
ValidatableProperty.KEY_ORDER_TAIL);
private String[] values;
private EnumerationProperty(final String propertyName) {
super(String.class, propertyName, KEY_ORDER);
}
public static EnumerationProperty enumerationProperty(final String propertyName) {
return new EnumerationProperty(propertyName);
}
public ValidatableProperty<String> values(final String... values) {
this.values = values;
return this;
}
@Override
protected void validate(final ArrayList<String> result, final String propValue, final Map<String, Object> props) {
if (Arrays.stream(values).noneMatch(v -> v.equals(propValue))) {
result.add(propertyName + " is expected to be one of " + Arrays.toString(values) + " but is '" + propValue + "'");
}
}
@Override
protected String simpleTypeName() {
return "enumeration";
}
}

View File

@ -1,38 +0,0 @@
package net.hostsharing.hsadminng.hs.validation;
import lombok.Setter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Map;
@Setter
public class EnumerationPropertyValidator extends HsPropertyValidator<String> {
private String[] values;
private EnumerationPropertyValidator(final String propertyName) {
super(String.class, propertyName);
}
public static EnumerationPropertyValidator enumerationProperty(final String propertyName) {
return new EnumerationPropertyValidator(propertyName);
}
public HsPropertyValidator<String> values(final String... values) {
this.values = values;
return this;
}
@Override
protected void validate(final ArrayList<String> result, final String propertiesName, final String propValue, final Map<String, Object> props) {
if (Arrays.stream(values).noneMatch(v -> v.equals(propValue))) {
result.add("'"+propertiesName+"." + propertyName + "' is expected to be one of " + Arrays.toString(values) + " but is '" + propValue + "'");
}
}
@Override
protected String simpleTypeName() {
return "enumeration";
}
}

View File

@ -1,49 +1,83 @@
package net.hostsharing.hsadminng.hs.validation;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
import net.hostsharing.hsadminng.hs.booking.item.validators.HsBookingItemEntityValidator;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
import static java.util.Arrays.stream;
import static java.util.Collections.emptyList;
public class HsEntityValidator<E extends Validatable<E, T>, T extends Enum<T>> {
public abstract class HsEntityValidator<E> {
public final HsPropertyValidator<?>[] propertyValidators;
public final ValidatableProperty<?>[] propertyValidators;
public HsEntityValidator(final HsPropertyValidator<?>... validators) {
public HsEntityValidator(final ValidatableProperty<?>... validators) {
propertyValidators = validators;
}
public List<String> validate(final E assetEntity) {
protected static List<String> enrich(final String prefix, final List<String> messages) {
return messages.stream()
.map(message -> prefix + "." + message)
.toList();
}
protected static String prefix(final String... parts) {
return String.join(".", parts);
}
public abstract List<String> validate(final E entity);
public final List<Map<String, Object>> properties() {
return Arrays.stream(propertyValidators)
.map(ValidatableProperty::toOrderedMap)
.toList();
}
protected ArrayList<String> validateProperties(final Map<String, Object> properties) {
final var result = new ArrayList<String>();
assetEntity.getProperties().keySet().forEach( givenPropName -> {
properties.keySet().forEach( givenPropName -> {
if (stream(propertyValidators).map(pv -> pv.propertyName).noneMatch(propName -> propName.equals(givenPropName))) {
result.add("'"+assetEntity.getPropertiesName()+"." + givenPropName + "' is not expected but is set to '" +assetEntity.getProperties().get(givenPropName) + "'");
result.add(givenPropName + " is not expected but is set to '" + properties.get(givenPropName) + "'");
}
});
stream(propertyValidators).forEach(pv -> {
result.addAll(pv.validate(assetEntity.getPropertiesName(), assetEntity.getProperties()));
result.addAll(pv.validate(properties));
});
return result;
}
public List<Map<String, Object>> properties() {
final var mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
return Arrays.stream(propertyValidators)
.map(propertyValidator -> propertyValidator.toMap(mapper))
.map(HsEntityValidator::asKeyValueMap)
.toList();
protected List<String> validateParentEntities(final HsBookingItemEntity bookingItem) {
return bookingItem.getParentItem() != null
? HsBookingItemEntityValidator.doValidate(bookingItem.getParentItem())
: emptyList();
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private static Map<String, Object> asKeyValueMap(final Map map) {
return (Map<String, Object>) map;
@SafeVarargs
protected static List<String> sequentiallyValidate(final Supplier<List<String>>... validators) {
return new ArrayList<>(stream(validators)
.map(Supplier::get)
.filter(violations -> !violations.isEmpty())
.findFirst()
.orElse(emptyList()));
}
protected static Integer getNonNullIntegerValue(final ValidatableProperty<?> prop, final Map<String, Object> propValues) {
final var value = prop.getValue(propValues);
if (value instanceof Integer) {
return (Integer) value;
}
throw new IllegalArgumentException(prop.propertyName + " Integer value expected, but got " + value);
}
protected static Integer toNonNullInteger(final Object value) {
if (value instanceof Integer) {
return (Integer) value;
}
throw new IllegalArgumentException("Integer value expected, but got " + value);
}
}

View File

@ -1,67 +0,0 @@
package net.hostsharing.hsadminng.hs.validation;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import java.util.AbstractMap.SimpleImmutableEntry;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@RequiredArgsConstructor
public abstract class HsPropertyValidator<T> {
final Class<T> type;
final String propertyName;
private Boolean required;
public static <K, V> Map.Entry<K, V> defType(K k, V v) {
return new SimpleImmutableEntry<>(k, v);
}
public HsPropertyValidator<T> required() {
required = Boolean.TRUE;
return this;
}
public HsPropertyValidator<T> optional() {
required = Boolean.FALSE;
return this;
}
public final List<String> validate(final String propertiesName, final Map<String, Object> props) {
final var result = new ArrayList<String>();
final var propValue = props.get(propertyName);
if (propValue == null) {
if (required) {
result.add("'"+propertiesName+"." + propertyName + "' is required but missing");
}
}
if (propValue != null){
if ( type.isInstance(propValue)) {
//noinspection unchecked
validate(result, propertiesName, (T) propValue, props);
} else {
result.add("'"+propertiesName+"." + propertyName + "' is expected to be of type " + type + ", " +
"but is of type '" + propValue.getClass().getSimpleName() + "'");
}
}
return result;
}
protected abstract void validate(final ArrayList<String> result, final String propertiesName, final T propValue, final Map<String, Object> props);
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()" );
}
}
public Map<String, Object> toMap(final ObjectMapper mapper) {
final Map<String, Object> map = mapper.convertValue(this, Map.class);
map.put("type", simpleTypeName());
return map;
}
protected abstract String simpleTypeName();
}

View File

@ -0,0 +1,56 @@
package net.hostsharing.hsadminng.hs.validation;
import lombok.Setter;
import net.hostsharing.hsadminng.mapper.Array;
import java.util.ArrayList;
import java.util.Map;
@Setter
public class IntegerProperty extends ValidatableProperty<Integer> {
private final static String[] KEY_ORDER = Array.join(
ValidatableProperty.KEY_ORDER_HEAD,
Array.of("unit", "min", "max", "step"),
ValidatableProperty.KEY_ORDER_TAIL);
private String unit;
private Integer min;
private Integer max;
private Integer step;
public static IntegerProperty integerProperty(final String propertyName) {
return new IntegerProperty(propertyName);
}
private IntegerProperty(final String propertyName) {
super(Integer.class, propertyName, KEY_ORDER);
}
@Override
public String unit() {
return unit;
}
public Integer max() {
return max;
}
@Override
protected void validate(final ArrayList<String> result, final Integer propValue, final Map<String, Object> 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);
}
if (step != null && propValue % step != 0) {
result.add(propertyName + " is expected to be multiple of " + step + " but is " + propValue);
}
}
@Override
protected String simpleTypeName() {
return "integer";
}
}

View File

@ -1,42 +0,0 @@
package net.hostsharing.hsadminng.hs.validation;
import lombok.Setter;
import java.util.ArrayList;
import java.util.Map;
@Setter
public class IntegerPropertyValidator extends HsPropertyValidator<Integer> {
private String unit;
private Integer min;
private Integer max;
private Integer step;
public static IntegerPropertyValidator integerProperty(final String propertyName) {
return new IntegerPropertyValidator(propertyName);
}
private IntegerPropertyValidator(final String propertyName) {
super(Integer.class, propertyName);
}
@Override
protected void validate(final ArrayList<String> result, final String propertiesName, final Integer propValue, final Map<String, Object> props) {
if (min != null && propValue < min) {
result.add("'"+propertiesName+"." + propertyName + "' is expected to be >= " + min + " but is " + propValue);
}
if (max != null && propValue > max) {
result.add("'"+propertiesName+"." + propertyName + "' is expected to be <= " + max + " but is " + propValue);
}
if (step != null && propValue % step != 0) {
result.add("'"+propertiesName+"." + propertyName + "' is expected to be multiple of " + step + " but is " + propValue);
}
}
@Override
protected String simpleTypeName() {
return "integer";
}
}

View File

@ -0,0 +1,19 @@
package net.hostsharing.hsadminng.hs.validation;
import jakarta.validation.ValidationException;
import java.util.List;
import static java.lang.String.join;
public class MultiValidationException extends ValidationException {
private MultiValidationException(final List<String> violations) {
super("[\n" + join(",\n", violations) + "\n]");
}
public static void throwInvalid(final List<String> violations) {
if (!violations.isEmpty()) {
throw new MultiValidationException(violations);
}
}
}

View File

@ -1,13 +0,0 @@
package net.hostsharing.hsadminng.hs.validation;
import java.util.Map;
public interface Validatable<E, T extends Enum<T>> {
Enum<T> getType();
String getPropertiesName();
Map<String, Object> getProperties();
}

View File

@ -0,0 +1,172 @@
package net.hostsharing.hsadminng.hs.validation;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
import net.hostsharing.hsadminng.mapper.Array;
import org.apache.commons.lang3.function.TriFunction;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import static java.lang.Boolean.FALSE;
import static java.lang.Boolean.TRUE;
import static java.util.Collections.emptyList;
@RequiredArgsConstructor
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", "isTotalsValidator", "thresholdPercentage");
final Class<T> type;
final String propertyName;
private final String[] keyOrder;
private Boolean required;
private T defaultValue;
private boolean isTotalsValidator = false;
@JsonIgnore
private List<Function<HsBookingItemEntity, List<String>>> asTotalLimitValidators; // TODO.impl: move to BookingItemIntegerProperty
private Integer thresholdPercentage; // TODO.impl: move to IntegerProperty
public String unit() {
return null;
}
public ValidatableProperty<T> required() {
required = TRUE;
return this;
}
public ValidatableProperty<T> optional() {
required = FALSE;
return this;
}
public ValidatableProperty<T> withDefault(final T value) {
defaultValue = value;
required = FALSE;
return this;
}
public ValidatableProperty<T> asTotalLimit() {
isTotalsValidator = true;
return this;
}
public String propertyName() {
return propertyName;
}
public boolean isTotalsValidator() {
return isTotalsValidator || asTotalLimitValidators != null;
}
public Integer thresholdPercentage() {
return thresholdPercentage;
}
public ValidatableProperty<T> eachComprising(final int factor, final TriFunction<HsBookingItemEntity, IntegerProperty, Integer, List<String>> validator) {
if (asTotalLimitValidators == null) {
asTotalLimitValidators = new ArrayList<>();
}
asTotalLimitValidators.add((final HsBookingItemEntity entity) -> validator.apply(entity, (IntegerProperty)this, factor));
return this;
}
public ValidatableProperty<?> withThreshold(final Integer percentage) {
this.thresholdPercentage = percentage;
return this;
}
public final List<String> validate(final Map<String, Object> props) {
final var result = new ArrayList<String>();
final var propValue = props.get(propertyName);
if (propValue == null) {
if (required) {
result.add(propertyName + " is required but missing");
}
}
if (propValue != null){
if ( type.isInstance(propValue)) {
//noinspection unchecked
validate(result, (T) propValue, props);
} else {
result.add(propertyName + " is expected to be of type " + type + ", " +
"but is of type '" + propValue.getClass().getSimpleName() + "'");
}
}
return result;
}
protected abstract void validate(final ArrayList<String> result, final T propValue, final Map<String, Object> props);
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()" );
}
}
@SuppressWarnings("unchecked")
public T getValue(final Map<String, Object> propValues) {
return (T) Optional.ofNullable(propValues.get(propertyName)).orElse(defaultValue);
}
protected abstract String simpleTypeName();
public Map<String, Object> toOrderedMap() {
Map<String, Object> sortedMap = new LinkedHashMap<>();
sortedMap.put("type", simpleTypeName());
// Add entries according to the given order
for (String key : keyOrder) {
final Optional<Object> propValue = getPropertyValue(key);
propValue.ifPresent(o -> sortedMap.put(key, o));
}
return sortedMap;
}
@SneakyThrows
private Optional<Object> getPropertyValue(final String key) {
try {
final var field = getClass().getDeclaredField(key);
field.setAccessible(true);
return Optional.ofNullable(arrayToList(field.get(this)));
} catch (final NoSuchFieldException e1) {
try {
final var field = getClass().getSuperclass().getDeclaredField(key);
field.setAccessible(true);
return Optional.ofNullable(arrayToList(field.get(this)));
} catch (final NoSuchFieldException e2) {
return Optional.empty();
}
}
}
private Object arrayToList(final Object value) {
if ( value instanceof String[]) {
return List.of((String[])value);
}
return value;
}
public List<String> validateTotals(final HsBookingItemEntity bookingItem) {
if (asTotalLimitValidators==null) {
return emptyList();
}
return asTotalLimitValidators.stream()
.map(v -> v.apply(bookingItem))
.filter(Objects::nonNull)
.flatMap(Collection::stream)
.toList();
}
}

View File

@ -1,4 +1,4 @@
package net.hostsharing.hsadminng.rbac.test;
package net.hostsharing.hsadminng.mapper;
import java.util.ArrayList;
import java.util.Arrays;
@ -37,4 +37,10 @@ public class Array {
return resultList.toArray(String[]::new);
}
public static String[] join(final String[]... parts) {
final String[] joined = Arrays.stream(parts)
.flatMap(Arrays::stream)
.toArray(String[]::new);
return joined;
}
}

View File

@ -62,6 +62,8 @@ public class RbacGrantsDiagramService {
@PersistenceContext
private EntityManager em;
private Map<UUID, List<RawRbacGrantEntity>> descendantsByUuid = new HashMap<>();
public String allGrantsToCurrentUser(final EnumSet<Include> includes) {
final var graph = new LimitedHashSet<RawRbacGrantEntity>();
for ( UUID subjectUuid: context.currentSubjectsUuids() ) {
@ -102,7 +104,7 @@ public class RbacGrantsDiagramService {
}
private void traverseGrantsFrom(final Set<RawRbacGrantEntity> graph, final UUID refUuid, final EnumSet<Include> option) {
final var grants = rawGrantRepo.findByDescendantUuid(refUuid);
final var grants = findDescendantsByUuid(refUuid);
grants.forEach(g -> {
if (!option.contains(USERS) && g.getAscendantIdName().startsWith("user:")) {
return;
@ -114,6 +116,11 @@ public class RbacGrantsDiagramService {
});
}
private List<RawRbacGrantEntity> findDescendantsByUuid(final UUID refUuid) {
// TODO.impl: if that UUID already got processed, do we need to return anything at all?
return descendantsByUuid.computeIfAbsent(refUuid, uuid -> rawGrantRepo.findByDescendantUuid(uuid));
}
private String toMermaidFlowchart(final HashSet<RawRbacGrantEntity> graph, final EnumSet<Include> includes) {
final var entities =
includes.contains(DETAILS)

View File

@ -33,13 +33,13 @@ begin
managedServerUuid := uuid_generate_v4();
insert
into hs_booking_item (uuid, projectuuid, type, parentitemuuid, caption, validity, resources)
values (privateCloudUuid, relatedProject.uuid, 'PRIVATE_CLOUD', null, 'some PrivateCloud', daterange('20240401', null, '[]'), '{ "CPUs": 10, "SDD": 10240, "HDD": 10240, "Traffic": 42 }'::jsonb),
(uuid_generate_v4(), null, 'MANAGED_SERVER', privateCloudUuid, 'some ManagedServer', daterange('20230115', '20240415', '[)'), '{ "CPUs": 2, "RAM": 4, "HDD": 1024, "Traffic": 42 }'::jsonb),
(uuid_generate_v4(), null, 'CLOUD_SERVER', privateCloudUuid, 'test CloudServer', daterange('20230115', '20240415', '[)'), '{ "CPUs": 2, "RAM": 4, "HDD": 1024, "Traffic": 42 }'::jsonb),
(uuid_generate_v4(), null, 'CLOUD_SERVER', privateCloudUuid, 'prod CloudServer', daterange('20230115', '20240415', '[)'), '{ "CPUs": 4, "RAM": 16, "HDD": 2924, "Traffic": 420 }'::jsonb),
(managedServerUuid, relatedProject.uuid, 'MANAGED_SERVER', null, 'separate ManagedServer', daterange('20221001', null, '[]'), '{ "CPUs": 2, "RAM": 8, "SDD": 512, "Traffic": 42 }'::jsonb),
(uuid_generate_v4(), null, 'MANAGED_WEBSPACE', managedServerUuid, 'some ManagedWebspace', daterange('20221001', null, '[]'), '{ "SDD": 512, "Traffic": 12, "Daemons": 2, "Multi": 4 }'::jsonb),
(uuid_generate_v4(), relatedProject.uuid, 'MANAGED_WEBSPACE', null, 'some ManagedWebspace', daterange('20221001', null, '[]'), '{ "SDD": 512, "Traffic": 12, "Daemons": 2, "Multi": 4 }'::jsonb);
values (privateCloudUuid, relatedProject.uuid, 'PRIVATE_CLOUD', null, 'some PrivateCloud', daterange('20240401', null, '[]'), '{ "CPUs": 10, "RAM": 32, "SSD": 4000, "HDD": 10000, "Traffic": 2000 }'::jsonb),
(uuid_generate_v4(), null, 'MANAGED_SERVER', privateCloudUuid, 'some ManagedServer', daterange('20230115', '20240415', '[)'), '{ "CPUs": 2, "RAM": 4, "SSD": 500, "Traffic": 500 }'::jsonb),
(uuid_generate_v4(), null, 'CLOUD_SERVER', privateCloudUuid, 'test CloudServer', daterange('20230115', '20240415', '[)'), '{ "CPUs": 2, "RAM": 4, "SSD": 750, "Traffic": 500 }'::jsonb),
(uuid_generate_v4(), null, 'CLOUD_SERVER', privateCloudUuid, 'prod CloudServer', daterange('20230115', '20240415', '[)'), '{ "CPUs": 4, "RAM": 16, "SSD": 1000, "Traffic": 500 }'::jsonb),
(managedServerUuid, relatedProject.uuid, 'MANAGED_SERVER', null, 'separate ManagedServer', daterange('20221001', null, '[]'), '{ "CPUs": 2, "RAM": 8, "SSD": 500, "Traffic": 500 }'::jsonb),
(uuid_generate_v4(), null, 'MANAGED_WEBSPACE', managedServerUuid, 'some ManagedWebspace', daterange('20221001', null, '[]'), '{ "SSD": 50, "Traffic": 20, "Daemons": 2, "Multi": 4 }'::jsonb),
(uuid_generate_v4(), relatedProject.uuid, 'MANAGED_WEBSPACE', null, 'separate ManagedWebspace', daterange('20221001', null, '[]'), '{ "SSD": 100, "Traffic": 50, "Daemons": 0, "Multi": 1 }'::jsonb);
end; $$;
--//

View File

@ -75,10 +75,10 @@ begin
end);
if expectedParentType is not null and actualParentType is null then
raise exception '[400] % must have % as parent, but got <NULL>',
raise exception '[400] HostingAsset % must have % as parent, but got <NULL>',
NEW.type, expectedParentType;
elsif expectedParentType is not null and actualParentType <> expectedParentType then
raise exception '[400] % must have % as parent, but got %s',
raise exception '[400] HostingAsset % must have % as parent, but got %s',
NEW.type, expectedParentType, actualParentType;
end if;
return NEW;
@ -100,27 +100,23 @@ create or replace function hs_hosting_asset_booking_item_hierarchy_check_tf()
language plpgsql as $$
declare
actualBookingItemType HsBookingItemType;
expectedBookingItemTypes HsBookingItemType[];
expectedBookingItemType HsBookingItemType;
begin
actualBookingItemType := (select type
from hs_booking_item
where NEW.bookingItemUuid = uuid);
if NEW.type = 'CLOUD_SERVER' then
expectedBookingItemTypes := ARRAY['PRIVATE_CLOUD', 'CLOUD_SERVER'];
expectedBookingItemType := 'CLOUD_SERVER';
elsif NEW.type = 'MANAGED_SERVER' then
expectedBookingItemTypes := ARRAY['PRIVATE_CLOUD', 'MANAGED_SERVER'];
expectedBookingItemType := 'MANAGED_SERVER';
elsif NEW.type = 'MANAGED_WEBSPACE' then
if NEW.parentAssetUuid is null then
expectedBookingItemTypes := ARRAY['MANAGED_WEBSPACE'];
else
expectedBookingItemTypes := ARRAY['PRIVATE_CLOUD', 'MANAGED_SERVER'];
end if;
expectedBookingItemType := 'MANAGED_WEBSPACE';
end if;
if not actualBookingItemType = any(expectedBookingItemTypes) then
raise exception '[400] % % must have any of % as booking-item, but got %',
NEW.type, NEW.identifier, expectedBookingItemTypes, actualBookingItemType;
if not actualBookingItemType = expectedBookingItemType then
raise exception '[400] HostingAsset % % must have % as booking-item, but got %',
NEW.type, NEW.identifier, expectedBookingItemType, actualBookingItemType;
end if;
return NEW;
end; $$;

View File

@ -16,6 +16,8 @@ declare
relatedDebitor hs_office_debitor;
relatedPrivateCloudBookingItem hs_booking_item;
relatedManagedServerBookingItem hs_booking_item;
relatedCloudServerBookingItem hs_booking_item;
relatedManagedWebspaceBookingItem hs_booking_item;
debitorNumberSuffix varchar;
defaultPrefix varchar;
managedServerUuid uuid;
@ -48,6 +50,18 @@ begin
and item.type = 'MANAGED_SERVER';
assert relatedManagedServerBookingItem.uuid is not null, 'relatedManagedServerBookingItem for "' || givenProjectCaption|| '" must not be null';
select item.* into relatedCloudServerBookingItem
from hs_booking_item item
where item.parentItemuuid = relatedPrivateCloudBookingItem.uuid
and item.type = 'CLOUD_SERVER';
assert relatedCloudServerBookingItem.uuid is not null, 'relatedCloudServerBookingItem for "' || givenProjectCaption|| '" must not be null';
select item.* into relatedManagedWebspaceBookingItem
from hs_booking_item item
where item.projectUuid = relatedProject.uuid
and item.type = 'MANAGED_WEBSPACE';
assert relatedManagedWebspaceBookingItem.uuid is not null, 'relatedManagedWebspaceBookingItem for "' || givenProjectCaption|| '" must not be null';
select uuid_generate_v4() into managedServerUuid;
select uuid_generate_v4() into managedWebspaceUuid;
select uuid_generate_v4() into webUnixUserUuid;
@ -56,11 +70,11 @@ begin
insert into hs_hosting_asset
(uuid, bookingitemuuid, type, parentAssetUuid, assignedToAssetUuid, identifier, caption, config)
values (managedServerUuid, relatedPrivateCloudBookingItem.uuid, 'MANAGED_SERVER', null, null, 'vm10' || debitorNumberSuffix, 'some ManagedServer', '{ "extra": 42 }'::jsonb),
(uuid_generate_v4(), relatedPrivateCloudBookingItem.uuid, 'CLOUD_SERVER', null, null, 'vm20' || debitorNumberSuffix, 'another CloudServer', '{ "extra": 42 }'::jsonb),
(managedWebspaceUuid, relatedManagedServerBookingItem.uuid, 'MANAGED_WEBSPACE', managedServerUuid, null, defaultPrefix || '01', 'some Webspace', '{ "extra": 42 }'::jsonb),
(webUnixUserUuid, null, 'UNIX_USER', managedWebspaceUuid, null, defaultPrefix || '01-web', 'some UnixUser for Website', '{ "SSD-soft-quota": "128", "SSD-hard-quota": "256", "HDD-soft-quota": "512", "HDD-hard-quota": "1024", "extra": 42 }'::jsonb),
(uuid_generate_v4(), null, 'DOMAIN_HTTP_SETUP', managedWebspaceUuid, webUnixUserUuid, defaultPrefix || '.example.org', 'some Domain-HTTP-Setup', '{ "option-htdocsfallback": true, "use-fcgiphpbin": "/usr/lib/cgi-bin/php", "validsubdomainnames": "*", "extra": 42 }'::jsonb);
values (managedServerUuid, relatedManagedServerBookingItem.uuid, 'MANAGED_SERVER', null, null, 'vm10' || debitorNumberSuffix, 'some ManagedServer', '{ "monit_max_cpu_usage": 90, "monit_max_ram_usage": 80, "monit_max_ssd_usage": 70 }'::jsonb),
(uuid_generate_v4(), relatedCloudServerBookingItem.uuid, 'CLOUD_SERVER', null, null, 'vm20' || debitorNumberSuffix, 'another CloudServer', '{}'::jsonb),
(managedWebspaceUuid, relatedManagedWebspaceBookingItem.uuid, 'MANAGED_WEBSPACE', managedServerUuid, null, defaultPrefix || '01', 'some Webspace', '{}'::jsonb),
(webUnixUserUuid, null, 'UNIX_USER', managedWebspaceUuid, null, defaultPrefix || '01-web', 'some UnixUser for Website', '{ "SSD-soft-quota": "128", "SSD-hard-quota": "256", "HDD-soft-quota": "512", "HDD-hard-quota": "1024"}'::jsonb),
(uuid_generate_v4(), null, 'DOMAIN_HTTP_SETUP', managedWebspaceUuid, webUnixUserUuid, defaultPrefix || '.example.org', 'some Domain-HTTP-Setup', '{ "option-htdocsfallback": true, "use-fcgiphpbin": "/usr/lib/cgi-bin/php", "validsubdomainnames": "*"}'::jsonb);
end; $$;
--//

View File

@ -77,14 +77,14 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
[
{
"type": "MANAGED_WEBSPACE",
"caption": "some ManagedWebspace",
"caption": "separate ManagedWebspace",
"validFrom": "2022-10-01",
"validTo": null,
"resources": {
"SDD": 512,
"Multi": 4,
"Daemons": 2,
"Traffic": 12
"SSD": 100,
"Multi": 1,
"Daemons": 0,
"Traffic": 50
}
},
{
@ -94,9 +94,9 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
"validTo": null,
"resources": {
"RAM": 8,
"SDD": 512,
"SSD": 500,
"CPUs": 2,
"Traffic": 42
"Traffic": 500
}
},
{
@ -105,10 +105,11 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
"validFrom": "2024-04-01",
"validTo": null,
"resources": {
"HDD": 10240,
"SDD": 10240,
"HDD": 10000,
"RAM": 32,
"SSD": 4000,
"CPUs": 10,
"Traffic": 42
"Traffic": 2000
}
}
]
@ -174,7 +175,7 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
@Test
void globalAdmin_canGetArbitraryBookingItem() {
context.define("superuser-alex@hostsharing.net");
final var givenBookingItemUuid = bookingItemRepo.findByCaption("some ManagedWebspace").stream()
final var givenBookingItemUuid = bookingItemRepo.findByCaption("separate ManagedWebspace").stream()
.filter(bi -> belongsToDebitorWithDefaultPrefix(bi, "fir"))
.map(HsBookingItemEntity::getUuid)
.findAny().orElseThrow();
@ -191,14 +192,14 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
.body("", lenientlyEquals("""
{
"type": "MANAGED_WEBSPACE",
"caption": "some ManagedWebspace",
"caption": "separate ManagedWebspace",
"validFrom": "2022-10-01",
"validTo": null,
"resources": {
"SDD": 512,
"Multi": 4,
"Daemons": 2,
"Traffic": 12
"SSD": 100,
"Multi": 1,
"Daemons": 0,
"Traffic": 50
}
}
""")); // @formatter:on
@ -227,14 +228,16 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
void projectAdmin_canGetRelatedBookingItem() {
context.define("superuser-alex@hostsharing.net");
final var givenBookingItemUuid = bookingItemRepo.findByCaption("separate ManagedServer").stream()
.filter(bi -> belongsToDebitorWithDefaultPrefix(bi, "thi"))
.filter(bi -> belongsToDebitorWithDefaultPrefix(bi, "sec"))
.map(HsBookingItemEntity::getUuid)
.findAny().orElseThrow();
generateRbacDiagramForObjectPermission(givenBookingItemUuid, "SELECT", "select");
RestAssured // @formatter:off
.given()
.header("current-user", "superuser-alex@hostsharing.net")
.header("assumed-roles", "hs_booking_project#D-1000313-D-1000313defaultproject:ADMIN")
.header("assumed-roles", "hs_booking_project#D-1000212-D-1000212defaultproject:ADMIN")
.port(port)
.when()
.get("http://localhost/api/hs/booking/items/" + givenBookingItemUuid)
@ -249,9 +252,9 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
"validTo": null,
"resources": {
"RAM": 8,
"SDD": 512,
"SSD": 500,
"CPUs": 2,
"Traffic": 42
"Traffic": 500
}
}
""")); // @formatter:on
@ -261,7 +264,7 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
return ofNullable(bi)
.map(HsBookingItemEntity::getProject)
.map(HsBookingProjectEntity::getDebitor)
.map(bd -> bd.getDefaultPrefix().equals(defaultPrefix))
.filter(bd -> bd.getDefaultPrefix().equals(defaultPrefix))
.isPresent();
}
}

View File

@ -44,11 +44,11 @@ class HsBookingItemEntityPatcherUnitTest extends PatchUnitTestBase<
private static final Map<String, Object> PATCH_RESOURCES = patchMap(
entry("CPU", 2),
entry("HDD", null),
entry("SDD", 256)
entry("SSD", 256)
);
private static final Map<String, Object> PATCHED_RESOURCES = patchMap(
entry("CPU", 2),
entry("SDD", 256),
entry("SSD", 256),
entry("MEM", 64)
);

View File

@ -6,7 +6,7 @@ import net.hostsharing.hsadminng.hs.booking.project.HsBookingProjectRepository;
import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorRepository;
import net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantRepository;
import net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleRepository;
import net.hostsharing.hsadminng.rbac.test.Array;
import net.hostsharing.hsadminng.mapper.Array;
import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup;
import net.hostsharing.hsadminng.rbac.test.JpaAttempt;
import org.junit.jupiter.api.Nested;
@ -30,7 +30,7 @@ import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.MANAGE
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.MANAGED_WEBSPACE;
import static net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantEntity.distinctGrantDisplaysOf;
import static net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleEntity.distinctRoleNamesOf;
import static net.hostsharing.hsadminng.rbac.test.Array.fromFormatted;
import static net.hostsharing.hsadminng.mapper.Array.fromFormatted;
import static net.hostsharing.hsadminng.rbac.test.JpaAttempt.attempt;
import static org.assertj.core.api.Assertions.assertThat;
@ -174,9 +174,9 @@ class HsBookingItemRepositoryIntegrationTest extends ContextBasedTestWithCleanup
// then
allTheseBookingItemsAreReturned(
result,
"HsBookingItemEntity(D-1000212:D-1000212 default project, MANAGED_WEBSPACE, [2022-10-01,), some ManagedWebspace, { Daemons: 2, Multi: 4, SDD: 512, Traffic: 12 })",
"HsBookingItemEntity(D-1000212:D-1000212 default project, MANAGED_SERVER, [2022-10-01,), separate ManagedServer, { CPUs: 2, RAM: 8, SDD: 512, Traffic: 42 })",
"HsBookingItemEntity(D-1000212:D-1000212 default project, PRIVATE_CLOUD, [2024-04-01,), some PrivateCloud, { CPUs: 10, HDD: 10240, SDD: 10240, Traffic: 42 })");
"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 })");
}
@Test
@ -194,9 +194,9 @@ class HsBookingItemRepositoryIntegrationTest extends ContextBasedTestWithCleanup
// then:
exactlyTheseBookingItemsAreReturned(
result,
"HsBookingItemEntity(D-1000111:D-1000111 default project, MANAGED_SERVER, [2022-10-01,), separate ManagedServer, { CPUs: 2, RAM: 8, SDD: 512, Traffic: 42 })",
"HsBookingItemEntity(D-1000111:D-1000111 default project, MANAGED_WEBSPACE, [2022-10-01,), some ManagedWebspace, { Daemons: 2, Multi: 4, SDD: 512, Traffic: 12 })",
"HsBookingItemEntity(D-1000111:D-1000111 default project, PRIVATE_CLOUD, [2024-04-01,), some PrivateCloud, { CPUs: 10, HDD: 10240, SDD: 10240, Traffic: 42 })");
"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, MANAGED_WEBSPACE, [2022-10-01,), separate ManagedWebspace, { Daemons: 0, Multi: 1, SSD: 100, Traffic: 50 })",
"HsBookingItemEntity(D-1000111:D-1000111 default project, PRIVATE_CLOUD, [2024-04-01,), some PrivateCloud, { CPUs: 10, HDD: 10000, RAM: 32, SSD: 4000, Traffic: 2000 })");
}
}

View File

@ -0,0 +1,56 @@
package net.hostsharing.hsadminng.hs.booking.item.validators;
import net.hostsharing.hsadminng.hs.booking.debitor.HsBookingDebitorEntity;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
import net.hostsharing.hsadminng.hs.booking.project.HsBookingProjectEntity;
import org.junit.jupiter.api.Test;
import jakarta.validation.ValidationException;
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.PRIVATE_CLOUD;
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.CLOUD_SERVER;
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.MANAGED_SERVER;
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.MANAGED_WEBSPACE;
import static net.hostsharing.hsadminng.hs.booking.item.validators.HsBookingItemEntityValidator.validated;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.catchThrowable;
class HsBookingItemEntityValidatorUnitTest {
final HsBookingDebitorEntity debitor = HsBookingDebitorEntity.builder()
.debitorNumber(12345)
.build();
final HsBookingProjectEntity project = HsBookingProjectEntity.builder()
.debitor(debitor)
.caption("test project")
.build();
@Test
void validThrowsException() {
// given
final var cloudServerBookingItemEntity = HsBookingItemEntity.builder()
.type(CLOUD_SERVER)
.project(project)
.caption("Test-Server")
.build();
// when
final var result = catchThrowable( ()-> validated(cloudServerBookingItemEntity) );
// 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.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");
}
@Test
void listsTypes() {
// when
final var result = HsBookingItemEntityValidator.types();
// then
assertThat(result).containsExactlyInAnyOrder(PRIVATE_CLOUD, CLOUD_SERVER, MANAGED_SERVER, MANAGED_WEBSPACE);
}
}

View File

@ -1,44 +0,0 @@
package net.hostsharing.hsadminng.hs.booking.item.validators;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
import org.junit.jupiter.api.Test;
import jakarta.validation.ValidationException;
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.MANAGED_SERVER;
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.MANAGED_WEBSPACE;
import static net.hostsharing.hsadminng.hs.booking.item.validators.HsBookingItemEntityValidators.valid;
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.CLOUD_SERVER;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.catchThrowable;
class HsBookingItemEntityValidatorsUnitTest {
@Test
void validThrowsException() {
// given
final var cloudServerBookingItemEntity = HsBookingItemEntity.builder()
.type(CLOUD_SERVER)
.build();
// when
final var result = catchThrowable( ()-> valid(cloudServerBookingItemEntity) );
// then
assertThat(result).isInstanceOf(ValidationException.class)
.hasMessageContaining(
"'resources.CPUs' is required but missing",
"'resources.RAM' is required but missing",
"'resources.SSD' is required but missing",
"'resources.Traffic' is required but missing");
}
@Test
void listsTypes() {
// when
final var result = HsBookingItemEntityValidators.types();
// then
assertThat(result).containsExactlyInAnyOrder(CLOUD_SERVER, MANAGED_SERVER, MANAGED_WEBSPACE);
}
}

View File

@ -1,23 +1,37 @@
package net.hostsharing.hsadminng.hs.booking.item.validators;
import net.hostsharing.hsadminng.hs.booking.debitor.HsBookingDebitorEntity;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
import net.hostsharing.hsadminng.hs.booking.project.HsBookingProjectEntity;
import org.junit.jupiter.api.Test;
import java.util.Map;
import static java.util.List.of;
import static java.util.Map.entry;
import static java.util.Map.ofEntries;
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.CLOUD_SERVER;
import static net.hostsharing.hsadminng.hs.booking.item.validators.HsBookingItemEntityValidators.forType;
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.MANAGED_SERVER;
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.PRIVATE_CLOUD;
import static org.assertj.core.api.Assertions.assertThat;
class HsCloudServerBookingItemValidatorUnitTest {
final HsBookingDebitorEntity debitor = HsBookingDebitorEntity.builder()
.debitorNumber(12345)
.build();
final HsBookingProjectEntity project = HsBookingProjectEntity.builder()
.debitor(debitor)
.caption("Test-Project")
.build();
@Test
void validatesProperties() {
// given
final var validator = HsBookingItemEntityValidators.forType(CLOUD_SERVER);
final var cloudServerBookingItemEntity = HsBookingItemEntity.builder()
.type(CLOUD_SERVER)
.project(project)
.caption("Test-Server")
.resources(Map.ofEntries(
entry("CPUs", 2),
entry("RAM", 25),
@ -28,24 +42,77 @@ class HsCloudServerBookingItemValidatorUnitTest {
.build();
// when
final var result = validator.validate(cloudServerBookingItemEntity);
final var result = HsBookingItemEntityValidator.doValidate(cloudServerBookingItemEntity);
// then
assertThat(result).containsExactly("'resources.SLA-EMail' is not expected but is set to 'true'");
assertThat(result).containsExactly("D-12345:Test-Project:Test-Server.resources.SLA-EMail is not expected but is set to 'true'");
}
@Test
void containsAllValidations() {
// when
final var validator = forType(CLOUD_SERVER);
final var validator = HsBookingItemEntityValidatorRegistry.forType(CLOUD_SERVER);
// then
assertThat(validator.properties()).map(Map::toString).containsExactlyInAnyOrder(
"{type=integer, propertyName=CPUs, required=true, unit=null, min=1, max=32, step=null}",
"{type=integer, propertyName=RAM, required=true, unit=GB, min=1, max=128, step=null}",
"{type=integer, propertyName=SSD, required=true, unit=GB, min=25, max=1000, step=25}",
"{type=integer, propertyName=HDD, required=false, unit=GB, min=0, max=4000, step=250}",
"{type=integer, propertyName=Traffic, required=true, unit=GB, min=250, max=10000, step=250}",
"{type=enumeration, propertyName=SLA-Infrastructure, required=false, values=[BASIC, EXT8H, EXT4H, EXT2H]}");
"{type=integer, propertyName=CPUs, min=1, max=32, required=true, isTotalsValidator=false}",
"{type=integer, propertyName=RAM, unit=GB, min=1, max=128, required=true, isTotalsValidator=false}",
"{type=integer, propertyName=SSD, unit=GB, min=25, max=1000, step=25, required=true, isTotalsValidator=false}",
"{type=integer, propertyName=HDD, unit=GB, min=0, max=4000, step=250, required=false, defaultValue=0, isTotalsValidator=false}",
"{type=integer, propertyName=Traffic, unit=GB, min=250, max=10000, step=250, required=true, isTotalsValidator=false}",
"{type=enumeration, propertyName=SLA-Infrastructure, values=[BASIC, EXT8H, EXT4H, EXT2H], required=false, isTotalsValidator=false}");
}
@Test
void validatesExceedingPropertyTotals() {
// given
final var subCloudServerBookingItemEntity = HsBookingItemEntity.builder()
.type(CLOUD_SERVER)
.caption("Test Cloud-Server")
.resources(ofEntries(
entry("CPUs", 2),
entry("RAM", 10),
entry("SSD", 50),
entry("Traffic", 2500)
))
.build();
final HsBookingItemEntity subManagedServerBookingItemEntity = HsBookingItemEntity.builder()
.type(MANAGED_SERVER)
.caption("Test Managed-Server")
.resources(ofEntries(
entry("CPUs", 3),
entry("RAM", 20),
entry("SSD", 100),
entry("Traffic", 3000)
))
.build();
final var privateCloudBookingItemEntity = HsBookingItemEntity.builder()
.type(PRIVATE_CLOUD)
.project(project)
.caption("Test Cloud")
.resources(ofEntries(
entry("CPUs", 4),
entry("RAM", 20),
entry("SSD", 100),
entry("Traffic", 5000)
))
.subBookingItems(of(
subManagedServerBookingItemEntity,
subCloudServerBookingItemEntity
))
.build();
subManagedServerBookingItemEntity.setParentItem(privateCloudBookingItemEntity);
subCloudServerBookingItemEntity.setParentItem(privateCloudBookingItemEntity);
// when
final var result = HsBookingItemEntityValidator.doValidate(subCloudServerBookingItemEntity);
// then
assertThat(result).containsExactlyInAnyOrder(
"D-12345:Test-Project:Test Cloud-Server.parentItem.total CPUs is 5 exceeds max total CPUs 4",
"D-12345:Test-Project:Test Cloud-Server.parentItem.total RAM is 30 GB exceeds max total RAM 20 GB",
"D-12345:Test-Project:Test Cloud-Server.parentItem.total SSD is 150 GB exceeds max total SSD 100 GB",
"D-12345:Test-Project:Test Cloud-Server.parentItem.total Traffic is 5500 GB exceeds max total Traffic 5000 GB"
);
}
}

View File

@ -1,56 +1,227 @@
package net.hostsharing.hsadminng.hs.booking.item.validators;
import net.hostsharing.hsadminng.hs.booking.debitor.HsBookingDebitorEntity;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
import net.hostsharing.hsadminng.hs.booking.project.HsBookingProjectEntity;
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType;
import org.junit.jupiter.api.Test;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import static java.util.Arrays.stream;
import static java.util.List.of;
import static java.util.Map.entry;
import static java.util.Map.ofEntries;
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.CLOUD_SERVER;
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.MANAGED_SERVER;
import static net.hostsharing.hsadminng.hs.booking.item.validators.HsBookingItemEntityValidators.forType;
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.MANAGED_WEBSPACE;
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.PRIVATE_CLOUD;
import static org.assertj.core.api.Assertions.assertThat;
class HsManagedServerBookingItemValidatorUnitTest {
final HsBookingDebitorEntity debitor = HsBookingDebitorEntity.builder()
.debitorNumber(12345)
.build();
final HsBookingProjectEntity project = HsBookingProjectEntity.builder()
.debitor(debitor)
.caption("Test-Project")
.build();
@Test
void validatesProperties() {
// given
final var validator = HsBookingItemEntityValidators.forType(MANAGED_SERVER);
final var mangedServerBookingItemEntity = HsBookingItemEntity.builder()
.type(MANAGED_SERVER)
.project(project)
.resources(Map.ofEntries(
entry("CPUs", 2),
entry("RAM", 25),
entry("SSD", 25),
entry("Traffic", 250),
entry("SLA-Platform", "BASIC"),
entry("SLA-EMail", true)
))
.build();
// when
final var result = validator.validate(mangedServerBookingItemEntity);
final var result = HsBookingItemEntityValidator.doValidate(mangedServerBookingItemEntity);
// then
assertThat(result).containsExactly("'resources.SLA-EMail' is expected to be false because resources.SLA-Platform=BASIC but is true");
assertThat(result).containsExactly("D-12345:Test-Project:null.resources.SLA-EMail is expected to be false because SLA-Platform=BASIC but is true");
}
@Test
void containsAllValidations() {
// when
final var validator = forType(MANAGED_SERVER);
final var validator = HsBookingItemEntityValidatorRegistry.forType(MANAGED_SERVER);
// then
assertThat(validator.properties()).map(Map::toString).containsExactlyInAnyOrder(
"{type=integer, propertyName=CPUs, required=true, unit=null, min=1, max=32, step=null}",
"{type=integer, propertyName=RAM, required=true, unit=GB, min=1, max=128, step=null}",
"{type=integer, propertyName=SSD, required=true, unit=GB, min=25, max=1000, step=25}",
"{type=integer, propertyName=HDD, required=false, unit=GB, min=0, max=4000, step=250}",
"{type=integer, propertyName=Traffic, required=true, unit=GB, min=250, max=10000, step=250}",
"{type=enumeration, propertyName=SLA-Platform, required=false, values=[BASIC, EXT8H, EXT4H, EXT2H]}",
"{type=boolean, propertyName=SLA-EMail, required=false, falseIf={SLA-Platform=BASIC}}",
"{type=boolean, propertyName=SLA-Maria, required=false, falseIf={SLA-Platform=BASIC}}",
"{type=boolean, propertyName=SLA-PgSQL, required=false, falseIf={SLA-Platform=BASIC}}",
"{type=boolean, propertyName=SLA-Office, required=false, falseIf={SLA-Platform=BASIC}}",
"{type=boolean, propertyName=SLA-Web, required=false, falseIf={SLA-Platform=BASIC}}");
"{type=integer, propertyName=CPUs, min=1, max=32, required=true, isTotalsValidator=false}",
"{type=integer, propertyName=RAM, unit=GB, min=1, max=128, required=true, isTotalsValidator=false}",
"{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, required=false, 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=enumeration, propertyName=SLA-Platform, values=[BASIC, EXT8H, EXT4H, EXT2H], required=false, defaultValue=BASIC, isTotalsValidator=false}",
"{type=boolean, propertyName=SLA-EMail, required=false, defaultValue=false, isTotalsValidator=false}",
"{type=boolean, propertyName=SLA-Maria, required=false, isTotalsValidator=false}",
"{type=boolean, propertyName=SLA-PgSQL, required=false, isTotalsValidator=false}",
"{type=boolean, propertyName=SLA-Office, required=false, isTotalsValidator=false}",
"{type=boolean, propertyName=SLA-Web, required=false, isTotalsValidator=false}");
}
@Test
void validatesExceedingPropertyTotals() {
// given
final var subCloudServerBookingItemEntity = HsBookingItemEntity.builder()
.type(CLOUD_SERVER)
.resources(ofEntries(
entry("CPUs", 2),
entry("RAM", 10),
entry("SSD", 50),
entry("Traffic", 2500)
))
.build();
final HsBookingItemEntity subManagedServerBookingItemEntity = HsBookingItemEntity.builder()
.type(MANAGED_SERVER)
.resources(ofEntries(
entry("CPUs", 3),
entry("RAM", 20),
entry("SSD", 100),
entry("Traffic", 3000)
))
.build();
final var privateCloudBookingItemEntity = HsBookingItemEntity.builder()
.type(PRIVATE_CLOUD)
.project(project)
.resources(ofEntries(
entry("CPUs", 4),
entry("RAM", 20),
entry("SSD", 100),
entry("Traffic", 5000)
))
.subBookingItems(of(
subManagedServerBookingItemEntity,
subCloudServerBookingItemEntity
))
.build();
subManagedServerBookingItemEntity.setParentItem(privateCloudBookingItemEntity);
subCloudServerBookingItemEntity.setParentItem(privateCloudBookingItemEntity);
// when
final var result = HsBookingItemEntityValidator.doValidate(subManagedServerBookingItemEntity);
// then
assertThat(result).containsExactlyInAnyOrder(
"D-12345:Test-Project:null.parentItem.total CPUs is 5 exceeds max total CPUs 4",
"D-12345:Test-Project:null.parentItem.total RAM is 30 GB exceeds max total RAM 20 GB",
"D-12345:Test-Project:null.parentItem.total SSD is 150 GB exceeds max total SSD 100 GB",
"D-12345:Test-Project:null.parentItem.total Traffic is 5500 GB exceeds max total Traffic 5000 GB"
);
}
@Test
void validatesExceedingTotals() {
// given
final var managedWebspaceBookingItem = HsBookingItemEntity.builder()
.type(MANAGED_WEBSPACE)
.caption("test Managed-Webspace")
.resources(ofEntries(
entry("SSD", 100),
entry("Traffic", 1000),
entry("Multi", 1)
))
.subHostingAssets(of(
HsHostingAssetEntity.builder()
.type(HsHostingAssetType.MANAGED_WEBSPACE)
.identifier("abc00")
.subHostingAssets(concat(
generate(26, HsHostingAssetType.UNIX_USER, "xyz00-%c%c"),
generateDbUsersWithDatabases(3, HsHostingAssetType.PGSQL_USER,
"xyz00_%c%c",
1, HsHostingAssetType.PGSQL_DATABASE
),
generateDbUsersWithDatabases(3, HsHostingAssetType.MARIADB_USER,
"xyz00_%c%c",
2, HsHostingAssetType.MARIADB_DATABASE
),
generateDomainEmailSetupsWithEMailAddresses(26, HsHostingAssetType.DOMAIN_EMAIL_SETUP,
"%c%c.example.com",
10, HsHostingAssetType.EMAIL_ADDRESS
)
))
.build()
))
.build();
// when
final var result = HsBookingItemEntityValidator.doValidate(managedWebspaceBookingItem);
// then
assertThat(result).containsExactlyInAnyOrder(
"Multi=1 allows at maximum 25 unix users, but 26 found",
"Multi=1 allows at maximum 5 database users, but 6 found",
"Multi=1 allows at maximum 5 databases, but 9 found",
"Multi=1 allows at maximum 250 databases, but 260 found"
);
}
@SafeVarargs
private List<HsHostingAssetEntity> concat(final List<HsHostingAssetEntity>... hostingAssets) {
return stream(hostingAssets)
.flatMap(Collection::stream)
.collect(Collectors.toList());
}
private List<HsHostingAssetEntity> generate(final int count, final HsHostingAssetType hostingAssetType,
final String identifierPattern) {
return IntStream.range(0, count)
.mapToObj(number -> HsHostingAssetEntity.builder()
.type(hostingAssetType)
.identifier(identifierPattern.formatted((number/'a')+'a', (number%'a')+'a'))
.build())
.toList();
}
private List<HsHostingAssetEntity> generateDbUsersWithDatabases(
final int userCount,
final HsHostingAssetType directAssetType,
final String directAssetIdentifierFormat,
final int dbCount,
final HsHostingAssetType subAssetType) {
return IntStream.range(0, userCount)
.mapToObj(n -> HsHostingAssetEntity.builder()
.type(directAssetType)
.identifier(directAssetIdentifierFormat.formatted((n/'a')+'a', (n%'a')+'a'))
.subHostingAssets(
generate(dbCount, subAssetType, "%c%c.example.com".formatted((n/'a')+'a', (n%'a')+'a'))
)
.build())
.toList();
}
private List<HsHostingAssetEntity> generateDomainEmailSetupsWithEMailAddresses(
final int domainCount,
final HsHostingAssetType directAssetType,
final String directAssetIdentifierFormat,
final int emailAddressCount,
final HsHostingAssetType subAssetType) {
return IntStream.range(0, domainCount)
.mapToObj(n -> HsHostingAssetEntity.builder()
.type(directAssetType)
.identifier(directAssetIdentifierFormat.formatted((n/'a')+'a', (n%'a')+'a'))
.subHostingAssets(
generate(emailAddressCount, subAssetType, "xyz00_%c%c%%c%%c".formatted((n/'a')+'a', (n%'a')+'a'))
)
.build())
.toList();
}
}

View File

@ -1,54 +1,66 @@
package net.hostsharing.hsadminng.hs.booking.item.validators;
import net.hostsharing.hsadminng.hs.booking.debitor.HsBookingDebitorEntity;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
import net.hostsharing.hsadminng.hs.booking.project.HsBookingProjectEntity;
import org.junit.jupiter.api.Test;
import java.util.Map;
import static java.util.Map.entry;
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.MANAGED_WEBSPACE;
import static net.hostsharing.hsadminng.hs.booking.item.validators.HsBookingItemEntityValidators.forType;
import static org.assertj.core.api.Assertions.assertThat;
class HsManagedWebspaceBookingItemValidatorUnitTest {
final HsBookingDebitorEntity debitor = HsBookingDebitorEntity.builder()
.debitorNumber(12345)
.build();
final HsBookingProjectEntity project = HsBookingProjectEntity.builder()
.debitor(debitor)
.caption("Test-Project")
.build();
@Test
void validatesProperties() {
// given
final var mangedServerBookingItemEntity = HsBookingItemEntity.builder()
.type(MANAGED_WEBSPACE)
.project(project)
.caption("Test Managed-Webspace")
.resources(Map.ofEntries(
entry("CPUs", 2),
entry("RAM", 25),
entry("SSD", 25),
entry("Traffic", 250),
entry("SLA-EMail", true)
))
.build();
final var validator = forType(mangedServerBookingItemEntity.getType());
// when
final var result = validator.validate(mangedServerBookingItemEntity);
final var result = HsBookingItemEntityValidator.doValidate(mangedServerBookingItemEntity);
// then
assertThat(result).containsExactlyInAnyOrder(
"'resources.CPUs' is not expected but is set to '2'",
"'resources.SLA-EMail' is not expected but is set to 'true'",
"'resources.RAM' is not expected but is set to '25'");
"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.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'"
);
}
@Test
void containsAllValidations() {
// when
final var validator = forType(MANAGED_WEBSPACE);
final var validator = HsBookingItemEntityValidatorRegistry.forType(MANAGED_WEBSPACE);
// then
assertThat(validator.properties()).map(Map::toString).containsExactlyInAnyOrder(
"{type=integer, propertyName=SSD, required=true, unit=GB, min=1, max=100, step=1}",
"{type=integer, propertyName=HDD, required=false, unit=GB, min=0, max=250, step=10}",
"{type=integer, propertyName=Traffic, required=true, unit=GB, min=10, max=1000, step=10}",
"{type=enumeration, propertyName=SLA-Platform, required=false, values=[BASIC, EXT24H]}",
"{type=integer, propertyName=Daemons, required=false, unit=null, min=0, max=10, step=null}",
"{type=boolean, propertyName=Online Office Server, required=false, falseIf=null}");
"{type=integer, propertyName=SSD, unit=GB, min=1, max=100, step=1, required=true, isTotalsValidator=false}",
"{type=integer, propertyName=HDD, unit=GB, min=0, max=250, step=10, required=false, isTotalsValidator=false}",
"{type=integer, propertyName=Traffic, unit=GB, min=10, max=1000, step=10, required=true, isTotalsValidator=false}",
"{type=integer, propertyName=Multi, min=1, max=100, step=1, required=false, defaultValue=1, isTotalsValidator=false}",
"{type=integer, propertyName=Daemons, min=0, max=10, required=false, defaultValue=0, isTotalsValidator=false}",
"{type=boolean, propertyName=Online Office Server, required=false, isTotalsValidator=false}",
"{type=enumeration, propertyName=SLA-Platform, values=[BASIC, EXT24H], required=false, defaultValue=BASIC, isTotalsValidator=false}");
}
}

View File

@ -0,0 +1,102 @@
package net.hostsharing.hsadminng.hs.booking.item.validators;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
import org.junit.jupiter.api.Test;
import static java.util.List.of;
import static java.util.Map.entry;
import static java.util.Map.ofEntries;
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.CLOUD_SERVER;
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.MANAGED_SERVER;
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.PRIVATE_CLOUD;
import static org.assertj.core.api.Assertions.assertThat;
class HsPrivateCloudBookingItemValidatorTest {
@Test
void validatesPropertyTotals() {
// given
final var privateCloudBookingItemEntity = HsBookingItemEntity.builder()
.type(PRIVATE_CLOUD)
.resources(ofEntries(
entry("CPUs", 4),
entry("RAM", 20),
entry("SSD", 100),
entry("Traffic", 5000)
))
.subBookingItems(of(
HsBookingItemEntity.builder()
.type(MANAGED_SERVER)
.resources(ofEntries(
entry("CPUs", 2),
entry("RAM", 10),
entry("SSD", 50),
entry("Traffic", 2500)
))
.build(),
HsBookingItemEntity.builder()
.type(CLOUD_SERVER)
.resources(ofEntries(
entry("CPUs", 2),
entry("RAM", 10),
entry("SSD", 50),
entry("Traffic", 2500)
))
.build()
))
.build();
// when
final var result = HsBookingItemEntityValidator.doValidate(privateCloudBookingItemEntity);
// then
assertThat(result).isEmpty();
}
@Test
void validatesExceedingPropertyTotals() {
// given
final var privateCloudBookingItemEntity = HsBookingItemEntity.builder()
.type(PRIVATE_CLOUD)
.resources(ofEntries(
entry("CPUs", 4),
entry("RAM", 20),
entry("SSD", 100),
entry("Traffic", 5000)
))
.subBookingItems(of(
HsBookingItemEntity.builder()
.type(MANAGED_SERVER)
.resources(ofEntries(
entry("CPUs", 3),
entry("RAM", 20),
entry("SSD", 100),
entry("Traffic", 3000)
))
.build(),
HsBookingItemEntity.builder()
.type(CLOUD_SERVER)
.resources(ofEntries(
entry("CPUs", 2),
entry("RAM", 10),
entry("SSD", 50),
entry("Traffic", 2500)
))
.build()
))
.build();
// when
final var result = HsBookingItemEntityValidator.doValidate(privateCloudBookingItemEntity);
// then
assertThat(result).containsExactlyInAnyOrder(
"total CPUs is 5 exceeds max total CPUs 4",
"total RAM is 30 GB exceeds max total RAM 20 GB",
"total SSD is 150 GB exceeds max total SSD 100 GB",
"total Traffic is 5500 GB exceeds max total Traffic 5000 GB"
);
}
}

View File

@ -4,7 +4,7 @@ import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.hs.booking.debitor.HsBookingDebitorRepository;
import net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantRepository;
import net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleRepository;
import net.hostsharing.hsadminng.rbac.test.Array;
import net.hostsharing.hsadminng.mapper.Array;
import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup;
import net.hostsharing.hsadminng.rbac.test.JpaAttempt;
import org.junit.jupiter.api.Nested;
@ -23,7 +23,7 @@ import java.util.List;
import static net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantEntity.distinctGrantDisplaysOf;
import static net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleEntity.distinctRoleNamesOf;
import static net.hostsharing.hsadminng.rbac.test.Array.fromFormatted;
import static net.hostsharing.hsadminng.mapper.Array.fromFormatted;
import static net.hostsharing.hsadminng.rbac.test.JpaAttempt.attempt;
import static org.assertj.core.api.Assertions.assertThat;

View File

@ -5,6 +5,7 @@ import io.restassured.http.ContentType;
import net.hostsharing.hsadminng.HsadminNgApplication;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemRepository;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType;
import net.hostsharing.hsadminng.hs.booking.project.HsBookingProjectRepository;
import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorRepository;
import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup;
@ -20,8 +21,9 @@ import java.util.Map;
import java.util.UUID;
import static java.util.Map.entry;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.CLOUD_SERVER;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_SERVER;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_WEBSPACE;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.UNIX_USER;
import static net.hostsharing.hsadminng.rbac.test.JsonMatcher.lenientlyEquals;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.matchesRegex;
@ -77,25 +79,19 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
"type": "MANAGED_WEBSPACE",
"identifier": "sec01",
"caption": "some Webspace",
"config": {
"extra": 42
}
"config": {}
},
{
"type": "MANAGED_WEBSPACE",
"identifier": "fir01",
"caption": "some Webspace",
"config": {
"extra": 42
}
"config": {}
},
{
"type": "MANAGED_WEBSPACE",
"identifier": "thi01",
"caption": "some Webspace",
"config": {
"extra": 42
}
"config": {}
}
]
"""));
@ -113,7 +109,7 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
.header("current-user", "superuser-alex@hostsharing.net")
.port(port)
.when()
.get("http://localhost/api/hs/hosting/assets?type=" + MANAGED_SERVER)
. get("http://localhost/api/hs/hosting/assets?type=" + MANAGED_SERVER)
.then().log().all().assertThat()
.statusCode(200)
.contentType("application/json")
@ -124,7 +120,9 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
"identifier": "vm1011",
"caption": "some ManagedServer",
"config": {
"extra": 42
"monit_max_cpu_usage": 90,
"monit_max_ram_usage": 80,
"monit_max_ssd_usage": 70
}
},
{
@ -132,7 +130,9 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
"identifier": "vm1012",
"caption": "some ManagedServer",
"config": {
"extra": 42
"monit_max_cpu_usage": 90,
"monit_max_ram_usage": 80,
"monit_max_ssd_usage": 70
}
},
{
@ -140,7 +140,9 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
"identifier": "vm1013",
"caption": "some ManagedServer",
"config": {
"extra": 42
"monit_max_cpu_usage": 90,
"monit_max_ram_usage": 80,
"monit_max_ssd_usage": 70
}
}
]
@ -156,7 +158,14 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
void globalAdmin_canAddBookedAsset() {
context.define("superuser-alex@hostsharing.net");
final var givenBookingItem = givenBookingItem("D-1000111 default project", "some PrivateCloud");
final var givenBookingItem = newBookingItem("D-1000111 default project",
HsBookingItemType.MANAGED_WEBSPACE, "separate ManagedWebspace BI",
Map.ofEntries(
entry("SSD", 50),
entry("Traffic", 50)
)
);
final var givenParentAsset = givenParentAsset(MANAGED_SERVER, "vm1011");
final var location = RestAssured // @formatter:off
.given()
@ -165,12 +174,13 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
.body("""
{
"bookingItemUuid": "%s",
"type": "MANAGED_SERVER",
"identifier": "vm1400",
"caption": "some new ManagedServer",
"config": { "monit_max_ssd_usage": 80, "monit_max_cpu_usage": 90, "monit_max_ram_usage": 70 }
"type": "MANAGED_WEBSPACE",
"identifier": "fir10",
"parentAssetUuid": "%s",
"caption": "some separate ManagedWebspace HA",
"config": {}
}
""".formatted(givenBookingItem.getUuid()))
""".formatted(givenBookingItem.getUuid(), givenParentAsset.getUuid()))
.port(port)
.when()
.post("http://localhost/api/hs/hosting/assets")
@ -179,10 +189,10 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
.contentType(ContentType.JSON)
.body("", lenientlyEquals("""
{
"type": "MANAGED_SERVER",
"identifier": "vm1400",
"caption": "some new ManagedServer",
"config": { "monit_max_ssd_usage": 80, "monit_max_cpu_usage": 90, "monit_max_ram_usage": 70 }
"type": "MANAGED_WEBSPACE",
"identifier": "fir10",
"caption": "some separate ManagedWebspace HA",
"config": {}
}
"""))
.header("Location", matchesRegex("http://localhost:[1-9][0-9]*/api/hs/hosting/assets/[^/]*"))
@ -192,6 +202,7 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
final var newUserUuid = UUID.fromString(
location.substring(location.lastIndexOf('/') + 1));
assertThat(newUserUuid).isNotNull();
toCleanup(HsHostingAssetEntity.class, newUserUuid);
}
@Test
@ -240,7 +251,7 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
}
@Test
void additionalValidationsArePerformend_whenAddingAsset() {
void propertyValidationsArePerformend_whenAddingAsset() {
context.define("superuser-alex@hostsharing.net");
final var givenBookingItem = givenBookingItem("D-1000111 default project", "some PrivateCloud");
@ -267,9 +278,66 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
.body("", lenientlyEquals("""
{
"statusPhrase": "Bad Request",
"message": "['config.extra' is not expected but is set to '42', 'config.monit_max_ssd_usage' is expected to be >= 10 but is 0, 'config.monit_max_cpu_usage' is expected to be <= 100 but is 101, 'config.monit_max_ram_usage' is required but missing]"
"message": "[
<<<MANAGED_SERVER:vm1400.config.extra is not expected but is set to '42',
<<<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 <= 100 but is 101,
<<<MANAGED_SERVER:vm1400.config.monit_max_ram_usage is required but missing
<<<]"
}
""")); // @formatter:on
""".replaceAll(" +<<<", ""))); // @formatter:on
}
@Test
void totalsLimitValidationsArePerformend_whenAddingAsset() {
context.define("superuser-alex@hostsharing.net");
final var givenHostingAsset = givenHostingAsset(MANAGED_WEBSPACE, "fir01");
assertThat(givenHostingAsset.getBookingItem().getResources().get("Multi"))
.as("precondition failed")
.isEqualTo(1);
jpaAttempt.transacted(() -> {
context.define("superuser-alex@hostsharing.net");
for (int n = 0; n < 25; ++n ) {
toCleanup(assetRepo.save(
HsHostingAssetEntity.builder()
.type(UNIX_USER)
.parentAsset(givenHostingAsset)
.identifier("fir01-%2d".formatted(n))
.caption("Test UnixUser fir01-%2d".formatted(n))
.build()));
}
}).assertSuccessful();
RestAssured // @formatter:off
.given()
.header("current-user", "superuser-alex@hostsharing.net")
.contentType(ContentType.JSON)
.body("""
{
"parentAssetUuid": "%s",
"type": "UNIX_USER",
"identifier": "fir01-extra",
"caption": "some extra UnixUser",
"config": { }
}
""".formatted(givenHostingAsset.getUuid()))
.port(port)
.when()
.post("http://localhost/api/hs/hosting/assets")
.then().log().all().assertThat()
.statusCode(400)
.contentType(ContentType.JSON)
.body("", lenientlyEquals("""
{
"statusPhrase": "Bad Request",
"message": "[
<<<UNIX_USER:fir01-extra.parentAsset.MANAGED_WEBSPACE:fir01.bookingItem.Multi=1 allows at maximum 25 unix users, but 26 found
<<<]"
}
""".replaceAll(" +<<<", ""))); // @formatter:on
}
}
@ -295,9 +363,7 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
.body("", lenientlyEquals("""
{
"caption": "some ManagedServer",
"config": {
"extra": 42
}
"config": {}
}
""")); // @formatter:on
}
@ -340,9 +406,7 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
{
"identifier": "vm1013",
"caption": "some ManagedServer",
"config": {
"extra": 42
}
"config": {}
}
""")); // @formatter:on
}
@ -443,6 +507,29 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
}
}
HsHostingAssetEntity givenHostingAsset(final HsHostingAssetType type, final String identifier) {
return assetRepo.findByIdentifier(identifier).stream()
.filter(ha -> ha.getType()==type)
.findAny().orElseThrow();
}
HsBookingItemEntity newBookingItem(
final String projectCaption,
final HsBookingItemType type, final String bookingItemCaption, final Map<String, Object> resources) {
return jpaAttempt.transacted(() -> {
context.define("superuser-alex@hostsharing.net");
final var project = projectRepo.findByCaption(projectCaption).stream()
.findAny().orElseThrow();
final var bookingItem = HsBookingItemEntity.builder()
.project(project)
.type(type)
.caption(bookingItemCaption)
.resources(resources)
.build();
return toCleanup(bookingItemRepo.save(bookingItem));
}).assertSuccessful().returnedValue();
}
HsBookingItemEntity givenBookingItem(final String projectCaption, final String bookingItemCaption) {
return bookingItemRepo.findByCaption(bookingItemCaption).stream()
.filter(bi -> bi.getRelatedProject().getCaption().contains(projectCaption))

View File

@ -40,11 +40,11 @@ class HsHostingAssetEntityPatcherUnitTest extends PatchUnitTestBase<
private static final Map<String, Object> PATCH_CONFIG = patchMap(
entry("CPU", 2),
entry("HDD", null),
entry("SDD", 256)
entry("SSD", 256)
);
private static final Map<String, Object> PATCHED_CONFIG = patchMap(
entry("CPU", 2),
entry("SDD", 256),
entry("SSD", 256),
entry("MEM", 64)
);

View File

@ -33,7 +33,8 @@ class HsHostingAssetPropsControllerAcceptanceTest {
[
"MANAGED_SERVER",
"MANAGED_WEBSPACE",
"CLOUD_SERVER"
"CLOUD_SERVER",
"UNIX_USER"
]
"""));
// @formatter:on
@ -55,56 +56,54 @@ class HsHostingAssetPropsControllerAcceptanceTest {
{
"type": "integer",
"propertyName": "monit_min_free_ssd",
"required": false,
"unit": null,
"min": 1,
"max": 1000,
"step": null
"required": false,
"isTotalsValidator": false
},
{
"type": "integer",
"propertyName": "monit_min_free_hdd",
"required": false,
"unit": null,
"min": 1,
"max": 4000,
"step": null
"required": false,
"isTotalsValidator": false
},
{
"type": "integer",
"propertyName": "monit_max_ssd_usage",
"required": true,
"unit": "%",
"min": 10,
"max": 100,
"step": null
"required": true,
"isTotalsValidator": false
},
{
"type": "integer",
"propertyName": "monit_max_hdd_usage",
"required": false,
"unit": "%",
"min": 10,
"max": 100,
"step": null
"required": false,
"isTotalsValidator": false
},
{
"type": "integer",
"propertyName": "monit_max_cpu_usage",
"required": true,
"unit": "%",
"min": 10,
"max": 100,
"step": null
"required": true,
"isTotalsValidator": false
},
{
"type": "integer",
"propertyName": "monit_max_ram_usage",
"required": true,
"unit": "%",
"min": 10,
"max": 100,
"step": null
"required": true,
"isTotalsValidator": false
}
]
"""));

View File

@ -3,10 +3,11 @@ package net.hostsharing.hsadminng.hs.hosting.asset;
import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemRepository;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType;
import net.hostsharing.hsadminng.hs.booking.project.HsBookingProjectRepository;
import net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantRepository;
import net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleRepository;
import net.hostsharing.hsadminng.rbac.test.Array;
import net.hostsharing.hsadminng.mapper.Array;
import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup;
import net.hostsharing.hsadminng.rbac.test.JpaAttempt;
import org.junit.jupiter.api.Nested;
@ -30,7 +31,7 @@ import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANA
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_WEBSPACE;
import static net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantEntity.distinctGrantDisplaysOf;
import static net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleEntity.distinctRoleNamesOf;
import static net.hostsharing.hsadminng.rbac.test.Array.fromFormatted;
import static net.hostsharing.hsadminng.mapper.Array.fromFormatted;
import static net.hostsharing.hsadminng.rbac.test.JpaAttempt.attempt;
import static org.assertj.core.api.Assertions.assertThat;
@ -70,12 +71,13 @@ class HsHostingAssetRepositoryIntegrationTest extends ContextBasedTestWithCleanu
// given
context("superuser-alex@hostsharing.net");
final var count = assetRepo.count();
final var givenManagedServer = givenManagedServer("D-1000111 default project", MANAGED_SERVER);
final var givenManagedServer = givenHostingAsset("D-1000111 default project", MANAGED_SERVER);
final var newWebspaceBookingItem = newBookingItem(givenManagedServer.getBookingItem(), HsBookingItemType.MANAGED_WEBSPACE, "fir01");
// when
final var result = attempt(em, () -> {
final var newAsset = HsHostingAssetEntity.builder()
.bookingItem(givenManagedServer.getBookingItem())
.bookingItem(newWebspaceBookingItem)
.parentAsset(givenManagedServer)
.caption("some new managed webspace")
.type(MANAGED_WEBSPACE)
@ -95,18 +97,19 @@ class HsHostingAssetRepositoryIntegrationTest extends ContextBasedTestWithCleanu
public void createsAndGrantsRoles() {
// given
context("superuser-alex@hostsharing.net");
final var givenManagedServer = givenHostingAsset("D-1000111 default project", MANAGED_SERVER);
final var newWebspaceBookingItem = newBookingItem(givenManagedServer.getBookingItem(), HsBookingItemType.MANAGED_WEBSPACE, "fir01");
em.flush();
final var initialRoleNames = distinctRoleNamesOf(rawRoleRepo.findAll());
final var initialGrantNames = distinctGrantDisplaysOf(rawGrantRepo.findAll()).stream()
.map(s -> s.replace("hs_office_", ""))
.toList();
final var givenBookingItem = givenBookingItem("D-1000111 default project", "some PrivateCloud");
final var initialGrantNames = distinctGrantDisplaysOf(rawGrantRepo.findAll());
// when
final var result = attempt(em, () -> {
final var newAsset = HsHostingAssetEntity.builder()
.bookingItem(givenBookingItem)
.type(HsHostingAssetType.MANAGED_SERVER)
.identifier("vm9000")
.bookingItem(newWebspaceBookingItem)
.parentAsset(givenManagedServer)
.type(HsHostingAssetType.MANAGED_WEBSPACE)
.identifier("fir00")
.caption("some new managed webspace")
.build();
return toCleanup(assetRepo.save(newAsset));
@ -117,29 +120,33 @@ class HsHostingAssetRepositoryIntegrationTest extends ContextBasedTestWithCleanu
final var all = rawRoleRepo.findAll();
assertThat(distinctRoleNamesOf(all)).containsExactlyInAnyOrder(Array.from(
initialRoleNames,
"hs_hosting_asset#vm9000:OWNER",
"hs_hosting_asset#vm9000:ADMIN",
"hs_hosting_asset#vm9000:AGENT",
"hs_hosting_asset#vm9000:TENANT"));
"hs_hosting_asset#fir00:ADMIN",
"hs_hosting_asset#fir00:AGENT",
"hs_hosting_asset#fir00:OWNER",
"hs_hosting_asset#fir00:TENANT"));
assertThat(distinctGrantDisplaysOf(rawGrantRepo.findAll()))
.map(s -> s.replace("hs_office_", ""))
.containsExactlyInAnyOrder(fromFormatted(
initialGrantNames,
// owner
"{ grant role:hs_hosting_asset#vm9000:OWNER to role:hs_booking_item#somePrivateCloud:ADMIN by system and assume }",
"{ grant perm:hs_hosting_asset#vm9000:DELETE to role:hs_hosting_asset#vm9000:OWNER by system and assume }",
"{ grant role:hs_hosting_asset#vm9000:ADMIN to role:hs_hosting_asset#vm9000:OWNER by system and assume }",
"{ grant role:hs_hosting_asset#fir00:OWNER to role:hs_booking_item#fir01:ADMIN by system and assume }",
"{ grant role:hs_hosting_asset#fir00:OWNER to role:hs_hosting_asset#vm1011:ADMIN by system and assume }",
"{ grant perm:hs_hosting_asset#fir00:DELETE to role:hs_hosting_asset#fir00:OWNER by system and assume }",
// admin
"{ grant perm:hs_hosting_asset#vm9000:INSERT>hs_hosting_asset to role:hs_hosting_asset#vm9000:ADMIN by system and assume }",
"{ grant perm:hs_hosting_asset#vm9000:UPDATE to role:hs_hosting_asset#vm9000:ADMIN by system and assume }",
"{ grant role:hs_hosting_asset#vm9000:ADMIN to role:hs_booking_item#somePrivateCloud:AGENT by system and assume }",
"{ grant role:hs_hosting_asset#vm9000:TENANT to role:hs_hosting_asset#vm9000:AGENT by system and assume }",
"{ grant role:hs_hosting_asset#vm9000:AGENT to role:hs_hosting_asset#vm9000:ADMIN by system and assume }",
"{ grant role:hs_hosting_asset#fir00:ADMIN to role:hs_hosting_asset#fir00:OWNER by system and assume }",
"{ grant role:hs_hosting_asset#fir00:ADMIN to role:hs_booking_item#fir01:AGENT by system and assume }",
"{ grant perm:hs_hosting_asset#fir00:INSERT>hs_hosting_asset to role:hs_hosting_asset#fir00:ADMIN by system and assume }",
"{ grant perm:hs_hosting_asset#fir00:UPDATE to role:hs_hosting_asset#fir00:ADMIN by system and assume }",
// agent
"{ grant role:hs_hosting_asset#fir00:ADMIN to role:hs_hosting_asset#vm1011:AGENT by system and assume }",
"{ grant role:hs_hosting_asset#fir00:AGENT to role:hs_hosting_asset#fir00:ADMIN by system and assume }",
// tenant
"{ grant role:hs_booking_item#somePrivateCloud:TENANT to role:hs_hosting_asset#vm9000:TENANT by system and assume }",
"{ grant role:hs_booking_item#fir01:TENANT to role:hs_hosting_asset#fir00:TENANT by system and assume }",
"{ grant role:hs_hosting_asset#fir00:TENANT to role:hs_hosting_asset#fir00:AGENT by system and assume }",
"{ grant role:hs_hosting_asset#vm1011:TENANT to role:hs_hosting_asset#fir00:TENANT by system and assume }",
null));
}
@ -164,9 +171,9 @@ class HsHostingAssetRepositoryIntegrationTest extends ContextBasedTestWithCleanu
// then
allTheseServersAreReturned(
result,
"HsHostingAssetEntity(MANAGED_WEBSPACE, sec01, some Webspace, MANAGED_SERVER:vm1012, D-1000212:D-1000212 default project:separate ManagedServer, { extra: 42 })",
"HsHostingAssetEntity(MANAGED_WEBSPACE, thi01, some Webspace, MANAGED_SERVER:vm1013, D-1000313:D-1000313 default project:separate ManagedServer, { extra: 42 })",
"HsHostingAssetEntity(MANAGED_WEBSPACE, fir01, some Webspace, MANAGED_SERVER:vm1011, D-1000111:D-1000111 default project:separate ManagedServer, { extra: 42 })");
"HsHostingAssetEntity(MANAGED_WEBSPACE, sec01, some Webspace, MANAGED_SERVER:vm1012, D-1000212:D-1000212 default project:separate ManagedWebspace)",
"HsHostingAssetEntity(MANAGED_WEBSPACE, fir01, some Webspace, MANAGED_SERVER:vm1011, D-1000111:D-1000111 default project:separate ManagedWebspace)",
"HsHostingAssetEntity(MANAGED_WEBSPACE, thi01, some Webspace, MANAGED_SERVER:vm1013, D-1000313:D-1000313 default project:separate ManagedWebspace)");
}
@Test
@ -182,9 +189,8 @@ class HsHostingAssetRepositoryIntegrationTest extends ContextBasedTestWithCleanu
// then:
exactlyTheseAssetsAreReturned(
result,
"HsHostingAssetEntity(MANAGED_WEBSPACE, fir01, some Webspace, MANAGED_SERVER:vm1011, D-1000111:D-1000111 default project:separate ManagedServer, { extra: 42 })",
"HsHostingAssetEntity(MANAGED_SERVER, vm1011, some ManagedServer, D-1000111:D-1000111 default project:some PrivateCloud, { extra: 42 })",
"HsHostingAssetEntity(CLOUD_SERVER, vm2011, another CloudServer, D-1000111:D-1000111 default project:some PrivateCloud, { extra: 42 })");
"HsHostingAssetEntity(MANAGED_WEBSPACE, fir01, some Webspace, MANAGED_SERVER:vm1011, D-1000111:D-1000111 default project:separate ManagedWebspace)",
"HsHostingAssetEntity(MANAGED_SERVER, vm1011, some ManagedServer, D-1000111:D-1000111 default project:separate ManagedServer, { monit_max_cpu_usage: 90, monit_max_ram_usage: 80, monit_max_ssd_usage: 70 })");
}
@Test
@ -200,7 +206,7 @@ class HsHostingAssetRepositoryIntegrationTest extends ContextBasedTestWithCleanu
// then
allTheseServersAreReturned(
result,
"HsHostingAssetEntity(MANAGED_WEBSPACE, thi01, some Webspace, MANAGED_SERVER:vm1013, D-1000313:D-1000313 default project:separate ManagedServer, { extra: 42 })");
"HsHostingAssetEntity(MANAGED_WEBSPACE, sec01, some Webspace, MANAGED_SERVER:vm1012, D-1000212:D-1000212 default project:separate ManagedWebspace)");
}
}
@ -351,7 +357,7 @@ class HsHostingAssetRepositoryIntegrationTest extends ContextBasedTestWithCleanu
private HsHostingAssetEntity givenSomeTemporaryAsset(final String projectCaption, final String identifier) {
return jpaAttempt.transacted(() -> {
context("superuser-alex@hostsharing.net");
final var givenBookingItem = givenBookingItem("D-1000111 default project", "some PrivateCloud");
final var givenBookingItem = givenBookingItem("D-1000111 default project", "test CloudServer");
final var newAsset = HsHostingAssetEntity.builder()
.bookingItem(givenBookingItem)
.type(CLOUD_SERVER)
@ -367,20 +373,30 @@ class HsHostingAssetRepositoryIntegrationTest extends ContextBasedTestWithCleanu
}
HsBookingItemEntity givenBookingItem(final String projectCaption, final String bookingItemCaption) {
final var givenProject = projectRepo.findByCaption(projectCaption).stream()
.findAny().orElseThrow();
return bookingItemRepo.findAllByProjectUuid(givenProject.getUuid()).stream()
.filter(i -> i.getCaption().equals(bookingItemCaption))
return bookingItemRepo.findByCaption(bookingItemCaption).stream()
.filter(i -> i.getRelatedProject().getCaption().equals(projectCaption))
.findAny().orElseThrow();
}
HsHostingAssetEntity givenManagedServer(final String projectCaption, final HsHostingAssetType type) {
HsHostingAssetEntity givenHostingAsset(final String projectCaption, final HsHostingAssetType type) {
final var givenProject = projectRepo.findByCaption(projectCaption).stream()
.findAny().orElseThrow();
return assetRepo.findAllByCriteria(givenProject.getUuid(), null, type).stream()
.findAny().orElseThrow();
}
HsBookingItemEntity newBookingItem(
final HsBookingItemEntity parentBookingItem,
final HsBookingItemType type,
final String caption) {
final var newBookingItem = HsBookingItemEntity.builder()
.parentItem(parentBookingItem)
.type(type)
.caption(caption)
.build();
return toCleanup(bookingItemRepo.save(newBookingItem));
}
void exactlyTheseAssetsAreReturned(
final List<HsHostingAssetEntity> actualResult,
final String... serverNames) {

View File

@ -7,7 +7,7 @@ import java.util.Map;
import static java.util.Map.entry;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.CLOUD_SERVER;
import static net.hostsharing.hsadminng.hs.hosting.asset.validators.HsHostingAssetEntityValidators.forType;
import static net.hostsharing.hsadminng.hs.hosting.asset.validators.HsHostingAssetEntityValidator.forType;
import static org.assertj.core.api.Assertions.assertThat;
class HsCloudServerHostingAssetValidatorUnitTest {
@ -17,6 +17,7 @@ class HsCloudServerHostingAssetValidatorUnitTest {
// given
final var cloudServerHostingAssetEntity = HsHostingAssetEntity.builder()
.type(CLOUD_SERVER)
.identifier("vm1234")
.config(Map.ofEntries(
entry("RAM", 2000)
))
@ -28,7 +29,7 @@ class HsCloudServerHostingAssetValidatorUnitTest {
final var result = validator.validate(cloudServerHostingAssetEntity);
// then
assertThat(result).containsExactly("'config.RAM' is not expected but is set to '2000'");
assertThat(result).containsExactly("CLOUD_SERVER:vm1234.config.RAM is not expected but is set to '2000'");
}
@Test

View File

@ -6,27 +6,28 @@ import org.junit.jupiter.api.Test;
import jakarta.validation.ValidationException;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_SERVER;
import static net.hostsharing.hsadminng.hs.hosting.asset.validators.HsHostingAssetEntityValidators.valid;
import static net.hostsharing.hsadminng.hs.hosting.asset.validators.HsHostingAssetEntityValidator.validated;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.catchThrowable;
class HsHostingAssetEntityValidatorsUnitTest {
class HsHostingAssetEntityValidatorUnitTest {
@Test
void validThrowsException() {
// given
final var managedServerHostingAssetEntity = HsHostingAssetEntity.builder()
.type(MANAGED_SERVER)
.identifier("vm1234")
.build();
// when
final var result = catchThrowable( ()-> valid(managedServerHostingAssetEntity) );
final var result = catchThrowable( ()-> validated(managedServerHostingAssetEntity) );
// then
assertThat(result).isInstanceOf(ValidationException.class)
.hasMessageContaining(
"'config.monit_max_ssd_usage' is required but missing",
"'config.monit_max_cpu_usage' is required but missing",
"'config.monit_max_ram_usage' is required but missing");
"MANAGED_SERVER:vm1234.config.monit_max_ssd_usage is required but missing",
"MANAGED_SERVER:vm1234.config.monit_max_cpu_usage is required but missing",
"MANAGED_SERVER:vm1234.config.monit_max_ram_usage is required but missing");
}
}

View File

@ -7,7 +7,7 @@ import java.util.Map;
import static java.util.Map.entry;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_SERVER;
import static net.hostsharing.hsadminng.hs.hosting.asset.validators.HsHostingAssetEntityValidators.forType;
import static net.hostsharing.hsadminng.hs.hosting.asset.validators.HsHostingAssetEntityValidator.forType;
import static org.assertj.core.api.Assertions.assertThat;
class HsManagedServerHostingAssetValidatorUnitTest {
@ -17,6 +17,7 @@ class HsManagedServerHostingAssetValidatorUnitTest {
// given
final var mangedWebspaceHostingAssetEntity = HsHostingAssetEntity.builder()
.type(MANAGED_SERVER)
.identifier("vm1234")
.config(Map.ofEntries(
entry("monit_max_hdd_usage", "90"),
entry("monit_max_cpu_usage", 2),
@ -30,9 +31,9 @@ class HsManagedServerHostingAssetValidatorUnitTest {
// then
assertThat(result).containsExactlyInAnyOrder(
"'config.monit_max_ssd_usage' is required but missing",
"'config.monit_max_hdd_usage' is expected to be of type class java.lang.Integer, but is of type 'String'",
"'config.monit_max_cpu_usage' is expected to be >= 10 but is 2",
"'config.monit_max_ram_usage' is expected to be <= 100 but is 101");
"MANAGED_SERVER:vm1234.config.monit_max_ssd_usage is required but missing",
"MANAGED_SERVER:vm1234.config.monit_max_hdd_usage is expected to be of type class java.lang.Integer, but is of type 'String'",
"MANAGED_SERVER:vm1234.config.monit_max_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");
}
}

View File

@ -1,13 +1,14 @@
package net.hostsharing.hsadminng.hs.hosting.asset.validators;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType;
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType;
import org.junit.jupiter.api.Test;
import java.util.Map;
import static java.util.Map.entry;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_SERVER;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_WEBSPACE;
import static org.assertj.core.api.Assertions.assertThat;
import static net.hostsharing.hsadminng.hs.booking.project.TestHsBookingProject.TEST_PROJECT;
@ -16,21 +17,32 @@ class HsManagedWebspaceHostingAssetValidatorUnitTest {
final HsBookingItemEntity managedServerBookingItem = HsBookingItemEntity.builder()
.project(TEST_PROJECT)
.type(HsBookingItemType.MANAGED_SERVER)
.caption("Test Managed-Server")
.resources(Map.ofEntries(
entry("CPUs", 2),
entry("RAM", 25),
entry("SSD", 25),
entry("Traffic", 250),
entry("SLA-Platform", "EXT4H"),
entry("SLA-EMail", true)
))
.build();
final HsHostingAssetEntity mangedServerAssetEntity = HsHostingAssetEntity.builder()
.type(MANAGED_SERVER)
.type(HsHostingAssetType.MANAGED_SERVER)
.bookingItem(managedServerBookingItem)
.identifier("vm1234")
.config(Map.ofEntries(
entry("HDD", 0),
entry("SSD", 1),
entry("Traffic", 10)
entry("monit_max_ssd_usage", 70),
entry("monit_max_cpu_usage", 80),
entry("monit_max_ram_usage", 90)
))
.build();
@Test
void validatesIdentifier() {
// given
final var validator = HsHostingAssetEntityValidators.forType(MANAGED_WEBSPACE);
final var validator = HsHostingAssetEntityValidator.forType(MANAGED_WEBSPACE);
final var mangedWebspaceHostingAssetEntity = HsHostingAssetEntity.builder()
.type(MANAGED_WEBSPACE)
.parentAsset(mangedServerAssetEntity)
@ -47,7 +59,7 @@ class HsManagedWebspaceHostingAssetValidatorUnitTest {
@Test
void validatesUnknownProperties() {
// given
final var validator = HsHostingAssetEntityValidators.forType(MANAGED_WEBSPACE);
final var validator = HsHostingAssetEntityValidator.forType(MANAGED_WEBSPACE);
final var mangedWebspaceHostingAssetEntity = HsHostingAssetEntity.builder()
.type(MANAGED_WEBSPACE)
.parentAsset(mangedServerAssetEntity)
@ -61,13 +73,13 @@ class HsManagedWebspaceHostingAssetValidatorUnitTest {
final var result = validator.validate(mangedWebspaceHostingAssetEntity);
// then
assertThat(result).containsExactly("'config.unknown' is not expected but is set to 'some value'");
assertThat(result).containsExactly("MANAGED_WEBSPACE:abc00.config.unknown is not expected but is set to 'some value'");
}
@Test
void validatesValidEntity() {
// given
final var validator = HsHostingAssetEntityValidators.forType(MANAGED_WEBSPACE);
final var validator = HsHostingAssetEntityValidator.forType(MANAGED_WEBSPACE);
final var mangedWebspaceHostingAssetEntity = HsHostingAssetEntity.builder()
.type(MANAGED_WEBSPACE)
.parentAsset(mangedServerAssetEntity)

View File

@ -4,7 +4,7 @@ import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup;
import net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantRepository;
import net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleRepository;
import net.hostsharing.hsadminng.rbac.test.Array;
import net.hostsharing.hsadminng.mapper.Array;
import net.hostsharing.hsadminng.rbac.test.JpaAttempt;
import org.apache.commons.lang3.RandomStringUtils;
import org.junit.jupiter.api.Nested;

View File

@ -4,7 +4,7 @@ import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup;
import net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantRepository;
import net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleRepository;
import net.hostsharing.hsadminng.rbac.test.Array;
import net.hostsharing.hsadminng.mapper.Array;
import net.hostsharing.hsadminng.rbac.test.JpaAttempt;
import org.apache.commons.lang3.RandomStringUtils;
import org.junit.jupiter.api.Nested;

View File

@ -5,7 +5,7 @@ import net.hostsharing.hsadminng.hs.office.membership.HsOfficeMembershipReposito
import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup;
import net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantRepository;
import net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleRepository;
import net.hostsharing.hsadminng.rbac.test.Array;
import net.hostsharing.hsadminng.mapper.Array;
import net.hostsharing.hsadminng.rbac.test.JpaAttempt;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;

View File

@ -5,7 +5,7 @@ import net.hostsharing.hsadminng.hs.office.membership.HsOfficeMembershipReposito
import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup;
import net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantRepository;
import net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleRepository;
import net.hostsharing.hsadminng.rbac.test.Array;
import net.hostsharing.hsadminng.mapper.Array;
import net.hostsharing.hsadminng.rbac.test.JpaAttempt;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;

View File

@ -11,7 +11,7 @@ import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup;
import net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantRepository;
import net.hostsharing.hsadminng.rbac.rbacgrant.RbacGrantsDiagramService;
import net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleRepository;
import net.hostsharing.hsadminng.rbac.test.Array;
import net.hostsharing.hsadminng.mapper.Array;
import net.hostsharing.hsadminng.rbac.test.JpaAttempt;
import org.hibernate.Hibernate;
import org.junit.jupiter.api.Disabled;

View File

@ -7,7 +7,7 @@ import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerRepository;
import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup;
import net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantRepository;
import net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleRepository;
import net.hostsharing.hsadminng.rbac.test.Array;
import net.hostsharing.hsadminng.mapper.Array;
import net.hostsharing.hsadminng.rbac.test.JpaAttempt;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

View File

@ -30,7 +30,7 @@ import java.util.Objects;
import static net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantEntity.distinctGrantDisplaysOf;
import static net.hostsharing.hsadminng.rbac.rbacrole.RawRbacObjectEntity.objectDisplaysOf;
import static net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleEntity.distinctRoleNamesOf;
import static net.hostsharing.hsadminng.rbac.test.Array.from;
import static net.hostsharing.hsadminng.mapper.Array.from;
import static net.hostsharing.hsadminng.rbac.test.JpaAttempt.attempt;
import static org.assertj.core.api.Assertions.assertThat;

View File

@ -4,7 +4,7 @@ import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup;
import net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantRepository;
import net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleRepository;
import net.hostsharing.hsadminng.rbac.test.Array;
import net.hostsharing.hsadminng.mapper.Array;
import net.hostsharing.hsadminng.rbac.test.JpaAttempt;
import org.apache.commons.lang3.RandomStringUtils;
import org.junit.jupiter.api.Nested;

View File

@ -6,7 +6,7 @@ import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRepository;
import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup;
import net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantRepository;
import net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleRepository;
import net.hostsharing.hsadminng.rbac.test.Array;
import net.hostsharing.hsadminng.mapper.Array;
import net.hostsharing.hsadminng.rbac.test.JpaAttempt;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

View File

@ -7,7 +7,7 @@ import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorRepository;
import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup;
import net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantRepository;
import net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleRepository;
import net.hostsharing.hsadminng.rbac.test.Array;
import net.hostsharing.hsadminng.mapper.Array;
import net.hostsharing.hsadminng.rbac.test.JpaAttempt;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
@ -26,7 +26,7 @@ import java.util.List;
import static net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantEntity.distinctGrantDisplaysOf;
import static net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleEntity.distinctRoleNamesOf;
import static net.hostsharing.hsadminng.rbac.test.Array.fromFormatted;
import static net.hostsharing.hsadminng.mapper.Array.fromFormatted;
import static net.hostsharing.hsadminng.rbac.test.JpaAttempt.attempt;
import static org.assertj.core.api.Assertions.assertThat;

View File

@ -2,7 +2,7 @@ package net.hostsharing.hsadminng.rbac.context;
import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.mapper.Mapper;
import net.hostsharing.hsadminng.rbac.test.Array;
import net.hostsharing.hsadminng.mapper.Array;
import net.hostsharing.hsadminng.rbac.test.JpaAttempt;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;

View File

@ -1,7 +1,7 @@
package net.hostsharing.hsadminng.rbac.rbacrole;
import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.rbac.test.Array;
import net.hostsharing.hsadminng.mapper.Array;
import net.hostsharing.hsadminng.rbac.test.JpaAttempt;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

View File

@ -2,7 +2,7 @@ package net.hostsharing.hsadminng.rbac.rbacuser;
import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.rbac.context.ContextBasedTest;
import net.hostsharing.hsadminng.rbac.test.Array;
import net.hostsharing.hsadminng.mapper.Array;
import net.hostsharing.hsadminng.rbac.test.JpaAttempt;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;