hosting-asset-data-migration (#79)

Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #79
Reviewed-by: Marc Sandlus <marc.sandlus@hostsharing.net>
This commit is contained in:
Michael Hoennig 2024-07-22 11:30:33 +02:00
parent c191af2ea1
commit 4d27a98c9a
51 changed files with 1761 additions and 602 deletions

View File

@ -1,4 +1,4 @@
# For using the alias import-office-tables,
# For using the alias gw-importOfficeData or gw-importHostingAssets,
# copy the file .tc-environment to .environment (ignored by git)
# and amend them according to your external DB.
@ -42,19 +42,29 @@ postgresAutodoc () {
}
alias postgres-autodoc=postgresAutodoc
function importOfficeData() {
source .tc-environment
if [ -f .environment ]; then
source .environment
fi
function importLegacyData() {
export target=$1
if [ -z "$target" ]; then
echo "importLegacyData needs target argument, but none was given" >&2
else
source .tc-environment
echo "using environment (with ending ';' for use in IntelliJ IDEA):"
set | grep ^HSADMINNG_ | sed 's/$/;/'
if [ -f .environment ]; then
source .environment
fi
./gradlew importOfficeData --rerun
echo "using environment (with ending ';' for use in IntelliJ IDEA):"
echo "--- BEGIN: ---"
set | grep ^HSADMINNG_ | sed 's/$/;/'
echo "---- END. ----"
echo
echo ./gradlew $target --rerun
./gradlew $target --rerun
fi
}
alias gw-importOfficeData=importOfficeData
alias gw-importOfficeData='importLegacyData importOfficeData'
alias gw-importHostingAssets='importLegacyData importHostingAssets'
alias podman-start='systemctl --user enable --now podman.socket && systemctl --user status podman.socket && ls -la /run/user/$UID/podman/podman.sock'
alias podman-stop='systemctl --user disable --now podman.socket && systemctl --user status podman.socket && ls -la /run/user/$UID/podman/podman.sock'

5
.gitignore vendored
View File

@ -136,4 +136,9 @@ Desktop.ini
# ESLint
######################
.eslintcache
######################
# Project Related
######################
/.environment*
/src/test/resources/migration-prod/*

View File

@ -318,7 +318,7 @@ jacocoTestCoverageVerification {
tasks.register('importOfficeData', Test) {
useJUnitPlatform {
includeTags 'import'
includeTags 'importOfficeData'
}
group 'verification'
@ -327,6 +327,16 @@ tasks.register('importOfficeData', Test) {
mustRunAfter spotlessJava
}
tasks.register('importHostingAssets', Test) {
useJUnitPlatform {
includeTags 'importHostingAssets'
}
group 'verification'
description 'run the import jobs as tests'
mustRunAfter spotlessJava
}
// pitest mutation testing
pitest {

View File

@ -18,7 +18,7 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
// a partial HsOfficeDebitorEntity to reduce the number of SQL queries to load the entity
@Entity
@Table(name = "hs_booking_debitor_rv")
@Table(name = "hs_booking_debitor_xv")
@Getter
@Builder
@NoArgsConstructor

View File

@ -184,7 +184,9 @@ public class HsBookingItemEntity implements Stringifyable, RbacObject, Propertie
}
public HsBookingProjectEntity getRelatedProject() {
return project != null ? project : parentItem.getRelatedProject();
return project != null ? project
: parentItem != null ? parentItem.getRelatedProject()
: null; // can be the case for technical assets like IP-numbers
}
public static RbacView rbac() {

View File

@ -22,6 +22,10 @@ public class HsBookingItemEntityValidator extends HsEntityValidator<HsBookingIte
@Override
public List<String> validateEntity(final HsBookingItemEntity bookingItem) {
// TODO.impl: HsBookingItemType could do this similar to HsHostingAssetType
if ( bookingItem.getParentItem() == null && bookingItem.getProject() == null) {
return List.of(bookingItem + ".'parentItem' or .'project' expected to be set, but both are null");
}
return enrich(prefix(bookingItem.toShortString(), "resources"), super.validateProperties(bookingItem));
}

View File

@ -11,11 +11,12 @@ class HsCloudServerBookingItemValidator extends HsBookingItemEntityValidator {
// @formatter:off
booleanProperty("active") .withDefault(true),
integerProperty("CPUs") .min( 1) .max( 32) .required(),
integerProperty("RAM").unit("GB") .min( 1) .max( 128) .required(),
integerProperty("SSD").unit("GB") .min( 0) .max( 1000) .step(25).required(), // (1)
integerProperty("HDD").unit("GB") .min( 0) .max( 4000) .step(250).withDefault(0),
integerProperty("Traffic").unit("GB") .min(250) .max(10000) .step(250).required(),
integerProperty("CPU") .min( 1) .max( 32) .required(),
integerProperty("RAM").unit("GB") .min( 1) .max( 8192) .required(),
integerProperty("SSD").unit("GB") .min( 25) .max( 1000) .step(25).requiresAtLeastOneOf("SDD", "HDD"),
integerProperty("HDD").unit("GB") .min(250) .max( 4000) .step(250).requiresAtLeastOneOf("SSD", "HDD"),
integerProperty("Traffic").unit("GB") .min(250) .max(10000) .step(250).requiresAtMaxOneOf("Bandwidth", "Traffic"),
integerProperty("Bandwidth").unit("GB") .min(250) .max(10000) .step(250).requiresAtMaxOneOf("Bandwidth", "Traffic"), // TODO.spec
enumerationProperty("SLA-Infrastructure").values("BASIC", "EXT8H", "EXT4H", "EXT2H").optional()
// @formatter:on

View File

@ -10,11 +10,12 @@ class HsManagedServerBookingItemValidator extends HsBookingItemEntityValidator {
HsManagedServerBookingItemValidator() {
super(
integerProperty("CPUs").min(1).max(32).required(),
integerProperty("CPU").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().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),
integerProperty("SSD").unit("GB").min(25).max(2000).step(25).requiresAtLeastOneOf("SSD", "HDD").asTotalLimit().withThreshold(200),
integerProperty("HDD").unit("GB").min(250).max(10000).step(250).requiresAtLeastOneOf("SSD", "HDD").asTotalLimit().withThreshold(200),
integerProperty("Traffic").unit("GB").min(250).max(64000).step(250).requiresAtMaxOneOf("Bandwidth", "Traffic").asTotalLimit().withThreshold(200),
integerProperty("Bandwidth").unit("GB").min(250).max(64000).step(250).requiresAtMaxOneOf("Bandwidth", "Traffic").asTotalLimit().withThreshold(200), // TODO.spec
enumerationProperty("SLA-Platform").values("BASIC", "EXT8H", "EXT4H", "EXT2H").withDefault("BASIC"),
booleanProperty("SLA-EMail").falseIf("SLA-Platform", "BASIC").withDefault(false),
booleanProperty("SLA-Maria").falseIf("SLA-Platform", "BASIC").optional(),

View File

@ -23,16 +23,17 @@ 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(),
integerProperty("SSD").unit("GB").min(1).max(2000).step(1).required(),
integerProperty("HDD").unit("GB").min(0).max(10000).step(10).optional(),
integerProperty("Traffic").unit("GB").min(10).max(64000).step(10).requiresAtMaxOneOf("Bandwidth", "Traffic"),
integerProperty("Bandwidth").unit("GB").min(10).max(1000).step(10).requiresAtMaxOneOf("Bandwidth", "Traffic"), // TODO.spec
integerProperty("Multi").min(1).max(100).step(1).withDefault(1)
.eachComprising( 25, unixUsers())
.eachComprising( 5, databaseUsers())
.eachComprising( 5, databases())
.eachComprising(250, eMailAddresses()),
integerProperty("Daemons").min(0).max(10).withDefault(0),
booleanProperty("Online Office Server").optional(),
integerProperty("Daemons").min(0).max(16).withDefault(0),
booleanProperty("Online Office Server").optional(), // TODO.impl: shorten to "Office"
enumerationProperty("SLA-Platform").values("BASIC", "EXT24H").withDefault("BASIC")
);
}

View File

@ -7,15 +7,16 @@ class HsPrivateCloudBookingItemValidator extends HsBookingItemEntityValidator {
HsPrivateCloudBookingItemValidator() {
super(
// @formatter:off
integerProperty("CPUs") .min( 1).max( 128).required().asTotalLimit(),
integerProperty("CPU") .min( 1).max( 128).required().asTotalLimit(),
integerProperty("RAM").unit("GB") .min( 1).max( 512).required().asTotalLimit(),
integerProperty("SSD").unit("GB") .min( 25).max( 4000).step(25).required().asTotalLimit(),
integerProperty("HDD").unit("GB") .min( 0).max(16000).step(250).withDefault(0).asTotalLimit(),
integerProperty("Traffic").unit("GB") .min(250).max(40000).step(250).required().asTotalLimit(),
integerProperty("SSD").unit("GB") .min( 25).max( 4000).step(25).requiresAtLeastOneOf("SSD", "HDD").asTotalLimit(),
integerProperty("HDD").unit("GB") .min(250).max(16000).step(250).requiresAtLeastOneOf("SSD", "HDD").asTotalLimit(),
integerProperty("Traffic").unit("GB") .min(250).max(64000).step(250).requiresAtMaxOneOf("Bandwidth", "Traffic").asTotalLimit(),
integerProperty("Bandwidth").unit("GB") .min(250).max(64000).step(250).requiresAtMaxOneOf("Bandwidth", "Traffic").asTotalLimit(), // TODO.spec
// Alternatively we could specify it similarly to "Multi" option but exclusively counting:
// integerProperty("Resource-Points") .min(4).max(100).required()
// .each("CPUs").countsAs(64)
// .each("CPU").countsAs(64)
// .each("RAM").countsAs(64)
// .each("SSD").countsAs(18)
// .each("HDD").countsAs(2)

View File

@ -8,6 +8,7 @@ import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
import net.hostsharing.hsadminng.hs.booking.project.HsBookingProjectEntity;
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity;
import net.hostsharing.hsadminng.hs.validation.PropertiesProvider;
import net.hostsharing.hsadminng.mapper.PatchableMapWrapper;
@ -38,6 +39,7 @@ import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import static java.util.Collections.emptyMap;
@ -108,7 +110,7 @@ public class HsHostingAssetEntity implements Stringifyable, RbacObject, Properti
private HsOfficeContactEntity alarmContact;
@OneToMany(cascade = CascadeType.REFRESH, orphanRemoval = true, fetch = FetchType.LAZY)
@JoinColumn(name="parentassetuuid", referencedColumnName="uuid")
@JoinColumn(name = "parentassetuuid", referencedColumnName = "uuid")
private List<HsHostingAssetEntity> subHostingAssets;
@Column(name = "identifier")
@ -134,12 +136,20 @@ public class HsHostingAssetEntity implements Stringifyable, RbacObject, Properti
this.isLoaded = true;
}
public HsBookingProjectEntity getRelatedProject() {
return Optional.ofNullable(bookingItem)
.map(HsBookingItemEntity::getRelatedProject)
.orElseGet(() -> Optional.ofNullable(parentAsset)
.map(HsHostingAssetEntity::getRelatedProject)
.orElse(null));
}
public PatchableMapWrapper<Object> getConfig() {
return PatchableMapWrapper.of(configWrapper, (newWrapper) -> {configWrapper = newWrapper; }, config );
return PatchableMapWrapper.of(configWrapper, (newWrapper) -> {configWrapper = newWrapper;}, config);
}
public void putConfig(Map<String, Object> newConfig) {
PatchableMapWrapper.of(configWrapper, (newWrapper) -> {configWrapper = newWrapper; }, config).assign(newConfig);
PatchableMapWrapper.of(configWrapper, (newWrapper) -> {configWrapper = newWrapper;}, config).assign(newConfig);
}
@Override
@ -150,20 +160,19 @@ public class HsHostingAssetEntity implements Stringifyable, RbacObject, Properti
@Override
public Object getContextValue(final String propName) {
final var v = config.get(propName);
if (v!= null) {
if (v != null) {
return v;
}
if (bookingItem!=null) {
if (bookingItem != null) {
return bookingItem.getResources().get(propName);
}
if (parentAsset!=null && parentAsset.getBookingItem()!=null) {
if (parentAsset != null && parentAsset.getBookingItem() != null) {
return parentAsset.getBookingItem().getResources().get(propName);
}
return emptyMap();
}
@Override
public String toString() {
return stringify.apply(this);
@ -182,9 +191,9 @@ public class HsHostingAssetEntity implements Stringifyable, RbacObject, Properti
.toRole(GLOBAL, ADMIN).grantPermission(INSERT) // TODO.impl: Why is this necessary to insert test data?
.importEntityAlias("bookingItem", HsBookingItemEntity.class, usingDefaultCase(),
dependsOnColumn("bookingItemUuid"),
directlyFetchedByDependsOnColumn(),
NULLABLE)
dependsOnColumn("bookingItemUuid"),
directlyFetchedByDependsOnColumn(),
NULLABLE)
.importEntityAlias("parentAsset", HsHostingAssetEntity.class, usingDefaultCase(),
dependsOnColumn("parentAssetUuid"),
@ -202,7 +211,8 @@ public class HsHostingAssetEntity implements Stringifyable, RbacObject, Properti
directlyFetchedByDependsOnColumn(),
NULLABLE)
.switchOnColumn("type",
.switchOnColumn(
"type",
inCaseOf("DOMAIN_SETUP", then -> {
then.toRole(GLOBAL, GUEST).grantPermission(INSERT);
})
@ -231,7 +241,14 @@ public class HsHostingAssetEntity implements Stringifyable, RbacObject, Properti
with.permission(SELECT);
})
.limitDiagramTo("asset", "bookingItem", "bookingItem.debitorRel", "parentAsset", "assignedToAsset", "alarmContact", "global");
.limitDiagramTo(
"asset",
"bookingItem",
"bookingItem.debitorRel",
"parentAsset",
"assignedToAsset",
"alarmContact",
"global");
}
public static void main(String[] args) throws IOException {

View File

@ -18,7 +18,7 @@ class HsManagedWebspaceHostingAssetValidator extends HostingAssetEntityValidator
protected Pattern identifierPattern(final HsHostingAssetEntity assetEntity) {
final var prefixPattern =
!assetEntity.isLoaded()
? assetEntity.getParentAsset().getBookingItem().getProject().getDebitor().getDefaultPrefix()
? assetEntity.getRelatedProject().getDebitor().getDefaultPrefix()
: "[a-z][a-z0-9][a-z0-9]";
return Pattern.compile("^" + prefixPattern + "[0-9][0-9]$");
}

View File

@ -68,7 +68,7 @@ public class HsOfficeMembershipEntity implements RbacObject, Stringifyable {
private static Stringify<HsOfficeMembershipEntity> stringify = stringify(HsOfficeMembershipEntity.class)
.withProp(e -> MEMBER_NUMBER_TAG + e.getMemberNumber())
.withProp(e -> e.getPartner().toShortString())
.withProp(HsOfficeMembershipEntity::getPartner)
.withProp(e -> e.getValidity().asString())
.withProp(HsOfficeMembershipEntity::getStatus)
.quotedValues(false);

View File

@ -13,10 +13,12 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import static java.lang.Boolean.FALSE;
@ -30,7 +32,7 @@ import static org.apache.commons.lang3.ObjectUtils.isArray;
public abstract class ValidatableProperty<P extends ValidatableProperty<?, ?>, T> {
protected static final String[] KEY_ORDER_HEAD = Array.of("propertyName");
protected static final String[] KEY_ORDER_TAIL = Array.of("required", "defaultValue", "readOnly", "writeOnly", "computed", "isTotalsValidator", "thresholdPercentage");
protected static final String[] KEY_ORDER_TAIL = Array.of("required", "requiresAtLeastOneOf", "requiresAtMaxOneOf", "defaultValue", "readOnly", "writeOnly", "computed", "isTotalsValidator", "thresholdPercentage");
protected static final String[] KEY_ORDER = Array.join(KEY_ORDER_HEAD, KEY_ORDER_TAIL);
final Class<T> type;
@ -40,6 +42,8 @@ public abstract class ValidatableProperty<P extends ValidatableProperty<?, ?>, T
private final String[] keyOrder;
private Boolean required;
private Set<String> requiresAtLeastOneOf;
private Set<String> requiresAtMaxOneOf;
private T defaultValue;
@JsonIgnore
@ -100,9 +104,19 @@ protected void setDeferredInit(final Function<ValidatableProperty<?, ?>[], T[]>
return self();
}
public ValidatableProperty<P, T> optional() {
public P optional() {
required = FALSE;
return this;
return self();
}
public P requiresAtLeastOneOf(final String... propNames) {
requiresAtLeastOneOf = new LinkedHashSet<>(List.of(propNames));
return self();
}
public P requiresAtMaxOneOf(final String... propNames) {
requiresAtMaxOneOf = new LinkedHashSet<>(List.of(propNames));
return self();
}
public P withDefault(final T value) {
@ -172,28 +186,57 @@ protected void setDeferredInit(final Function<ValidatableProperty<?, ?>[], T[]>
final var result = new ArrayList<String>();
final var props = propsProvider.directProps();
final var propValue = props.get(propertyName);
if (propValue == null) {
if (required) {
if (required == TRUE) {
result.add(propertyName + "' is required but missing");
}
validateRequiresAtLeastOneOf(result, propsProvider);
}
if (propValue != null){
validateRequiresAtMaxOneOf(result, propsProvider);
if ( type.isInstance(propValue)) {
//noinspection unchecked
validate(result, (T) propValue, propsProvider);
} else {
result.add(propertyName + "' is expected to be of type " + type.getSimpleName() + ", " +
"but is of type " + propValue.getClass().getSimpleName() + "");
"but is of type " + propValue.getClass().getSimpleName());
}
}
return result;
}
private void validateRequiresAtLeastOneOf(final ArrayList<String> result, final PropertiesProvider propsProvider) {
if (requiresAtLeastOneOf != null ) {
final var allPropNames = propsProvider.directProps().keySet();
final var entriesWithValue = allPropNames.stream()
.filter(name -> requiresAtLeastOneOf.contains(name))
.count();
if (entriesWithValue == 0) {
result.add(propertyName + "' is required once in group " + requiresAtLeastOneOf + " but missing");
}
}
}
private void validateRequiresAtMaxOneOf(final ArrayList<String> result, final PropertiesProvider propsProvider) {
if (requiresAtMaxOneOf != null) {
final var allPropNames = propsProvider.directProps().keySet();
final var entriesWithValue = allPropNames.stream()
.filter(name -> requiresAtMaxOneOf.contains(name))
.count();
if (entriesWithValue > 1) {
result.add(propertyName + "' is required at max once in group " + requiresAtMaxOneOf
+ " but multiple properties are set");
}
}
}
protected abstract void validate(final List<String> result, final T propValue, final PropertiesProvider propProvider);
public void verifyConsistency(final Map.Entry<? extends Enum<?>, ?> typeDef) {
if (required == null ) {
throw new IllegalStateException(typeDef.getKey() + "[" + propertyName + "] not fully initialized, please call either .required() or .optional()" );
if (required == null && requiresAtLeastOneOf == null && requiresAtMaxOneOf == null) {
throw new IllegalStateException(typeDef.getKey() + "[" + propertyName + "] not fully initialized, please call either .required(), .optional(), .withDefault(...), .requiresAtLeastOneOf(...) or .requiresAtMaxOneOf(...)" );
}
}

View File

@ -4,12 +4,12 @@
--changeset hs-booking-debitor-RESTRICTED-VIEW:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
create view hs_booking_debitor_rv as
create view hs_booking_debitor_xv as
select debitor.uuid,
debitor.version,
(partner.partnerNumber::varchar || debitor.debitorNumberSuffix)::numeric as debitorNumber,
debitor.defaultPrefix
from hs_office_debitor_rv debitor
from hs_office_debitor debitor
-- RBAC for debitor is sufficient, for faster access we are bypassing RBAC for the join tables
join hs_office_relation debitorRel on debitor.debitorReluUid=debitorRel.uuid
join hs_office_relation partnerRel on partnerRel.holderUuid=debitorRel.anchorUuid

View File

@ -33,11 +33,11 @@ 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, "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),
values (privateCloudUuid, relatedProject.uuid, 'PRIVATE_CLOUD', null, 'some PrivateCloud', daterange('20240401', null, '[]'), '{ "CPU": 10, "RAM": 32, "SSD": 4000, "HDD": 10000, "Traffic": 2000 }'::jsonb),
(uuid_generate_v4(), null, 'MANAGED_SERVER', privateCloudUuid, 'some ManagedServer', daterange('20230115', '20240415', '[)'), '{ "CPU": 2, "RAM": 4, "SSD": 500, "Traffic": 500 }'::jsonb),
(uuid_generate_v4(), null, 'CLOUD_SERVER', privateCloudUuid, 'test CloudServer', daterange('20230115', '20240415', '[)'), '{ "CPU": 2, "RAM": 4, "SSD": 750, "Traffic": 500 }'::jsonb),
(uuid_generate_v4(), null, 'CLOUD_SERVER', privateCloudUuid, 'prod CloudServer', daterange('20230115', '20240415', '[)'), '{ "CPU": 4, "RAM": 16, "SSD": 1000, "Traffic": 500 }'::jsonb),
(managedServerUuid, relatedProject.uuid, 'MANAGED_SERVER', null, 'separate ManagedServer', daterange('20221001', null, '[]'), '{ "CPU": 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

@ -42,7 +42,7 @@ create table if not exists hs_hosting_asset
alarmContactUuid uuid null references hs_office_contact(uuid) initially deferred,
constraint chk_hs_hosting_asset_has_booking_item_or_parent_asset
check (bookingItemUuid is not null or parentAssetUuid is not null or type='DOMAIN_SETUP')
check (bookingItemUuid is not null or parentAssetUuid is not null or type in ('DOMAIN_SETUP', 'IPV4_NUMBER', 'IPV6_NUMBER'))
);
--//

View File

@ -51,7 +51,7 @@ public class ArchitectureTest {
"..hs.office.coopshares",
"..hs.office.debitor",
"..hs.office.membership",
"..hs.office.migration",
"..hs.migration",
"..hs.office.partner",
"..hs.office.person",
"..hs.office.relation",
@ -156,6 +156,7 @@ public class ArchitectureTest {
"..hs.office.(*)..",
"..hs.booking.(*)..",
"..hs.hosting.(*)..",
"..hs.migration",
"..rbac.rbacgrant" // TODO.test: just because of RbacGrantsDiagramServiceIntegrationTest
);
@ -167,7 +168,8 @@ public class ArchitectureTest {
.resideInAnyPackage(
"..hs.booking.(*)..",
"..hs.hosting.(*)..",
"..hs.validation" // TODO.impl: Some Validators need to be refactored to booking package.
"..hs.validation", // TODO.impl: Some Validators need to be refactored to booking package.
"..hs.migration.."
);
@ArchTest
@ -177,7 +179,8 @@ public class ArchitectureTest {
.should().onlyBeAccessed().byClassesThat()
.resideInAnyPackage(
"..hs.hosting.(*)..",
"..hs.booking.(*).." // TODO.impl: fix this cyclic dependency
"..hs.booking.(*)..", // TODO.impl: fix this cyclic dependency
"..hs.migration.."
);
@ArchTest
@ -189,7 +192,7 @@ public class ArchitectureTest {
"..hs.office.bankaccount..",
"..hs.office.sepamandate..",
"..hs.office.debitor..",
"..hs.office.migration..");
"..hs.migration..");
@ArchTest
@SuppressWarnings("unused")
@ -199,7 +202,7 @@ public class ArchitectureTest {
.resideInAnyPackage(
"..hs.office.sepamandate..",
"..hs.office.debitor..",
"..hs.office.migration..");
"..hs.migration..");
@ArchTest
@SuppressWarnings("unused")
@ -212,7 +215,7 @@ public class ArchitectureTest {
"..hs.office.partner..",
"..hs.office.debitor..",
"..hs.office.membership..",
"..hs.office.migration..",
"..hs.migration..",
"..hs.hosting.asset.."
);
@ -227,7 +230,7 @@ public class ArchitectureTest {
"..hs.office.partner..",
"..hs.office.debitor..",
"..hs.office.membership..",
"..hs.office.migration..")
"..hs.migration..")
.orShould().haveNameNotMatching(".*Test$");
@ -239,7 +242,7 @@ public class ArchitectureTest {
.resideInAnyPackage(
"..hs.office.relation..",
"..hs.office.partner..",
"..hs.office.migration..")
"..hs.migration..")
.orShould().haveNameNotMatching(".*Test$");
@ArchTest
@ -251,7 +254,7 @@ public class ArchitectureTest {
"..hs.office.partner..",
"..hs.office.debitor..",
"..hs.office.membership..",
"..hs.office.migration..")
"..hs.migration..")
.orShould().haveNameNotMatching(".*Test$");
@ArchTest
@ -263,7 +266,7 @@ public class ArchitectureTest {
"..hs.office.membership..",
"..hs.office.coopassets..",
"..hs.office.coopshares..",
"..hs.office.migration..");
"..hs.migration..");
@ArchTest
@SuppressWarnings("unused")
@ -272,7 +275,7 @@ public class ArchitectureTest {
.should().onlyBeAccessed().byClassesThat()
.resideInAnyPackage(
"..hs.office.coopassets..",
"..hs.office.migration..");
"..hs.migration..");
@ArchTest
@SuppressWarnings("unused")
@ -281,14 +284,14 @@ public class ArchitectureTest {
.should().onlyBeAccessed().byClassesThat()
.resideInAnyPackage(
"..hs.office.coopshares..",
"..hs.office.migration..");
"..hs.migration..");
@ArchTest
@SuppressWarnings("unused")
public static final ArchRule hsOfficeMigrationPackageRule = classes()
.that().resideInAPackage("..hs.office.migration..")
.that().resideInAPackage("..hs.migration..")
.should().onlyBeAccessed().byClassesThat()
.resideInAnyPackage("..hs.office.migration..");
.resideInAnyPackage("..hs.migration..");
@ArchTest
@SuppressWarnings("unused")

View File

@ -101,7 +101,7 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
"resources": {
"RAM": 8,
"SSD": 500,
"CPUs": 2,
"CPU": 2,
"Traffic": 500
}
},
@ -114,7 +114,7 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
"HDD": 10000,
"RAM": 32,
"SSD": 4000,
"CPUs": 10,
"CPU": 10,
"Traffic": 2000
}
}
@ -148,7 +148,7 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
"type": "MANAGED_SERVER",
"caption": "some new booking",
"validTo": "{validTo}",
"resources": { "CPUs": 12, "RAM": 4, "SSD": 100, "Traffic": 250 }
"resources": { "CPU": 12, "RAM": 4, "SSD": 100, "Traffic": 250 }
}
"""
.replace("{projectUuid}", givenProject.getUuid().toString())
@ -166,7 +166,7 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
"caption": "some new booking",
"validFrom": "{today}",
"validTo": "{todayPlus1Month}",
"resources": { "CPUs": 12, "SSD": 100, "Traffic": 250 }
"resources": { "CPU": 12, "SSD": 100, "Traffic": 250 }
}
"""
.replace("{today}", LocalDate.now().toString())
@ -267,7 +267,7 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
"resources": {
"RAM": 8,
"SSD": 500,
"CPUs": 2,
"CPU": 2,
"Traffic": 500
}
}

View File

@ -92,7 +92,7 @@ class HsBookingItemControllerRestTest {
"caption": "some new booking",
"validTo": "{validTo}",
"garbage": "should not be accepted",
"resources": { "CPUs": 12, "RAM": 4, "SSD": 100, "Traffic": 250 }
"resources": { "CPU": 12, "RAM": 4, "SSD": 100, "Traffic": 250 }
}
"""
.replace("{projectUuid}", givenProjectUuid.toString())
@ -108,7 +108,7 @@ class HsBookingItemControllerRestTest {
"caption": "some new booking",
"validFrom": "{today}",
"validTo": "{todayPlus1Month}",
"resources": { "CPUs": 12, "SSD": 100, "Traffic": 250 }
"resources": { "CPU": 12, "SSD": 100, "Traffic": 250 }
}
"""
.replace("{today}", LocalDate.now().toString())
@ -141,7 +141,7 @@ class HsBookingItemControllerRestTest {
"type": "MANAGED_SERVER",
"caption": "some new booking",
"validFrom": "{validFrom}",
"resources": { "CPUs": 12, "RAM": 4, "SSD": 100, "Traffic": 250 }
"resources": { "CPU": 12, "RAM": 4, "SSD": 100, "Traffic": 250 }
}
"""
.replace("{projectUuid}", givenProjectUuid.toString())
@ -159,7 +159,7 @@ class HsBookingItemControllerRestTest {
"caption": "some new booking",
"validFrom": "{today}",
"validTo": null,
"resources": { "CPUs": 12, "SSD": 100, "Traffic": 250 }
"resources": { "CPU": 12, "SSD": 100, "Traffic": 250 }
}
"""
.replace("{today}", LocalDate.now().toString())

View File

@ -25,7 +25,7 @@ class HsBookingItemEntityUnitTest {
.type(HsBookingItemType.CLOUD_SERVER)
.caption("some caption")
.resources(Map.ofEntries(
entry("CPUs", 2),
entry("CPU", 2),
entry("SSD-storage", 512),
entry("HDD-storage", 2048)))
.validity(toPostgresDateRange(GIVEN_VALID_FROM, GIVEN_VALID_TO))
@ -53,7 +53,7 @@ class HsBookingItemEntityUnitTest {
void toStringContainsAllPropertiesAndResourcesSortedByKey() {
final var result = givenBookingItem.toString();
assertThat(result).isEqualToIgnoringWhitespace("HsBookingItemEntity(D-1234500:test project, CLOUD_SERVER, [2020-01-01,2031-01-01), some caption, { \"CPUs\": 2, \"HDD-storage\": 2048, \"SSD-storage\": 512 })");
assertThat(result).isEqualToIgnoringWhitespace("HsBookingItemEntity(D-1234500:test project, CLOUD_SERVER, [2020-01-01,2031-01-01), some caption, { \"CPU\": 2, \"HDD-storage\": 2048, \"SSD-storage\": 512 })");
}
@Test

View File

@ -171,8 +171,8 @@ class HsBookingItemRepositoryIntegrationTest extends ContextBasedTestWithCleanup
allTheseBookingItemsAreReturned(
result,
"HsBookingItemEntity(D-1000212:D-1000212 default project, MANAGED_WEBSPACE, [2022-10-01,), separate ManagedWebspace, { Daemons: 0, Multi: 1, SSD: 100, Traffic: 50 } )",
"HsBookingItemEntity(D-1000212:D-1000212 default project, MANAGED_SERVER, [2022-10-01,), separate ManagedServer, { CPUs: 2, RAM: 8, SSD: 500, Traffic: 500 } )",
"HsBookingItemEntity(D-1000212:D-1000212 default project, PRIVATE_CLOUD, [2024-04-01,), some PrivateCloud, { CPUs: 10, HDD: 10000, RAM: 32, SSD: 4000, Traffic: 2000 } )");
"HsBookingItemEntity(D-1000212:D-1000212 default project, MANAGED_SERVER, [2022-10-01,), separate ManagedServer, { CPU: 2, RAM: 8, SSD: 500, Traffic: 500 } )",
"HsBookingItemEntity(D-1000212:D-1000212 default project, PRIVATE_CLOUD, [2024-04-01,), some PrivateCloud, { CPU: 10, HDD: 10000, RAM: 32, SSD: 4000, Traffic: 2000 } )");
assertThat(result.stream().filter(bi -> bi.getRelatedHostingAsset()!=null).findAny())
.as("at least one relatedProject expected, but none found => fetching relatedProject does not work")
.isNotEmpty();
@ -194,8 +194,8 @@ class HsBookingItemRepositoryIntegrationTest extends ContextBasedTestWithCleanup
exactlyTheseBookingItemsAreReturned(
result,
"HsBookingItemEntity(D-1000111:D-1000111 default project, MANAGED_WEBSPACE, [2022-10-01,), separate ManagedWebspace, { Daemons: 0, Multi: 1, SSD: 100, Traffic: 50 } )",
"HsBookingItemEntity(D-1000111:D-1000111 default project, MANAGED_SERVER, [2022-10-01,), separate ManagedServer, { CPUs: 2, RAM: 8, SSD: 500, Traffic: 500 } )",
"HsBookingItemEntity(D-1000111:D-1000111 default project, PRIVATE_CLOUD, [2024-04-01,), some PrivateCloud, { CPUs: 10, HDD: 10000, RAM: 32, SSD: 4000, Traffic: 2000 } )");
"HsBookingItemEntity(D-1000111:D-1000111 default project, MANAGED_SERVER, [2022-10-01,), separate ManagedServer, { CPU: 2, RAM: 8, SSD: 500, Traffic: 500 } )",
"HsBookingItemEntity(D-1000111:D-1000111 default project, PRIVATE_CLOUD, [2024-04-01,), some PrivateCloud, { CPU: 10, HDD: 10000, RAM: 32, SSD: 4000, Traffic: 2000 } )");
}
}
@ -211,7 +211,7 @@ class HsBookingItemRepositoryIntegrationTest extends ContextBasedTestWithCleanup
final var result = jpaAttempt.transacted(() -> {
context("superuser-alex@hostsharing.net");
final var foundBookingItem = em.find(HsBookingItemEntity.class, givenBookingItemUuid);
foundBookingItem.getResources().put("CPUs", 2);
foundBookingItem.getResources().put("CPU", 2);
foundBookingItem.getResources().remove("SSD-storage");
foundBookingItem.getResources().put("HSD-storage", 2048);
foundBookingItem.setValidity(Range.closedOpen(
@ -336,7 +336,7 @@ class HsBookingItemRepositoryIntegrationTest extends ContextBasedTestWithCleanup
.validity(Range.closedOpen(
LocalDate.parse("2020-01-01"), LocalDate.parse("2023-01-01")))
.resources(Map.ofEntries(
entry("CPUs", 1),
entry("CPU", 1),
entry("SSD-storage", 256)))
.build();

View File

@ -17,7 +17,7 @@ public class TestHsBookingItem {
.type(HsBookingItemType.CLOUD_SERVER)
.caption("test cloud server booking item")
.resources(Map.ofEntries(
entry("CPUs", 2),
entry("CPU", 2),
entry("RAM", 4),
entry("SSD", 50),
entry("Traffic", 250)
@ -30,7 +30,7 @@ public class TestHsBookingItem {
.type(HsBookingItemType.MANAGED_SERVER)
.caption("test project booking item")
.resources(Map.ofEntries(
entry("CPUs", 2),
entry("CPU", 2),
entry("RAM", 4),
entry("SSD", 50),
entry("Traffic", 250)

View File

@ -38,7 +38,7 @@ class HsBookingItemEntityValidatorUnitTest {
// then
assertThat(result).isInstanceOf(ValidationException.class)
.hasMessageContaining(
"'D-12345:test project:Test-Server.resources.CPUs' is required but missing",
"'D-12345:test project:Test-Server.resources.CPU' is required but missing",
"'D-12345:test project:Test-Server.resources.RAM' is required but missing",
"'D-12345:test project:Test-Server.resources.SSD' is required but missing",
"'D-12345:test project:Test-Server.resources.Traffic' is required but missing");

View File

@ -33,7 +33,7 @@ class HsCloudServerBookingItemValidatorUnitTest {
.project(project)
.caption("Test-Server")
.resources(Map.ofEntries(
entry("CPUs", 2),
entry("CPU", 2),
entry("RAM", 25),
entry("SSD", 25),
entry("Traffic", 250),
@ -56,11 +56,12 @@ class HsCloudServerBookingItemValidatorUnitTest {
// then
assertThat(validator.properties()).map(Map::toString).containsExactlyInAnyOrder(
"{type=boolean, propertyName=active, defaultValue=true}",
"{type=integer, propertyName=CPUs, min=1, max=32, required=true}",
"{type=integer, propertyName=RAM, unit=GB, min=1, max=128, required=true}",
"{type=integer, propertyName=SSD, unit=GB, min=0, max=1000, step=25, required=true}",
"{type=integer, propertyName=HDD, unit=GB, min=0, max=4000, step=250, defaultValue=0}",
"{type=integer, propertyName=Traffic, unit=GB, min=250, max=10000, step=250, required=true}",
"{type=integer, propertyName=CPU, min=1, max=32, required=true}",
"{type=integer, propertyName=RAM, unit=GB, min=1, max=8192, required=true}",
"{type=integer, propertyName=SSD, unit=GB, min=25, max=1000, step=25, requiresAtLeastOneOf=[SDD, HDD]}",
"{type=integer, propertyName=HDD, unit=GB, min=250, max=4000, step=250, requiresAtLeastOneOf=[SSD, HDD]}",
"{type=integer, propertyName=Traffic, unit=GB, min=250, max=10000, step=250, requiresAtMaxOneOf=[Bandwidth, Traffic]}",
"{type=integer, propertyName=Bandwidth, unit=GB, min=250, max=10000, step=250, requiresAtMaxOneOf=[Bandwidth, Traffic]}",
"{type=enumeration, propertyName=SLA-Infrastructure, values=[BASIC, EXT8H, EXT4H, EXT2H]}");
}
@ -71,7 +72,7 @@ class HsCloudServerBookingItemValidatorUnitTest {
.type(CLOUD_SERVER)
.caption("Test Cloud-Server")
.resources(ofEntries(
entry("CPUs", 2),
entry("CPU", 2),
entry("RAM", 10),
entry("SSD", 50),
entry("Traffic", 2500)
@ -81,7 +82,7 @@ class HsCloudServerBookingItemValidatorUnitTest {
.type(MANAGED_SERVER)
.caption("Test Managed-Server")
.resources(ofEntries(
entry("CPUs", 3),
entry("CPU", 3),
entry("RAM", 20),
entry("SSD", 100),
entry("Traffic", 3000)
@ -92,7 +93,7 @@ class HsCloudServerBookingItemValidatorUnitTest {
.project(project)
.caption("Test Cloud")
.resources(ofEntries(
entry("CPUs", 4),
entry("CPU", 4),
entry("RAM", 20),
entry("SSD", 100),
entry("Traffic", 5000)
@ -110,7 +111,7 @@ class HsCloudServerBookingItemValidatorUnitTest {
// then
assertThat(result).containsExactlyInAnyOrder(
"'D-12345:Test-Project:Test Cloud.resources.CPUs' maximum total is 4, but actual total CPUs is 5",
"'D-12345:Test-Project:Test Cloud.resources.CPU' maximum total is 4, but actual total CPU is 5",
"'D-12345:Test-Project:Test Cloud.resources.RAM' maximum total is 20 GB, but actual total RAM is 30 GB",
"'D-12345:Test-Project:Test Cloud.resources.SSD' maximum total is 100 GB, but actual total SSD is 150 GB",
"'D-12345:Test-Project:Test Cloud.resources.Traffic' maximum total is 5000 GB, but actual total Traffic is 5500 GB"

View File

@ -40,7 +40,7 @@ class HsManagedServerBookingItemValidatorUnitTest {
.type(MANAGED_SERVER)
.project(project)
.resources(Map.ofEntries(
entry("CPUs", 2),
entry("CPU", 2),
entry("RAM", 25),
entry("SSD", 25),
entry("Traffic", 250),
@ -63,11 +63,12 @@ class HsManagedServerBookingItemValidatorUnitTest {
// then
assertThat(validator.properties()).map(Map::toString).containsExactlyInAnyOrder(
"{type=integer, propertyName=CPUs, min=1, max=32, required=true}",
"{type=integer, propertyName=CPU, min=1, max=32, required=true}",
"{type=integer, propertyName=RAM, unit=GB, min=1, max=128, required=true}",
"{type=integer, propertyName=SSD, unit=GB, min=25, max=1000, step=25, required=true, isTotalsValidator=true, thresholdPercentage=200}",
"{type=integer, propertyName=HDD, unit=GB, min=0, max=4000, step=250, defaultValue=0, isTotalsValidator=true, thresholdPercentage=200}",
"{type=integer, propertyName=Traffic, unit=GB, min=250, max=10000, step=250, required=true, isTotalsValidator=true, thresholdPercentage=200}",
"{type=integer, propertyName=SSD, unit=GB, min=25, max=2000, step=25, requiresAtLeastOneOf=[SSD, HDD], isTotalsValidator=true, thresholdPercentage=200}",
"{type=integer, propertyName=HDD, unit=GB, min=250, max=10000, step=250, requiresAtLeastOneOf=[SSD, HDD], isTotalsValidator=true, thresholdPercentage=200}",
"{type=integer, propertyName=Traffic, unit=GB, min=250, max=64000, step=250, requiresAtMaxOneOf=[Bandwidth, Traffic], isTotalsValidator=true, thresholdPercentage=200}",
"{type=integer, propertyName=Bandwidth, unit=GB, min=250, max=64000, step=250, requiresAtMaxOneOf=[Bandwidth, Traffic], isTotalsValidator=true, thresholdPercentage=200}",
"{type=enumeration, propertyName=SLA-Platform, values=[BASIC, EXT8H, EXT4H, EXT2H], defaultValue=BASIC}",
"{type=boolean, propertyName=SLA-EMail}", // TODO.impl: falseIf-validation is missing in output
"{type=boolean, propertyName=SLA-Maria}",
@ -82,7 +83,7 @@ class HsManagedServerBookingItemValidatorUnitTest {
final var subCloudServerBookingItemEntity = HsBookingItemEntity.builder()
.type(CLOUD_SERVER)
.resources(ofEntries(
entry("CPUs", 2),
entry("CPU", 2),
entry("RAM", 10),
entry("SSD", 50),
entry("Traffic", 2500)
@ -91,7 +92,7 @@ class HsManagedServerBookingItemValidatorUnitTest {
final HsBookingItemEntity subManagedServerBookingItemEntity = HsBookingItemEntity.builder()
.type(MANAGED_SERVER)
.resources(ofEntries(
entry("CPUs", 3),
entry("CPU", 3),
entry("RAM", 20),
entry("SSD", 100),
entry("Traffic", 3000)
@ -101,7 +102,7 @@ class HsManagedServerBookingItemValidatorUnitTest {
.type(PRIVATE_CLOUD)
.project(project)
.resources(ofEntries(
entry("CPUs", 4),
entry("CPU", 4),
entry("RAM", 20),
entry("SSD", 100),
entry("Traffic", 5000)
@ -120,7 +121,7 @@ class HsManagedServerBookingItemValidatorUnitTest {
// then
assertThat(result).containsExactlyInAnyOrder(
"'D-12345:Test-Project:null.resources.CPUs' maximum total is 4, but actual total CPUs is 5",
"'D-12345:Test-Project:null.resources.CPU' maximum total is 4, but actual total CPU is 5",
"'D-12345:Test-Project:null.resources.RAM' maximum total is 20 GB, but actual total RAM is 30 GB",
"'D-12345:Test-Project:null.resources.SSD' maximum total is 100 GB, but actual total SSD is 150 GB",
"'D-12345:Test-Project:null.resources.Traffic' maximum total is 5000 GB, but actual total Traffic is 5500 GB"

View File

@ -29,7 +29,7 @@ class HsManagedWebspaceBookingItemValidatorUnitTest {
.project(project)
.caption("Test Managed-Webspace")
.resources(Map.ofEntries(
entry("CPUs", 2),
entry("CPU", 2),
entry("RAM", 25),
entry("Traffic", 250),
entry("SLA-EMail", true)
@ -41,7 +41,7 @@ class HsManagedWebspaceBookingItemValidatorUnitTest {
// then
assertThat(result).containsExactlyInAnyOrder(
"'D-12345:Test-Project:Test Managed-Webspace.resources.CPUs' is not expected but is set to '2'",
"'D-12345:Test-Project:Test Managed-Webspace.resources.CPU' is not expected but is set to '2'",
"'D-12345:Test-Project:Test Managed-Webspace.resources.RAM' is not expected but is set to '25'",
"'D-12345:Test-Project:Test Managed-Webspace.resources.SSD' is required but missing",
"'D-12345:Test-Project:Test Managed-Webspace.resources.SLA-EMail' is not expected but is set to 'true'"
@ -55,11 +55,12 @@ class HsManagedWebspaceBookingItemValidatorUnitTest {
// then
assertThat(validator.properties()).map(Map::toString).containsExactlyInAnyOrder(
"{type=integer, propertyName=SSD, unit=GB, min=1, max=100, step=1, required=true}",
"{type=integer, propertyName=HDD, unit=GB, min=0, max=250, step=10}",
"{type=integer, propertyName=Traffic, unit=GB, min=10, max=1000, step=10, required=true}",
"{type=integer, propertyName=SSD, unit=GB, min=1, max=2000, step=1, required=true}",
"{type=integer, propertyName=HDD, unit=GB, min=0, max=10000, step=10}",
"{type=integer, propertyName=Traffic, unit=GB, min=10, max=64000, step=10, requiresAtMaxOneOf=[Bandwidth, Traffic]}",
"{type=integer, propertyName=Bandwidth, unit=GB, min=10, max=1000, step=10, requiresAtMaxOneOf=[Bandwidth, Traffic]}",
"{type=integer, propertyName=Multi, min=1, max=100, step=1, defaultValue=1}",
"{type=integer, propertyName=Daemons, min=0, max=10, defaultValue=0}",
"{type=integer, propertyName=Daemons, min=0, max=16, defaultValue=0}",
"{type=boolean, propertyName=Online Office Server}",
"{type=enumeration, propertyName=SLA-Platform, values=[BASIC, EXT24H], defaultValue=BASIC}");
}

View File

@ -11,6 +11,7 @@ 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 net.hostsharing.hsadminng.hs.booking.project.TestHsBookingProject.TEST_PROJECT;
import static org.assertj.core.api.Assertions.assertThat;
class HsPrivateCloudBookingItemValidatorUnitTest {
@ -28,9 +29,10 @@ class HsPrivateCloudBookingItemValidatorUnitTest {
// given
final var privateCloudBookingItemEntity = HsBookingItemEntity.builder()
.type(PRIVATE_CLOUD)
.project(TEST_PROJECT)
.caption("myPC")
.resources(ofEntries(
entry("CPUs", 4),
entry("CPU", 4),
entry("RAM", 20),