hierarchical-validation-baseline #59

Merged
hsh-michaelhoennig merged 18 commits from hierarchical-validation-baseline into master 2024-06-14 16:48:01 +02:00
23 changed files with 160 additions and 111 deletions
Showing only changes of commit 0045c62a27 - Show all commits

View File

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

View File

@ -1,5 +1,6 @@
package net.hostsharing.hsadminng.errors; package net.hostsharing.hsadminng.errors;
import net.hostsharing.hsadminng.hs.validation.MultiValidationException;
import org.iban4j.Iban4jException; import org.iban4j.Iban4jException;
import org.springframework.core.NestedExceptionUtils; import org.springframework.core.NestedExceptionUtils;
import org.springframework.dao.DataIntegrityViolationException; import org.springframework.dao.DataIntegrityViolationException;
@ -75,7 +76,8 @@ public class RestResponseEntityExceptionHandler
@ExceptionHandler({ Iban4jException.class, ValidationException.class }) @ExceptionHandler({ Iban4jException.class, ValidationException.class })
protected ResponseEntity<CustomErrorResponse> handleIbanAndBicExceptions( protected ResponseEntity<CustomErrorResponse> handleIbanAndBicExceptions(
final Throwable exc, final WebRequest request) { 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); 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.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder; import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import static net.hostsharing.hsadminng.hs.booking.item.validators.HsBookingItemEntityValidator.valid; import static net.hostsharing.hsadminng.hs.booking.item.validators.HsBookingItemEntityValidator.validated;
import static net.hostsharing.hsadminng.mapper.PostgresDateRange.toPostgresDateRange; import static net.hostsharing.hsadminng.mapper.PostgresDateRange.toPostgresDateRange;
@RestController @RestController
@ -32,6 +34,9 @@ public class HsBookingItemController implements HsBookingItemsApi {
@Autowired @Autowired
private HsBookingItemRepository bookingItemRepo; private HsBookingItemRepository bookingItemRepo;
@PersistenceContext
private EntityManager em;
@Override @Override
@Transactional(readOnly = true) @Transactional(readOnly = true)
public ResponseEntity<List<HsBookingItemResource>> listBookingItemsByProjectUuid( 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 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 = final var uri =
MvcUriComponentsBuilder.fromController(getClass()) MvcUriComponentsBuilder.fromController(getClass())
@ -78,6 +83,7 @@ public class HsBookingItemController implements HsBookingItemsApi {
context.define(currentUser, assumedRoles); context.define(currentUser, assumedRoles);
final var result = bookingItemRepo.findByUuid(bookingItemUuid); final var result = bookingItemRepo.findByUuid(bookingItemUuid);
result.ifPresent(entity -> em.detach(entity));
return result return result
.map(bookingItemEntity -> ResponseEntity.ok( .map(bookingItemEntity -> ResponseEntity.ok(
mapper.map(bookingItemEntity, HsBookingItemResource.class, ENTITY_TO_RESOURCE_POSTMAPPER))) mapper.map(bookingItemEntity, HsBookingItemResource.class, ENTITY_TO_RESOURCE_POSTMAPPER)))
@ -112,7 +118,7 @@ public class HsBookingItemController implements HsBookingItemsApi {
new HsBookingItemEntityPatcher(current).apply(body); 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); final var mapped = mapper.map(saved, HsBookingItemResource.class, ENTITY_TO_RESOURCE_POSTMAPPER);
return ResponseEntity.ok(mapped); return ResponseEntity.ok(mapped);
} }

View File

@ -88,6 +88,7 @@ public class HsBookingItemEntity implements Stringifyable, RbacObject {
@ManyToOne @ManyToOne
@JoinColumn(name = "parentitemuuid") @JoinColumn(name = "parentitemuuid")
// @Transient
private HsBookingItemEntity parentItem; private HsBookingItemEntity parentItem;
@Column(name = "type") @Column(name = "type")
@ -110,10 +111,12 @@ public class HsBookingItemEntity implements Stringifyable, RbacObject {
@OneToMany(cascade = CascadeType.REFRESH, orphanRemoval = true) @OneToMany(cascade = CascadeType.REFRESH, orphanRemoval = true)
@JoinColumn(name="parentitemuuid", referencedColumnName="uuid") @JoinColumn(name="parentitemuuid", referencedColumnName="uuid")
// @Transient
private List<HsBookingItemEntity> subBookingItems; private List<HsBookingItemEntity> subBookingItems;
@OneToMany(cascade = CascadeType.REFRESH, orphanRemoval = true) @OneToMany(cascade = CascadeType.REFRESH, orphanRemoval = true)
@JoinColumn(name="bookingitemuuid", referencedColumnName="uuid") @JoinColumn(name="bookingitemuuid", referencedColumnName="uuid")
// @Transient
private List<HsHostingAssetEntity> subHostingAssets; private List<HsHostingAssetEntity> subHostingAssets;
@Transient @Transient

View File

@ -3,9 +3,9 @@ package net.hostsharing.hsadminng.hs.booking.item.validators;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity; import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType; import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType;
import net.hostsharing.hsadminng.hs.validation.HsEntityValidator; import net.hostsharing.hsadminng.hs.validation.HsEntityValidator;
import net.hostsharing.hsadminng.hs.validation.MultiValidationException;
import net.hostsharing.hsadminng.hs.validation.ValidatableProperty; import net.hostsharing.hsadminng.hs.validation.ValidatableProperty;
import jakarta.validation.ValidationException;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@ -54,11 +54,8 @@ public class HsBookingItemEntityValidator extends HsEntityValidator<HsBookingIte
return HsBookingItemEntityValidator.forType(bookingItem.getType()).validate(bookingItem); return HsBookingItemEntityValidator.forType(bookingItem.getType()).validate(bookingItem);
} }
public static HsBookingItemEntity valid(final HsBookingItemEntity entityToSave) { public static HsBookingItemEntity validated(final HsBookingItemEntity entityToSave) {
final var violations = doValidate(entityToSave); MultiValidationException.throwInvalid(doValidate(entityToSave));
if (!violations.isEmpty()) {
throw new ValidationException(violations.toString());
}
return entityToSave; return entityToSave;
} }

View File

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

View File

@ -5,15 +5,16 @@ import net.hostsharing.hsadminng.hs.booking.item.validators.HsBookingItemEntityV
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity; import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType; import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType;
import net.hostsharing.hsadminng.hs.validation.HsEntityValidator; import net.hostsharing.hsadminng.hs.validation.HsEntityValidator;
import net.hostsharing.hsadminng.hs.validation.MultiValidationException;
import net.hostsharing.hsadminng.hs.validation.ValidatableProperty; import net.hostsharing.hsadminng.hs.validation.ValidatableProperty;
import jakarta.validation.ValidationException;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
import static java.lang.String.join;
import static java.util.Arrays.stream; import static java.util.Arrays.stream;
import static java.util.Collections.emptyList; import static java.util.Collections.emptyList;
import static java.util.Optional.ofNullable; import static java.util.Optional.ofNullable;
@ -52,11 +53,8 @@ public class HsHostingAssetEntityValidator extends HsEntityValidator<HsHostingAs
return HsHostingAssetEntityValidator.forType(hostingAsset.getType()).validate(hostingAsset); return HsHostingAssetEntityValidator.forType(hostingAsset.getType()).validate(hostingAsset);
} }
public static HsHostingAssetEntity valid(final HsHostingAssetEntity entityToSave) { public static HsHostingAssetEntity validated(final HsHostingAssetEntity entityToSave) {
final var violations = doValidate(entityToSave); MultiValidationException.throwInvalid(doValidate(entityToSave));
if (!violations.isEmpty()) {
throw new ValidationException(violations.toString());
}
return entityToSave; return entityToSave;
} }

View File

@ -98,7 +98,7 @@ public class HsOfficeCoopAssetsTransactionController implements HsOfficeCoopAsse
validateCreditTransaction(requestBody, violations); validateCreditTransaction(requestBody, violations);
validateAssetValue(requestBody, violations); validateAssetValue(requestBody, violations);
if (violations.size() > 0) { if (violations.size() > 0) {
throw new ValidationException("[" + join(", ", violations) + "]"); throw new ValidationException("[" + join(",\n", violations) + "]"); // FIXME: move the join into an exception subclass
} }
} }

View File

@ -100,7 +100,7 @@ public class HsOfficeCoopSharesTransactionController implements HsOfficeCoopShar
validateCancellationTransaction(requestBody, violations); validateCancellationTransaction(requestBody, violations);
validateshareCount(requestBody, violations); validateshareCount(requestBody, violations);
if (violations.size() > 0) { if (violations.size() > 0) {
throw new ValidationException("[" + join(", ", violations) + "]"); throw new ValidationException("[" + join(",\n", violations) + "]"); // FIXME: move the join into an exception subclass
} }
} }

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

@ -33,13 +33,13 @@ begin
managedServerUuid := uuid_generate_v4(); managedServerUuid := uuid_generate_v4();
insert insert
into hs_booking_item (uuid, projectuuid, type, parentitemuuid, caption, validity, resources) 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), 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, "HDD": 1024, "Traffic": 42 }'::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, "HDD": 1024, "Traffic": 42 }'::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, "HDD": 2924, "Traffic": 420 }'::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, "SDD": 512, "Traffic": 42 }'::jsonb), (managedServerUuid, relatedProject.uuid, 'MANAGED_SERVER', null, 'separate ManagedServer', daterange('20221001', null, '[]'), '{ "CPUs": 2, "RAM": 8, "SSD": 512, "Traffic": 500 }'::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(), null, 'MANAGED_WEBSPACE', managedServerUuid, 'some ManagedWebspace', daterange('20221001', null, '[]'), '{ "SSD": 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); (uuid_generate_v4(), relatedProject.uuid, 'MANAGED_WEBSPACE', null, 'some ManagedWebspace', daterange('20221001', null, '[]'), '{ "SSD": 512, "Traffic": 12, "Daemons": 2, "Multi": 4 }'::jsonb);
end; $$; end; $$;
--// --//

View File

@ -56,9 +56,9 @@ begin
insert into hs_hosting_asset insert into hs_hosting_asset
(uuid, bookingitemuuid, type, parentAssetUuid, assignedToAssetUuid, identifier, caption, config) (uuid, bookingitemuuid, type, parentAssetUuid, assignedToAssetUuid, identifier, caption, config)
values (managedServerUuid, relatedPrivateCloudBookingItem.uuid, 'MANAGED_SERVER', null, null, 'vm10' || debitorNumberSuffix, 'some ManagedServer', '{ "extra": 42 }'::jsonb), values (managedServerUuid, relatedPrivateCloudBookingItem.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(), relatedPrivateCloudBookingItem.uuid, 'CLOUD_SERVER', null, null, 'vm20' || debitorNumberSuffix, 'another CloudServer', '{ "extra": 42 }'::jsonb), (uuid_generate_v4(), relatedPrivateCloudBookingItem.uuid, 'CLOUD_SERVER', null, null, 'vm20' || debitorNumberSuffix, 'another CloudServer', '{}'::jsonb),
(managedWebspaceUuid, relatedManagedServerBookingItem.uuid, 'MANAGED_WEBSPACE', managedServerUuid, null, defaultPrefix || '01', 'some Webspace', '{ "extra": 42 }'::jsonb), (managedWebspaceUuid, relatedManagedServerBookingItem.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", "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); (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);
end; $$; end; $$;

View File

@ -81,7 +81,7 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
"validFrom": "2022-10-01", "validFrom": "2022-10-01",
"validTo": null, "validTo": null,
"resources": { "resources": {
"SDD": 512, "SSD": 512,
"Multi": 4, "Multi": 4,
"Daemons": 2, "Daemons": 2,
"Traffic": 12 "Traffic": 12
@ -94,9 +94,9 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
"validTo": null, "validTo": null,
"resources": { "resources": {
"RAM": 8, "RAM": 8,
"SDD": 512, "SSD": 512,
"CPUs": 2, "CPUs": 2,
"Traffic": 42 "Traffic": 500
} }
}, },
{ {
@ -105,10 +105,10 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
"validFrom": "2024-04-01", "validFrom": "2024-04-01",
"validTo": null, "validTo": null,
"resources": { "resources": {
"HDD": 10240, "SSD": 4000,
"SDD": 10240, "HDD": 10000,
"CPUs": 10, "CPUs": 10,
"Traffic": 42 "Traffic": 2000
} }
} }
] ]
@ -195,7 +195,7 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
"validFrom": "2022-10-01", "validFrom": "2022-10-01",
"validTo": null, "validTo": null,
"resources": { "resources": {
"SDD": 512, "SSD": 512,
"Multi": 4, "Multi": 4,
"Daemons": 2, "Daemons": 2,
"Traffic": 12 "Traffic": 12
@ -227,14 +227,16 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
void projectAdmin_canGetRelatedBookingItem() { void projectAdmin_canGetRelatedBookingItem() {
context.define("superuser-alex@hostsharing.net"); context.define("superuser-alex@hostsharing.net");
final var givenBookingItemUuid = bookingItemRepo.findByCaption("separate ManagedServer").stream() final var givenBookingItemUuid = bookingItemRepo.findByCaption("separate ManagedServer").stream()
.filter(bi -> belongsToDebitorWithDefaultPrefix(bi, "thi")) .filter(bi -> belongsToDebitorWithDefaultPrefix(bi, "sec"))
.map(HsBookingItemEntity::getUuid) .map(HsBookingItemEntity::getUuid)
.findAny().orElseThrow(); .findAny().orElseThrow();
generateRbacDiagramForObjectPermission(givenBookingItemUuid, "SELECT", "select");
RestAssured // @formatter:off RestAssured // @formatter:off
.given() .given()
.header("current-user", "superuser-alex@hostsharing.net") .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) .port(port)
.when() .when()
.get("http://localhost/api/hs/booking/items/" + givenBookingItemUuid) .get("http://localhost/api/hs/booking/items/" + givenBookingItemUuid)
@ -249,9 +251,9 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
"validTo": null, "validTo": null,
"resources": { "resources": {
"RAM": 8, "RAM": 8,
"SDD": 512, "SSD": 512,
"CPUs": 2, "CPUs": 2,
"Traffic": 42 "Traffic": 500
} }
} }
""")); // @formatter:on """)); // @formatter:on
@ -261,7 +263,7 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
return ofNullable(bi) return ofNullable(bi)
.map(HsBookingItemEntity::getProject) .map(HsBookingItemEntity::getProject)
.map(HsBookingProjectEntity::getDebitor) .map(HsBookingProjectEntity::getDebitor)
.map(bd -> bd.getDefaultPrefix().equals(defaultPrefix)) .filter(bd -> bd.getDefaultPrefix().equals(defaultPrefix))
.isPresent(); .isPresent();
} }
} }

View File

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

View File

@ -174,9 +174,9 @@ class HsBookingItemRepositoryIntegrationTest extends ContextBasedTestWithCleanup
// then // then
allTheseBookingItemsAreReturned( allTheseBookingItemsAreReturned(
result, 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_WEBSPACE, [2022-10-01,), some ManagedWebspace, { Daemons: 2, Multi: 4, SSD: 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, MANAGED_SERVER, [2022-10-01,), separate ManagedServer, { CPUs: 2, RAM: 8, SSD: 512, Traffic: 500 })",
"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, PRIVATE_CLOUD, [2024-04-01,), some PrivateCloud, { CPUs: 10, HDD: 10000, RAM: 32, SSD: 4000, Traffic: 2000 })");
} }
@Test @Test
@ -194,9 +194,9 @@ class HsBookingItemRepositoryIntegrationTest extends ContextBasedTestWithCleanup
// then: // then:
exactlyTheseBookingItemsAreReturned( exactlyTheseBookingItemsAreReturned(
result, 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_SERVER, [2022-10-01,), separate ManagedServer, { CPUs: 2, RAM: 8, SSD: 512, Traffic: 500 })",
"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, MANAGED_WEBSPACE, [2022-10-01,), some ManagedWebspace, { Daemons: 2, Multi: 4, SSD: 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, PRIVATE_CLOUD, [2024-04-01,), some PrivateCloud, { CPUs: 10, HDD: 10000, RAM: 32, SSD: 4000, Traffic: 2000 })");
} }
} }

View File

@ -11,7 +11,7 @@ import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.PRIVAT
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.CLOUD_SERVER; 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_SERVER;
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.MANAGED_WEBSPACE; import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.MANAGED_WEBSPACE;
import static net.hostsharing.hsadminng.hs.booking.item.validators.HsBookingItemEntityValidator.valid; 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.assertThat;
import static org.assertj.core.api.Assertions.catchThrowable; import static org.assertj.core.api.Assertions.catchThrowable;
@ -34,7 +34,7 @@ class HsBookingItemEntityValidatorUnitTest {
.build(); .build();
// when // when
final var result = catchThrowable( ()-> valid(cloudServerBookingItemEntity) ); final var result = catchThrowable( ()-> validated(cloudServerBookingItemEntity) );
// then // then
assertThat(result).isInstanceOf(ValidationException.class) assertThat(result).isInstanceOf(ValidationException.class)

View File

@ -151,7 +151,11 @@ class HsManagedServerBookingItemValidatorUnitTest {
), ),
generateDbUsersWithDatabases(3, HsHostingAssetType.MARIADB_USER, generateDbUsersWithDatabases(3, HsHostingAssetType.MARIADB_USER,
"xyz00_%c%c", "xyz00_%c%c",
1, HsHostingAssetType.MARIADB_DATABASE 2, HsHostingAssetType.MARIADB_DATABASE
),
generateDomainEmailSetupsWithEMailAddresses(26, HsHostingAssetType.DOMAIN_EMAIL_SETUP,
"%c%c.example.com",
10, HsHostingAssetType.EMAIL_ADDRESS
) )
)) ))
.build() .build()
@ -165,7 +169,8 @@ class HsManagedServerBookingItemValidatorUnitTest {
assertThat(result).containsExactlyInAnyOrder( assertThat(result).containsExactlyInAnyOrder(
"MultiOptions=1 allows at maximum 25 unix users, but 26 found", "MultiOptions=1 allows at maximum 25 unix users, but 26 found",
"MultiOptions=1 allows at maximum 5 database users, but 6 found", "MultiOptions=1 allows at maximum 5 database users, but 6 found",
"MultiOptions=1 allows at maximum 5 databases, but 6 found" "MultiOptions=1 allows at maximum 5 databases, but 9 found",
"MultiOptions=1 allows at maximum 250 databases, but 260 found"
); );
} }
@ -189,14 +194,32 @@ class HsManagedServerBookingItemValidatorUnitTest {
private List<HsHostingAssetEntity> generateDbUsersWithDatabases( private List<HsHostingAssetEntity> generateDbUsersWithDatabases(
final int userCount, final int userCount,
final HsHostingAssetType directAssetType, final HsHostingAssetType directAssetType,
final String directAssetIdentifierFormat, final int dbCount, final String directAssetIdentifierFormat,
final int dbCount,
final HsHostingAssetType subAssetType) { final HsHostingAssetType subAssetType) {
return IntStream.range(0, userCount) return IntStream.range(0, userCount)
.mapToObj(n -> HsHostingAssetEntity.builder() .mapToObj(n -> HsHostingAssetEntity.builder()
.type(directAssetType) .type(directAssetType)
.identifier(directAssetIdentifierFormat.formatted((n/'a')+'a', (n%'a')+'a')) .identifier(directAssetIdentifierFormat.formatted((n/'a')+'a', (n%'a')+'a'))
.subHostingAssets( .subHostingAssets(
generate(dbCount, subAssetType, "xyz00_%c%c%%c%%c".formatted((n/'a')+'a', (n%'a')+'a')) 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()) .build())
.toList(); .toList();

View File

@ -31,7 +31,6 @@ class HsManagedWebspaceBookingItemValidatorUnitTest {
.resources(Map.ofEntries( .resources(Map.ofEntries(
entry("CPUs", 2), entry("CPUs", 2),
entry("RAM", 25), entry("RAM", 25),
entry("SSD", 25),
entry("Traffic", 250), entry("Traffic", 250),
entry("SLA-EMail", true) entry("SLA-EMail", true)
)) ))
@ -42,10 +41,11 @@ class HsManagedWebspaceBookingItemValidatorUnitTest {
// then // then
assertThat(result).containsExactlyInAnyOrder( assertThat(result).containsExactlyInAnyOrder(
"D-12345:Test-Project:Test Managed-Webspace.resources.SLA-EMail is not expected but is set to 'true'",
"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.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.RAM is not expected but is set to '25'",
"D-12345:Test-Project:Test Managed-Webspace.resources.MultiOptions is required but missing"); "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 @Test
@ -58,9 +58,9 @@ class HsManagedWebspaceBookingItemValidatorUnitTest {
"{type=integer, propertyName=SSD, unit=GB, min=1, max=100, step=1, required=true, isTotalsValidator=false}", "{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=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=Traffic, unit=GB, min=10, max=1000, step=10, required=true, isTotalsValidator=false}",
"{type=integer, propertyName=MultiOptions, min=1, max=100, step=1, required=true, isTotalsValidator=false}", "{type=integer, propertyName=MultiOptions, min=1, max=100, step=1, required=false, defaultValue=1, isTotalsValidator=false}",
"{type=integer, propertyName=Daemons, min=0, max=10, required=false, 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=boolean, propertyName=Online Office Server, required=false, isTotalsValidator=false}",
"{type=enumeration, propertyName=SLA-Platform, values=[BASIC, EXT24H], required=false, isTotalsValidator=false}"); "{type=enumeration, propertyName=SLA-Platform, values=[BASIC, EXT24H], required=false, defaultValue=BASIC, isTotalsValidator=false}");
} }
} }

View File

@ -76,25 +76,19 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
"type": "MANAGED_WEBSPACE", "type": "MANAGED_WEBSPACE",
"identifier": "sec01", "identifier": "sec01",
"caption": "some Webspace", "caption": "some Webspace",
"config": { "config": {}
"extra": 42
}
}, },
{ {
"type": "MANAGED_WEBSPACE", "type": "MANAGED_WEBSPACE",
"identifier": "fir01", "identifier": "fir01",
"caption": "some Webspace", "caption": "some Webspace",
"config": { "config": {}
"extra": 42
}
}, },
{ {
"type": "MANAGED_WEBSPACE", "type": "MANAGED_WEBSPACE",
"identifier": "thi01", "identifier": "thi01",
"caption": "some Webspace", "caption": "some Webspace",
"config": { "config": {}
"extra": 42
}
} }
] ]
""")); """));
@ -123,7 +117,9 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
"identifier": "vm1011", "identifier": "vm1011",
"caption": "some ManagedServer", "caption": "some ManagedServer",
"config": { "config": {
"extra": 42 "monit_max_cpu_usage": 90,
"monit_max_ram_usage": 80,
"monit_max_ssd_usage": 70
} }
}, },
{ {
@ -131,7 +127,9 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
"identifier": "vm1012", "identifier": "vm1012",
"caption": "some ManagedServer", "caption": "some ManagedServer",
"config": { "config": {
"extra": 42 "monit_max_cpu_usage": 90,
"monit_max_ram_usage": 80,
"monit_max_ssd_usage": 70
} }
}, },
{ {
@ -139,7 +137,9 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
"identifier": "vm1013", "identifier": "vm1013",
"caption": "some ManagedServer", "caption": "some ManagedServer",
"config": { "config": {
"extra": 42 "monit_max_cpu_usage": 90,
"monit_max_ram_usage": 80,
"monit_max_ssd_usage": 70
} }
} }
] ]
@ -266,9 +266,14 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
.body("", lenientlyEquals(""" .body("", lenientlyEquals("""
{ {
"statusPhrase": "Bad Request", "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
} }
} }
@ -294,9 +299,7 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
.body("", lenientlyEquals(""" .body("", lenientlyEquals("""
{ {
"caption": "some ManagedServer", "caption": "some ManagedServer",
"config": { "config": {}
"extra": 42
}
} }
""")); // @formatter:on """)); // @formatter:on
} }
@ -339,9 +342,7 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
{ {
"identifier": "vm1013", "identifier": "vm1013",
"caption": "some ManagedServer", "caption": "some ManagedServer",
"config": { "config": {}
"extra": 42
}
} }
""")); // @formatter:on """)); // @formatter:on
} }

View File

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

View File

@ -55,56 +55,54 @@ class HsHostingAssetPropsControllerAcceptanceTest {
{ {
"type": "integer", "type": "integer",
"propertyName": "monit_min_free_ssd", "propertyName": "monit_min_free_ssd",
"required": false,
"unit": null,
"min": 1, "min": 1,
"max": 1000, "max": 1000,
"step": null "required": false,
"isTotalsValidator": false
}, },
{ {
"type": "integer", "type": "integer",
"propertyName": "monit_min_free_hdd", "propertyName": "monit_min_free_hdd",
"required": false,
"unit": null,
"min": 1, "min": 1,
"max": 4000, "max": 4000,
"step": null "required": false,
"isTotalsValidator": false
}, },
{ {
"type": "integer", "type": "integer",
"propertyName": "monit_max_ssd_usage", "propertyName": "monit_max_ssd_usage",
"required": true,
"unit": "%", "unit": "%",
"min": 10, "min": 10,
"max": 100, "max": 100,
"step": null "required": true,
"isTotalsValidator": false
}, },
{ {
"type": "integer", "type": "integer",
"propertyName": "monit_max_hdd_usage", "propertyName": "monit_max_hdd_usage",
"required": false,
"unit": "%", "unit": "%",
"min": 10, "min": 10,
"max": 100, "max": 100,
"step": null "required": false,
"isTotalsValidator": false
}, },
{ {
"type": "integer", "type": "integer",
"propertyName": "monit_max_cpu_usage", "propertyName": "monit_max_cpu_usage",
"required": true,
"unit": "%", "unit": "%",
"min": 10, "min": 10,
"max": 100, "max": 100,
"step": null "required": true,
"isTotalsValidator": false
}, },
{ {
"type": "integer", "type": "integer",
"propertyName": "monit_max_ram_usage", "propertyName": "monit_max_ram_usage",
"required": true,
"unit": "%", "unit": "%",
"min": 10, "min": 10,
"max": 100, "max": 100,
"step": null "required": true,
"isTotalsValidator": false
} }
] ]
""")); """));

View File

@ -164,9 +164,9 @@ class HsHostingAssetRepositoryIntegrationTest extends ContextBasedTestWithCleanu
// then // then
allTheseServersAreReturned( allTheseServersAreReturned(
result, result,
"HsHostingAssetEntity(MANAGED_WEBSPACE, sec01, some Webspace, MANAGED_SERVER:vm1012, D-1000212:D-1000212 default project:separate ManagedServer, { extra: 42 })", "HsHostingAssetEntity(MANAGED_WEBSPACE, sec01, some Webspace, MANAGED_SERVER:vm1012, D-1000212:D-1000212 default project:separate ManagedServer)",
"HsHostingAssetEntity(MANAGED_WEBSPACE, thi01, some Webspace, MANAGED_SERVER:vm1013, D-1000313:D-1000313 default project:separate ManagedServer, { extra: 42 })", "HsHostingAssetEntity(MANAGED_WEBSPACE, thi01, some Webspace, MANAGED_SERVER:vm1013, D-1000313:D-1000313 default project:separate ManagedServer)",
"HsHostingAssetEntity(MANAGED_WEBSPACE, fir01, some Webspace, MANAGED_SERVER:vm1011, D-1000111:D-1000111 default project:separate ManagedServer, { extra: 42 })"); "HsHostingAssetEntity(MANAGED_WEBSPACE, fir01, some Webspace, MANAGED_SERVER:vm1011, D-1000111:D-1000111 default project:separate ManagedServer)");
} }
@Test @Test
@ -182,9 +182,9 @@ class HsHostingAssetRepositoryIntegrationTest extends ContextBasedTestWithCleanu
// then: // then:
exactlyTheseAssetsAreReturned( exactlyTheseAssetsAreReturned(
result, result,
"HsHostingAssetEntity(MANAGED_WEBSPACE, fir01, some Webspace, MANAGED_SERVER:vm1011, D-1000111:D-1000111 default project:separate ManagedServer, { extra: 42 })", "HsHostingAssetEntity(MANAGED_WEBSPACE, fir01, some Webspace, MANAGED_SERVER:vm1011, D-1000111:D-1000111 default project:separate ManagedServer)",
"HsHostingAssetEntity(MANAGED_SERVER, vm1011, some ManagedServer, D-1000111:D-1000111 default project:some PrivateCloud, { extra: 42 })", "HsHostingAssetEntity(MANAGED_SERVER, vm1011, some ManagedServer, D-1000111:D-1000111 default project:some PrivateCloud, { monit_max_cpu_usage: 90, monit_max_ram_usage: 80, monit_max_ssd_usage: 70 })",
"HsHostingAssetEntity(CLOUD_SERVER, vm2011, another CloudServer, D-1000111:D-1000111 default project:some PrivateCloud, { extra: 42 })"); "HsHostingAssetEntity(CLOUD_SERVER, vm2011, another CloudServer, D-1000111:D-1000111 default project:some PrivateCloud)");
} }
@Test @Test
@ -200,7 +200,7 @@ class HsHostingAssetRepositoryIntegrationTest extends ContextBasedTestWithCleanu
// then // then
allTheseServersAreReturned( allTheseServersAreReturned(
result, result,
"HsHostingAssetEntity(MANAGED_WEBSPACE, thi01, some Webspace, MANAGED_SERVER:vm1013, D-1000313:D-1000313 default project:separate ManagedServer, { extra: 42 })"); "HsHostingAssetEntity(MANAGED_WEBSPACE, thi01, some Webspace, MANAGED_SERVER:vm1013, D-1000313:D-1000313 default project:separate ManagedServer)");
} }
} }

View File

@ -6,7 +6,7 @@ import org.junit.jupiter.api.Test;
import jakarta.validation.ValidationException; import jakarta.validation.ValidationException;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_SERVER; import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_SERVER;
import static net.hostsharing.hsadminng.hs.hosting.asset.validators.HsHostingAssetEntityValidator.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.assertThat;
import static org.assertj.core.api.Assertions.catchThrowable; import static org.assertj.core.api.Assertions.catchThrowable;
@ -21,7 +21,7 @@ class HsHostingAssetEntityValidatorUnitTest {
.build(); .build();
// when // when
final var result = catchThrowable( ()-> valid(managedServerHostingAssetEntity) ); final var result = catchThrowable( ()-> validated(managedServerHostingAssetEntity) );
// then // then
assertThat(result).isInstanceOf(ValidationException.class) assertThat(result).isInstanceOf(ValidationException.class)