diff --git a/.aliases b/.aliases index 991f34de..f215442d 100644 --- a/.aliases +++ b/.aliases @@ -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' diff --git a/.gitignore b/.gitignore index 522bf4fa..bd8ec3fe 100644 --- a/.gitignore +++ b/.gitignore @@ -136,4 +136,9 @@ Desktop.ini # ESLint ###################### .eslintcache + +###################### +# Project Related +###################### /.environment* +/src/test/resources/migration-prod/* diff --git a/build.gradle b/build.gradle index 63f4a996..41ceaed8 100644 --- a/build.gradle +++ b/build.gradle @@ -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 { diff --git a/src/main/java/net/hostsharing/hsadminng/hs/booking/debitor/HsBookingDebitorEntity.java b/src/main/java/net/hostsharing/hsadminng/hs/booking/debitor/HsBookingDebitorEntity.java index 3bc83ee6..052370f0 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/booking/debitor/HsBookingDebitorEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/booking/debitor/HsBookingDebitorEntity.java @@ -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 diff --git a/src/main/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemEntity.java b/src/main/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemEntity.java index ba1d2a7e..17b4fb65 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemEntity.java @@ -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() { diff --git a/src/main/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsBookingItemEntityValidator.java b/src/main/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsBookingItemEntityValidator.java index 82a20e54..7b596ad5 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsBookingItemEntityValidator.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsBookingItemEntityValidator.java @@ -22,6 +22,10 @@ public class HsBookingItemEntityValidator extends HsEntityValidator 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)); } diff --git a/src/main/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsCloudServerBookingItemValidator.java b/src/main/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsCloudServerBookingItemValidator.java index d673f01a..41fea174 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsCloudServerBookingItemValidator.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsCloudServerBookingItemValidator.java @@ -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 diff --git a/src/main/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsManagedServerBookingItemValidator.java b/src/main/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsManagedServerBookingItemValidator.java index a267b104..67cae520 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsManagedServerBookingItemValidator.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsManagedServerBookingItemValidator.java @@ -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(), diff --git a/src/main/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsManagedWebspaceBookingItemValidator.java b/src/main/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsManagedWebspaceBookingItemValidator.java index 1f094f36..ffa2b525 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsManagedWebspaceBookingItemValidator.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsManagedWebspaceBookingItemValidator.java @@ -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") ); } diff --git a/src/main/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsPrivateCloudBookingItemValidator.java b/src/main/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsPrivateCloudBookingItemValidator.java index 236a000a..e0e54f1e 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsPrivateCloudBookingItemValidator.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsPrivateCloudBookingItemValidator.java @@ -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) diff --git a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetEntity.java b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetEntity.java index 55b8d00e..96203a66 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetEntity.java @@ -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 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 getConfig() { - return PatchableMapWrapper.of(configWrapper, (newWrapper) -> {configWrapper = newWrapper; }, config ); + return PatchableMapWrapper.of(configWrapper, (newWrapper) -> {configWrapper = newWrapper;}, config); } public void putConfig(Map 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 { diff --git a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsManagedWebspaceHostingAssetValidator.java b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsManagedWebspaceHostingAssetValidator.java index 45e9e520..b56f8549 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsManagedWebspaceHostingAssetValidator.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsManagedWebspaceHostingAssetValidator.java @@ -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]$"); } diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipEntity.java b/src/main/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipEntity.java index 67050ccc..f71408a7 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipEntity.java @@ -68,7 +68,7 @@ public class HsOfficeMembershipEntity implements RbacObject, Stringifyable { private static Stringify 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); diff --git a/src/main/java/net/hostsharing/hsadminng/hs/validation/ValidatableProperty.java b/src/main/java/net/hostsharing/hsadminng/hs/validation/ValidatableProperty.java index 01daf6aa..eda673d1 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/validation/ValidatableProperty.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/validation/ValidatableProperty.java @@ -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

, 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 type; @@ -40,6 +42,8 @@ public abstract class ValidatableProperty

, T private final String[] keyOrder; private Boolean required; + private Set requiresAtLeastOneOf; + private Set requiresAtMaxOneOf; private T defaultValue; @JsonIgnore @@ -100,9 +104,19 @@ protected void setDeferredInit(final Function[], T[]> return self(); } - public ValidatableProperty 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[], T[]> final var result = new ArrayList(); 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 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 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 result, final T propValue, final PropertiesProvider propProvider); public void verifyConsistency(final Map.Entry, ?> 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(...)" ); } } diff --git a/src/main/resources/db/changelog/6-hs-booking/610-booking-debitor/6100-hs-booking-debitor.sql b/src/main/resources/db/changelog/6-hs-booking/610-booking-debitor/6100-hs-booking-debitor.sql index c9dc8287..72d9563f 100644 --- a/src/main/resources/db/changelog/6-hs-booking/610-booking-debitor/6100-hs-booking-debitor.sql +++ b/src/main/resources/db/changelog/6-hs-booking/610-booking-debitor/6100-hs-booking-debitor.sql @@ -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 diff --git a/src/main/resources/db/changelog/6-hs-booking/630-booking-item/6208-hs-booking-item-test-data.sql b/src/main/resources/db/changelog/6-hs-booking/630-booking-item/6208-hs-booking-item-test-data.sql index 3f007ab8..94c2e665 100644 --- a/src/main/resources/db/changelog/6-hs-booking/630-booking-item/6208-hs-booking-item-test-data.sql +++ b/src/main/resources/db/changelog/6-hs-booking/630-booking-item/6208-hs-booking-item-test-data.sql @@ -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; $$; diff --git a/src/main/resources/db/changelog/7-hs-hosting/701-hosting-asset/7010-hs-hosting-asset.sql b/src/main/resources/db/changelog/7-hs-hosting/701-hosting-asset/7010-hs-hosting-asset.sql index b54629ee..3b1b54d1 100644 --- a/src/main/resources/db/changelog/7-hs-hosting/701-hosting-asset/7010-hs-hosting-asset.sql +++ b/src/main/resources/db/changelog/7-hs-hosting/701-hosting-asset/7010-hs-hosting-asset.sql @@ -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')) ); --// diff --git a/src/test/java/net/hostsharing/hsadminng/arch/ArchitectureTest.java b/src/test/java/net/hostsharing/hsadminng/arch/ArchitectureTest.java index cc2dafa6..68a62763 100644 --- a/src/test/java/net/hostsharing/hsadminng/arch/ArchitectureTest.java +++ b/src/test/java/net/hostsharing/hsadminng/arch/ArchitectureTest.java @@ -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") diff --git a/src/test/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemControllerAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemControllerAcceptanceTest.java index 5edc23af..71753976 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemControllerAcceptanceTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemControllerAcceptanceTest.java @@ -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 } } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemControllerRestTest.java b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemControllerRestTest.java index 0fb0f6f0..4a50cb19 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemControllerRestTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemControllerRestTest.java @@ -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()) diff --git a/src/test/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemEntityUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemEntityUnitTest.java index 258b55b7..627eabc2 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemEntityUnitTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemEntityUnitTest.java @@ -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 diff --git a/src/test/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemRepositoryIntegrationTest.java b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemRepositoryIntegrationTest.java index 5e32e23d..eedfe603 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemRepositoryIntegrationTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemRepositoryIntegrationTest.java @@ -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(); diff --git a/src/test/java/net/hostsharing/hsadminng/hs/booking/item/TestHsBookingItem.java b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/TestHsBookingItem.java index bcb2baac..1d143ab3 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/booking/item/TestHsBookingItem.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/TestHsBookingItem.java @@ -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) diff --git a/src/test/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsBookingItemEntityValidatorUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsBookingItemEntityValidatorUnitTest.java index e784edec..c8383dc9 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsBookingItemEntityValidatorUnitTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsBookingItemEntityValidatorUnitTest.java @@ -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"); diff --git a/src/test/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsCloudServerBookingItemValidatorUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsCloudServerBookingItemValidatorUnitTest.java index b5307cd7..5646c2a3 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsCloudServerBookingItemValidatorUnitTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsCloudServerBookingItemValidatorUnitTest.java @@ -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" diff --git a/src/test/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsManagedServerBookingItemValidatorUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsManagedServerBookingItemValidatorUnitTest.java index 5f95e598..ab54f050 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsManagedServerBookingItemValidatorUnitTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsManagedServerBookingItemValidatorUnitTest.java @@ -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" diff --git a/src/test/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsManagedWebspaceBookingItemValidatorUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsManagedWebspaceBookingItemValidatorUnitTest.java index e75cd551..4e7dc561 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsManagedWebspaceBookingItemValidatorUnitTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsManagedWebspaceBookingItemValidatorUnitTest.java @@ -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}"); } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsPrivateCloudBookingItemValidatorUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsPrivateCloudBookingItemValidatorUnitTest.java index 2a100d2c..9f939d58 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsPrivateCloudBookingItemValidatorUnitTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsPrivateCloudBookingItemValidatorUnitTest.java @@ -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), entry("SSD", 100), entry("Traffic", 5000), @@ -42,7 +44,7 @@ class HsPrivateCloudBookingItemValidatorUnitTest { .type(MANAGED_SERVER) .caption("myMS-1") .resources(ofEntries( - entry("CPUs", 2), + entry("CPU", 2), entry("RAM", 10), entry("SSD", 50), entry("Traffic", 2500), @@ -54,7 +56,7 @@ class HsPrivateCloudBookingItemValidatorUnitTest { .type(CLOUD_SERVER) .caption("myMS-2") .resources(ofEntries( - entry("CPUs", 2), + entry("CPU", 2), entry("RAM", 10), entry("SSD", 50), entry("Traffic", 2500), @@ -80,7 +82,7 @@ class HsPrivateCloudBookingItemValidatorUnitTest { .type(PRIVATE_CLOUD) .caption("myPC") .resources(ofEntries( - entry("CPUs", 4), + entry("CPU", 4), entry("RAM", 20), entry("SSD", 100), entry("Traffic", 5000), @@ -92,7 +94,7 @@ class HsPrivateCloudBookingItemValidatorUnitTest { .type(MANAGED_SERVER) .caption("myMS-1") .resources(ofEntries( - entry("CPUs", 3), + entry("CPU", 3), entry("RAM", 20), entry("SSD", 100), entry("Traffic", 3000), @@ -104,7 +106,7 @@ class HsPrivateCloudBookingItemValidatorUnitTest { .type(CLOUD_SERVER) .caption("myMS-2") .resources(ofEntries( - entry("CPUs", 2), + entry("CPU", 2), entry("RAM", 10), entry("SSD", 50), entry("Traffic", 2500), @@ -124,7 +126,7 @@ class HsPrivateCloudBookingItemValidatorUnitTest { // then assertThat(result).containsExactlyInAnyOrder( - "'D-12345:Test-Project:myPC.resources.CPUs' maximum total is 4, but actual total CPUs is 5", + "'D-12345:Test-Project:myPC.resources.CPU' maximum total is 4, but actual total CPU is 5", "'D-12345:Test-Project:myPC.resources.RAM' maximum total is 20 GB, but actual total RAM is 30 GB", "'D-12345:Test-Project:myPC.resources.SSD' maximum total is 100 GB, but actual total SSD is 150 GB", "'D-12345:Test-Project:myPC.resources.Traffic' maximum total is 5000 GB, but actual total Traffic is 5500 GB", diff --git a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetControllerAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetControllerAcceptanceTest.java index 28933662..54edc9ef 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetControllerAcceptanceTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetControllerAcceptanceTest.java @@ -702,7 +702,7 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup context.define("superuser-alex@hostsharing.net"); final var project = projectRepo.findByCaption(projectCaption).getFirst(); final var resources = switch (bookingItemType) { - case MANAGED_SERVER -> Map.ofEntries(entry("CPUs", 1), + case MANAGED_SERVER -> Map.ofEntries(entry("CPU", 1), entry("RAM", 20), entry("SSD", 25), entry("Traffic", 250)); diff --git a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetEntityUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetEntityUnitTest.java index 6460ae39..cbc5c67e 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetEntityUnitTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetEntityUnitTest.java @@ -16,7 +16,7 @@ class HsHostingAssetEntityUnitTest { .identifier("vm1234") .caption("some managed asset") .config(Map.ofEntries( - entry("CPUs", 2), + entry("CPU", 2), entry("SSD-storage", 512), entry("HDD-storage", 2048))) .build(); @@ -27,7 +27,7 @@ class HsHostingAssetEntityUnitTest { .identifier("xyz00") .caption("some managed webspace") .config(Map.ofEntries( - entry("CPUs", 2), + entry("CPU", 2), entry("SSD-storage", 512), entry("HDD-storage", 2048))) .build(); @@ -58,7 +58,7 @@ class HsHostingAssetEntityUnitTest { void toStringContainsAllPropertiesAndResourcesSortedByKey() { assertThat(givenWebspace.toString()).isEqualToIgnoringWhitespace( - "HsHostingAssetEntity(MANAGED_WEBSPACE, xyz00, some managed webspace, MANAGED_SERVER:vm1234, D-1234500:test project:test cloud server booking item, { \"CPUs\": 2, \"HDD-storage\": 2048, \"SSD-storage\": 512 })"); + "HsHostingAssetEntity(MANAGED_WEBSPACE, xyz00, some managed webspace, MANAGED_SERVER:vm1234, D-1234500:test project:test cloud server booking item, { \"CPU\": 2, \"HDD-storage\": 2048, \"SSD-storage\": 512 })"); assertThat(givenUnixUser.toString()).isEqualToIgnoringWhitespace( "HsHostingAssetEntity(UNIX_USER, xyz00-web, some unix-user, MANAGED_WEBSPACE:xyz00, { \"HDD-hard-quota\": 512, \"HDD-soft-quota\": 256, \"SSD-hard-quota\": 256, \"SSD-soft-quota\": 128 })"); diff --git a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetRepositoryIntegrationTest.java b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetRepositoryIntegrationTest.java index fe48e886..99f0efd6 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetRepositoryIntegrationTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetRepositoryIntegrationTest.java @@ -263,7 +263,7 @@ class HsHostingAssetRepositoryIntegrationTest extends ContextBasedTestWithCleanu final var result = jpaAttempt.transacted(() -> { context("superuser-alex@hostsharing.net"); final var foundAsset = em.find(HsHostingAssetEntity.class, givenAssetUuid); - foundAsset.getConfig().put("CPUs", 2); + foundAsset.getConfig().put("CPU", 2); foundAsset.getConfig().remove("SSD-storage"); foundAsset.getConfig().put("HSD-storage", 2048); return toCleanup(assetRepo.save(foundAsset)); @@ -404,7 +404,7 @@ class HsHostingAssetRepositoryIntegrationTest extends ContextBasedTestWithCleanu .identifier(identifier) .caption("some temp cloud asset") .config(Map.ofEntries( - entry("CPUs", 1), + entry("CPU", 1), entry("SSD-storage", 256))) .build(); diff --git a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetTypeUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetTypeUnitTest.java index c0b97d1f..9e518831 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetTypeUnitTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetTypeUnitTest.java @@ -12,9 +12,9 @@ class HsHostingAssetTypeUnitTest { assertThat(result).isEqualTo(""" ## HostingAsset Type Structure - - - ### Webspace+Server + + + ### Server+Webspace ```plantuml @startuml @@ -35,6 +35,12 @@ class HsHostingAssetTypeUnitTest { entity HA_IPV6_NUMBER } + package Webspace #99bcdb { + entity HA_MANAGED_WEBSPACE + entity HA_UNIX_USER + entity HA_EMAIL_ALIAS + } + } BI_CLOUD_SERVER *--> BI_PRIVATE_CLOUD @@ -43,10 +49,16 @@ class HsHostingAssetTypeUnitTest { HA_CLOUD_SERVER *==> BI_CLOUD_SERVER HA_MANAGED_SERVER *==> BI_MANAGED_SERVER + HA_MANAGED_WEBSPACE *==> BI_MANAGED_WEBSPACE + HA_MANAGED_WEBSPACE o..> HA_MANAGED_SERVER + HA_UNIX_USER *==> HA_MANAGED_WEBSPACE + HA_EMAIL_ALIAS *==> HA_MANAGED_WEBSPACE HA_IPV4_NUMBER o..> HA_CLOUD_SERVER HA_IPV4_NUMBER o..> HA_MANAGED_SERVER + HA_IPV4_NUMBER o..> HA_MANAGED_WEBSPACE HA_IPV6_NUMBER o..> HA_CLOUD_SERVER HA_IPV6_NUMBER o..> HA_MANAGED_SERVER + HA_IPV6_NUMBER o..> HA_MANAGED_WEBSPACE package Legend #white { SUB_ENTITY1 *--> REQUIRED_PARENT_ENTITY diff --git a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsManagedWebspaceHostingAssetValidatorUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsManagedWebspaceHostingAssetValidatorUnitTest.java index d7efcda4..02384389 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsManagedWebspaceHostingAssetValidatorUnitTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsManagedWebspaceHostingAssetValidatorUnitTest.java @@ -22,7 +22,7 @@ class HsManagedWebspaceHostingAssetValidatorUnitTest { .type(HsBookingItemType.MANAGED_SERVER) .caption("Test Managed-Server") .resources(Map.ofEntries( - entry("CPUs", 2), + entry("CPU", 2), entry("RAM", 25), entry("SSD", 25), entry("Traffic", 250), @@ -125,6 +125,7 @@ class HsManagedWebspaceHostingAssetValidatorUnitTest { .type(MANAGED_WEBSPACE) .bookingItem(HsBookingItemEntity.builder() .type(HsBookingItemType.MANAGED_WEBSPACE) + .project(TEST_PROJECT) .caption("some ManagedWebspace") .resources(Map.ofEntries(entry("SSD", 25), entry("Traffic", 250))) .build()) diff --git a/src/test/java/net/hostsharing/hsadminng/hs/migration/CsvDataImport.java b/src/test/java/net/hostsharing/hsadminng/hs/migration/CsvDataImport.java new file mode 100644 index 00000000..de741b46 --- /dev/null +++ b/src/test/java/net/hostsharing/hsadminng/hs/migration/CsvDataImport.java @@ -0,0 +1,312 @@ +package net.hostsharing.hsadminng.hs.migration; + +import com.opencsv.CSVParserBuilder; +import com.opencsv.CSVReader; +import com.opencsv.CSVReaderBuilder; +import net.hostsharing.hsadminng.rbac.context.ContextBasedTest; +import net.hostsharing.hsadminng.rbac.rbacobject.RbacObject; +import net.hostsharing.hsadminng.rbac.test.JpaAttempt; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.TestWatcher; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.transaction.support.TransactionTemplate; + +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.constraints.NotNull; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.StringReader; +import java.io.StringWriter; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.stream.Collectors; + +import static java.lang.Boolean.parseBoolean; +import static java.util.Arrays.stream; +import static java.util.Objects.requireNonNull; +import static java.util.Optional.ofNullable; +import static org.apache.commons.lang3.StringUtils.isNotBlank; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assumptions.assumeThat; + +public class CsvDataImport extends ContextBasedTest { + + public static final String TEST_DATA_MIGRATION_DATA_PATH = "migration"; + public static final String MIGRATION_DATA_PATH = ofNullable(System.getenv("HSADMINNG_MIGRATION_DATA_PATH")) + .orElse(TEST_DATA_MIGRATION_DATA_PATH); + + @Value("${spring.datasource.url}") + protected String jdbcUrl; + + @Value("${spring.datasource.username}") + protected String postgresAdminUser; + + @Value("${hsadminng.superuser}") + protected String rbacSuperuser; + + @PersistenceContext + EntityManager em; + + @Autowired + TransactionTemplate txTemplate; + + @Autowired + JpaAttempt jpaAttempt; + + @MockBean + HttpServletRequest request; + + private static final List errors = new ArrayList<>(); + + public List readAllLines(Reader reader) throws Exception { + + final var parser = new CSVParserBuilder() + .withSeparator(';') + .withQuoteChar('"') + .build(); + + final var filteredReader = skippingEmptyAndCommentLines(reader); + try (CSVReader csvReader = new CSVReaderBuilder(filteredReader) + .withCSVParser(parser) + .build()) { + return csvReader.readAll(); + } + } + + public static Reader skippingEmptyAndCommentLines(Reader reader) throws IOException { + try (var bufferedReader = new BufferedReader(reader); + StringWriter writer = new StringWriter()) { + + String line; + while ((line = bufferedReader.readLine()) != null) { + if (!line.isBlank() && !line.startsWith("#")) { + writer.write(line); + writer.write("\n"); + } + } + + return new StringReader(writer.toString()); + } + } + + protected static String[] justHeader(final List lines) { + return stream(lines.getFirst()).map(String::trim).toArray(String[]::new); + } + + protected Reader resourceReader(@NotNull final String resourcePath) { + return new InputStreamReader(requireNonNull(getClass().getClassLoader().getResourceAsStream(resourcePath))); + } + + protected List withoutHeader(final List records) { + return records.subList(1, records.size()); + } + + String[] trimAll(final String[] record) { + for (int i = 0; i < record.length; ++i) { + if (record[i] != null) { + record[i] = record[i].trim(); + } + } + return record; + } + + public T persist(final Integer id, final T entity) { + try { + final var asString = entity.toString(); + if ( asString.contains("'null null, null'") || asString.equals("person()")) { + System.err.println("skipping to persist empty record-id " + id + " #" + entity.hashCode() + ": " + entity); + return entity; + } + //System.out.println("persisting #" + entity.hashCode() + ": " + entity); + em.persist(entity); + // uncomment for debugging purposes + // em.flush(); // makes it slow, but produces better error messages + // System.out.println("persisted #" + entity.hashCode() + " as " + entity.getUuid()); + } catch (Exception exc) { + System.err.println("failed to persist #" + entity.hashCode() + ": " + entity); + System.err.println(exc); + } + return entity; + } + + protected String toFormattedString(final Map map) { + if ( map.isEmpty() ) { + return "{}"; + } + return "{\n" + + map.keySet().stream() + .map(id -> " " + id + "=" + map.get(id).toString()) + .map(e -> e.replaceAll("\n ", " ").replace("\n", "")) + .sorted() + .collect(Collectors.joining(",\n")) + + "\n}\n"; + } + + protected void deleteTestDataFromHsOfficeTables() { + jpaAttempt.transacted(() -> { + context(rbacSuperuser); + // TODO.perf: could we instead skip creating test-data based on an env var? + em.createNativeQuery("delete from hs_hosting_asset where true").executeUpdate(); + em.createNativeQuery("delete from hs_booking_item where true").executeUpdate(); + em.createNativeQuery("delete from hs_booking_project where true").executeUpdate(); + em.createNativeQuery("delete from hs_office_coopassetstransaction where true").executeUpdate(); + em.createNativeQuery("delete from hs_office_coopassetstransaction_legacy_id where true").executeUpdate(); + em.createNativeQuery("delete from hs_office_coopsharestransaction where true").executeUpdate(); + em.createNativeQuery("delete from hs_office_coopsharestransaction_legacy_id where true").executeUpdate(); + em.createNativeQuery("delete from hs_office_membership where true").executeUpdate(); + em.createNativeQuery("delete from hs_office_sepamandate where true").executeUpdate(); + em.createNativeQuery("delete from hs_office_sepamandate_legacy_id where true").executeUpdate(); + em.createNativeQuery("delete from hs_office_debitor where true").executeUpdate(); + em.createNativeQuery("delete from hs_office_bankaccount where true").executeUpdate(); + em.createNativeQuery("delete from hs_office_partner where true").executeUpdate(); + em.createNativeQuery("delete from hs_office_partner_details where true").executeUpdate(); + em.createNativeQuery("delete from hs_office_relation where true").executeUpdate(); + em.createNativeQuery("delete from hs_office_contact where true").executeUpdate(); + em.createNativeQuery("delete from hs_office_person where true").executeUpdate(); + }).assertSuccessful(); + } + + protected void resetHsOfficeSequences() { + jpaAttempt.transacted(() -> { + context(rbacSuperuser); + em.createNativeQuery("alter sequence hs_office_contact_legacy_id_seq restart with 1000000000;").executeUpdate(); + em.createNativeQuery("alter sequence hs_office_coopassetstransaction_legacy_id_seq restart with 1000000000;") + .executeUpdate(); + em.createNativeQuery("alter sequence public.hs_office_coopsharestransaction_legacy_id_seq restart with 1000000000;") + .executeUpdate(); + em.createNativeQuery("alter sequence public.hs_office_partner_legacy_id_seq restart with 1000000000;") + .executeUpdate(); + em.createNativeQuery("alter sequence public.hs_office_sepamandate_legacy_id_seq restart with 1000000000;") + .executeUpdate(); + }); + } + + protected void deleteFromTestTables() { + jpaAttempt.transacted(() -> { + context(rbacSuperuser); + em.createNativeQuery("delete from test_domain where true").executeUpdate(); + em.createNativeQuery("delete from test_package where true").executeUpdate(); + em.createNativeQuery("delete from test_customer where true").executeUpdate(); + }).assertSuccessful(); + } + + protected void deleteFromRbacTables() { + jpaAttempt.transacted(() -> { + context(rbacSuperuser); + em.createNativeQuery("delete from rbacuser_rv where name not like 'superuser-%'").executeUpdate(); + em.createNativeQuery("delete from tx_journal where true").executeUpdate(); + em.createNativeQuery("delete from tx_context where true").executeUpdate(); + }).assertSuccessful(); + } + + void logError(final Runnable assertion) { + try { + assertion.run(); + } catch (final AssertionError exc) { + errors.add(exc); + } + } + + void logErrors() { + assumeThat(errors).isEmpty(); + } +} + +class Columns { + + private final List columnNames; + + public Columns(final String[] header) { + columnNames = List.of(header); + } + + int indexOf(final String columnName) { + int index = columnNames.indexOf(columnName); + if (index < 0) { + throw new RuntimeException("column name '" + columnName + "' not found in: " + columnNames); + } + return index; + } +} + +class Record { + + private final Columns columns; + private final String[] row; + + public Record(final Columns columns, final String[] row) { + this.columns = columns; + this.row = row; + } + + String getString(final String columnName) { + return row[columns.indexOf(columnName)]; + } + + boolean isEmpty(final String columnName) { + final String value = getString(columnName); + return value == null || value.isBlank(); + } + + boolean getBoolean(final String columnName) { + final String value = getString(columnName); + return isNotBlank(value) && + ( parseBoolean(value.trim()) || value.trim().startsWith("t")); + } + + Integer getInteger(final String columnName) { + final String value = getString(columnName); + return isNotBlank(value) ? Integer.parseInt(value.trim()) : null; + } + + BigDecimal getBigDecimal(final String columnName) { + final String value = getString(columnName); + if (isNotBlank(value)) { + return new BigDecimal(value); + } + return null; + } + + LocalDate getLocalDate(final String columnName) { + final String dateString = getString(columnName); + if (isNotBlank(dateString)) { + return LocalDate.parse(dateString); + } + return null; + } +} + +class OrderedDependedTestsExtension implements TestWatcher, BeforeEachCallback { + + private static boolean previousTestsPassed = true; + + public void testFailed(ExtensionContext context, Throwable cause) { + previousTestsPassed = false; + } + + @Override + public void beforeEach(final ExtensionContext extensionContext) { + assumeThat(previousTestsPassed).isTrue(); + } +} + +class WriteOnceMap extends TreeMap { + + @Override + public V put(final K k, final V v) { + assertThat(containsKey(k)).describedAs("overwriting " + get(k) + " index " + k + " with " + v).isFalse(); + return super.put(k, v); + } +} diff --git a/src/test/java/net/hostsharing/hsadminng/hs/migration/ImportHostingAssets.java b/src/test/java/net/hostsharing/hsadminng/hs/migration/ImportHostingAssets.java new file mode 100644 index 00000000..cda4c482 --- /dev/null +++ b/src/test/java/net/hostsharing/hsadminng/hs/migration/ImportHostingAssets.java @@ -0,0 +1,620 @@ +package net.hostsharing.hsadminng.hs.migration; + +import net.hostsharing.hsadminng.context.Context; +import net.hostsharing.hsadminng.hs.booking.debitor.HsBookingDebitorEntity; +import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity; +import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType; +import net.hostsharing.hsadminng.hs.booking.item.validators.HsBookingItemEntityValidatorRegistry; +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 net.hostsharing.hsadminng.hs.hosting.asset.validators.HostingAssetEntitySaveProcessor; +import net.hostsharing.hsadminng.rbac.test.JpaAttempt; +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.context.annotation.Import; +import org.springframework.test.annotation.Commit; +import org.springframework.test.annotation.DirtiesContext; + +import java.io.Reader; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Function; + +import static java.util.Arrays.stream; +import static java.util.Optional.ofNullable; +import static java.util.stream.Collectors.toMap; +import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.CLOUD_SERVER; +import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.IPV4_NUMBER; +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.mapper.PostgresDateRange.toPostgresDateRange; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assumptions.assumeThat; + +/* + * This 'test' includes the complete legacy 'office' data import. + * + * There is no code in 'main' because the import is not needed a normal runtime. + * There is some test data in Java resources to verify the data conversion. + * For a real import a main method will be added later + * which reads CSV files from the file system. + * + * When run on a Hostsharing database, it needs the following settings (hsh99_... just examples). + * + * In a real Hostsharing environment, these are created via (the old) hsadmin: + + CREATE USER hsh99_admin WITH PASSWORD 'password'; + CREATE DATABASE hsh99_hsadminng ENCODING 'UTF8' TEMPLATE template0; + REVOKE ALL ON DATABASE hsh99_hsadminng FROM public; -- why does hsadmin do that? + ALTER DATABASE hsh99_hsadminng OWNER TO hsh99_admin; + + CREATE USER hsh99_restricted WITH PASSWORD 'password'; + + \c hsh99_hsadminng + + GRANT ALL PRIVILEGES ON SCHEMA public to hsh99_admin; + + * Additionally, we need these settings (because the Hostsharing DB-Admin has no CREATE right): + + CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; + + -- maybe something like that is needed for the 2nd user + -- GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public to hsh99_restricted; + + * Then copy the file .tc-environment to a file named .environment (excluded from git) and fill in your specific values. + + * To finally import the office data, run: + * + * gw-importHostingAssets # comes from .aliases file and uses .environment + */ +@Tag("importHostingAssets") +@DataJpaTest(properties = { + "spring.datasource.url=${HSADMINNG_POSTGRES_JDBC_URL:jdbc:tc:postgresql:15.5-bookworm:///spring_boot_testcontainers}", + "spring.datasource.username=${HSADMINNG_POSTGRES_ADMIN_USERNAME:ADMIN}", + "spring.datasource.password=${HSADMINNG_POSTGRES_ADMIN_PASSWORD:password}", + "hsadminng.superuser=${HSADMINNG_SUPERUSER:superuser-alex@hostsharing.net}" +}) +@DirtiesContext +@Import({ Context.class, JpaAttempt.class }) +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +@ExtendWith(OrderedDependedTestsExtension.class) +public class ImportHostingAssets extends ImportOfficeData { + + static final Integer IP_NUMBER_ID_OFFSET = 1000000; + static final Integer HIVE_ID_OFFSET = 2000000; + static final Integer PACKET_ID_OFFSET = 3000000; + + record Hive(int hive_id, String hive_name, int inet_addr_id, AtomicReference serverRef) {} + + static Map bookingProjects = new WriteOnceMap<>(); + static Map bookingItems = new WriteOnceMap<>(); + static Map hives = new WriteOnceMap<>(); + static Map hostingAssets = new WriteOnceMap<>(); // TODO.impl: separate maps for each type? + + @Test + @Order(11010) + void createBookingProjects() { + debitors.forEach((id, debitor) -> { + bookingProjects.put(id, HsBookingProjectEntity.builder() + .caption(debitor.getDefaultPrefix() + " default project") + .debitor(em.find(HsBookingDebitorEntity.class, debitor.getUuid())) + .build()); + }); + } + + @Test + @Order(12010) + void importIpNumbers() { + try (Reader reader = resourceReader(MIGRATION_DATA_PATH + "/hosting/inet_addr.csv")) { + final var lines = readAllLines(reader); + importIpNumbers(justHeader(lines), withoutHeader(lines)); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Test + @Order(12019) + void verifyIpNumbers() { + assumeThatWeAreImportingControlledTestData(); + + // no contacts yet => mostly null values + assertThat(firstOfEachType(5, IPV4_NUMBER)).isEqualToIgnoringWhitespace(""" + { + 1000363=HsHostingAssetEntity(IPV4_NUMBER, 83.223.95.34), + 1000381=HsHostingAssetEntity(IPV4_NUMBER, 83.223.95.52), + 1000402=HsHostingAssetEntity(IPV4_NUMBER, 83.223.95.73), + 1000433=HsHostingAssetEntity(IPV4_NUMBER, 83.223.95.104), + 1000457=HsHostingAssetEntity(IPV4_NUMBER, 83.223.95.128) + } + """); + } + + @Test + @Order(12030) + void importHives() { + try (Reader reader = resourceReader(MIGRATION_DATA_PATH + "/hosting/hive.csv")) { + final var lines = readAllLines(reader); + importHives(justHeader(lines), withoutHeader(lines)); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Test + @Order(12039) + void verifyHives() { + assumeThatWeAreImportingControlledTestData(); + + // no contacts yet => mostly null values + assertThat(toFormattedString(first(5, hives))).isEqualToIgnoringWhitespace(""" + { + 2000001=Hive[hive_id=1, hive_name=h00, inet_addr_id=358, serverRef=null], + 2000002=Hive[hive_id=2, hive_name=h01, inet_addr_id=359, serverRef=null], + 2000004=Hive[hive_id=4, hive_name=h02, inet_addr_id=360, serverRef=null], + 2000007=Hive[hive_id=7, hive_name=h03, inet_addr_id=361, serverRef=null], + 2000013=Hive[hive_id=13, hive_name=h04, inet_addr_id=430, serverRef=null] + } + """); + } + + @Test + @Order(13000) + void importPackets() { + try (Reader reader = resourceReader(MIGRATION_DATA_PATH + "/hosting/packet.csv")) { + final var lines = readAllLines(reader); + importPackets(justHeader(lines), withoutHeader(lines)); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Test + @Order(13009) + void verifyPackets() { + assumeThatWeAreImportingControlledTestData(); + + assertThat(firstOfEachType(3, CLOUD_SERVER, MANAGED_SERVER, MANAGED_WEBSPACE)).isEqualToIgnoringWhitespace(""" + { + 3000630=HsHostingAssetEntity(MANAGED_WEBSPACE, hsh00, HA hsh00, MANAGED_SERVER:vm1050, D-1000000:hsh default project:BI hsh00), + 3000968=HsHostingAssetEntity(MANAGED_SERVER, vm1061, HA vm1061, D-1015200:rar default project:BI vm1061), + 3000978=HsHostingAssetEntity(MANAGED_SERVER, vm1050, HA vm1050, D-1000000:hsh default project:BI vm1050), + 3001061=HsHostingAssetEntity(MANAGED_SERVER, vm1068, HA vm1068, D-1000300:mim default project:BI vm1068), + 3001094=HsHostingAssetEntity(MANAGED_WEBSPACE, lug00, HA lug00, MANAGED_SERVER:vm1068, D-1000300:mim default project:BI lug00), + 3001112=HsHostingAssetEntity(MANAGED_WEBSPACE, mim00, HA mim00, MANAGED_SERVER:vm1068, D-1000300:mim default project:BI mim00), + 3023611=HsHostingAssetEntity(CLOUD_SERVER, vm2097, HA vm2097, D-1101800:wws default project:BI vm2097) + } + """); + assertThat(firstOfEachType( + 3, + HsBookingItemType.CLOUD_SERVER, + HsBookingItemType.MANAGED_SERVER, + HsBookingItemType.MANAGED_WEBSPACE)).isEqualToIgnoringWhitespace(""" + { + 3000630=HsBookingItemEntity(D-1000000:hsh default project, MANAGED_WEBSPACE, [2001-06-01,), BI hsh00), + 3000968=HsBookingItemEntity(D-1015200:rar default project, MANAGED_SERVER, [2013-04-01,), BI vm1061), + 3000978=HsBookingItemEntity(D-1000000:hsh default project, MANAGED_SERVER, [2013-04-01,), BI vm1050), + 3001061=HsBookingItemEntity(D-1000300:mim default project, MANAGED_SERVER, [2013-08-19,), BI vm1068), + 3001094=HsBookingItemEntity(D-1000300:mim default project, MANAGED_WEBSPACE, [2013-09-10,), BI lug00), + 3001112=HsBookingItemEntity(D-1000300:mim default project, MANAGED_WEBSPACE, [2013-09-17,), BI mim00), + 3023611=HsBookingItemEntity(D-1101800:wws default project, CLOUD_SERVER, [2022-08-10,), BI vm2097) + } + """); + } + + @Test + @Order(13010) + void importPacketComponents() { + try (Reader reader = resourceReader(MIGRATION_DATA_PATH + "/hosting/packet_component.csv")) { + final var lines = readAllLines(reader); + importPacketComponents(justHeader(lines), withoutHeader(lines)); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Test + @Order(13019) + void verifyPacketComponents() { + assumeThatWeAreImportingControlledTestData(); + + // no contacts yet => mostly null values + assertThat(firstOfEachType(5, CLOUD_SERVER, MANAGED_SERVER, MANAGED_WEBSPACE)) + .isEqualToIgnoringWhitespace(""" + { + 3000630=HsHostingAssetEntity(MANAGED_WEBSPACE, hsh00, HA hsh00, MANAGED_SERVER:vm1050, D-1000000:hsh default project:BI hsh00), + 3000968=HsHostingAssetEntity(MANAGED_SERVER, vm1061, HA vm1061, D-1015200:rar default project:BI vm1061), + 3000978=HsHostingAssetEntity(MANAGED_SERVER, vm1050, HA vm1050, D-1000000:hsh default project:BI vm1050), + 3001061=HsHostingAssetEntity(MANAGED_SERVER, vm1068, HA vm1068, D-1000300:mim default project:BI vm1068), + 3001094=HsHostingAssetEntity(MANAGED_WEBSPACE, lug00, HA lug00, MANAGED_SERVER:vm1068, D-1000300:mim default project:BI lug00), + 3001112=HsHostingAssetEntity(MANAGED_WEBSPACE, mim00, HA mim00, MANAGED_SERVER:vm1068, D-1000300:mim default project:BI mim00), + 3001447=HsHostingAssetEntity(MANAGED_SERVER, vm1093, HA vm1093, D-1000000:hsh default project:BI vm1093), + 3019959=HsHostingAssetEntity(MANAGED_WEBSPACE, dph00, HA dph00, MANAGED_SERVER:vm1093, D-1101900:dph default project:BI dph00), + 3023611=HsHostingAssetEntity(CLOUD_SERVER, vm2097, HA vm2097, D-1101800:wws default project:BI vm2097) + } + """); + assertThat(firstOfEachType( + 5, + HsBookingItemType.CLOUD_SERVER, + HsBookingItemType.MANAGED_SERVER, + HsBookingItemType.MANAGED_WEBSPACE)) + .isEqualToIgnoringWhitespace(""" + { + 3000630=HsBookingItemEntity(D-1000000:hsh default project, MANAGED_WEBSPACE, [2001-06-01,), BI hsh00, { "HDD": 10, "Multi": 25, "SLA-Platform": "EXT24H", "SSD": 16, "Traffic": 50}), + 3000968=HsBookingItemEntity(D-1015200:rar default project, MANAGED_SERVER, [2013-04-01,), BI vm1061, { "CPU": 6, "HDD": 250, "RAM": 14, "SLA-EMail": true, "SLA-Maria": true, "SLA-Office": true, "SLA-PgSQL": true, "SLA-Platform": "EXT4H", "SLA-Web": true, "SSD": 375, "Traffic": 250}), + 3000978=HsBookingItemEntity(D-1000000:hsh default project, MANAGED_SERVER, [2013-04-01,), BI vm1050, { "CPU": 4, "HDD": 250, "RAM": 32, "SLA-EMail": true, "SLA-Maria": true, "SLA-Office": true, "SLA-PgSQL": true, "SLA-Platform": "EXT4H", "SLA-Web": true, "SSD": 150, "Traffic": 250}), + 3001061=HsBookingItemEntity(D-1000300:mim default project, MANAGED_SERVER, [2013-08-19,), BI vm1068, { "CPU": 2, "HDD": 250, "RAM": 4, "SLA-EMail": true, "SLA-Maria": true, "SLA-Office": true, "SLA-PgSQL": true, "SLA-Platform": "EXT2H", "SLA-Web": true, "Traffic": 250}), + 3001094=HsBookingItemEntity(D-1000300:mim default project, MANAGED_WEBSPACE, [2013-09-10,), BI lug00, { "Multi": 5, "SLA-Platform": "EXT24H", "SSD": 1, "Traffic": 10}), + 3001112=HsBookingItemEntity(D-1000300:mim default project, MANAGED_WEBSPACE, [2013-09-17,), BI mim00, { "Multi": 5, "SLA-Platform": "EXT24H", "SSD": 3, "Traffic": 20}), + 3001447=HsBookingItemEntity(D-1000000:hsh default project, MANAGED_SERVER, [2014-11-28,), BI vm1093, { "CPU": 6, "HDD": 500, "RAM": 16, "SLA-EMail": true, "SLA-Maria": true, "SLA-Office": true, "SLA-PgSQL": true, "SLA-Platform": "EXT4H", "SLA-Web": true, "SSD": 300, "Traffic": 250}), + 3019959=HsBookingItemEntity(D-1101900:dph default project, MANAGED_WEBSPACE, [2021-06-02,), BI dph00, { "Multi": 1, "SLA-Platform": "EXT24H", "SSD": 25, "Traffic": 20}), + 3023611=HsBookingItemEntity(D-1101800:wws default project, CLOUD_SERVER, [2022-08-10,), BI vm2097, { "CPU": 8, "RAM": 12, "SLA-Infrastructure": "EXT4H", "SSD": 25, "Traffic": 250}) + } + """); + } + + @Test + @Order(11400) + void validateBookingItems() { + bookingItems.forEach((id, bi) -> { + try { + HsBookingItemEntityValidatorRegistry.validated(bi); + } catch (final Exception exc) { + System.err.println("validation failed for id:" + id + "( " + bi + "): " + exc.getMessage()); + } + }); + } + + @Test + @Order(11410) + void validateHostingAssets() { + hostingAssets.forEach((id, ha) -> { + try { + new HostingAssetEntitySaveProcessor(ha) + .preprocessEntity() + .validateEntity(); + } catch (final Exception exc) { + System.err.println("validation failed for id:" + id + "( " + ha + "): " + exc.getMessage()); + } + }); + } + + @Test + @Order(19000) + @Commit + void persistHostingAssetEntities() { + + System.out.println("PERSISTING hosting-assets to database '" + jdbcUrl + "' as user '" + postgresAdminUser + "'"); + + jpaAttempt.transacted(() -> { + context(rbacSuperuser); + bookingProjects.forEach(this::persist); + }).assertSuccessful(); + + jpaAttempt.transacted(() -> { + context(rbacSuperuser); + bookingItems.forEach(this::persistRecursively); + }).assertSuccessful(); + + persistHostingAssetsOfType(CLOUD_SERVER); + persistHostingAssetsOfType(MANAGED_SERVER); + persistHostingAssetsOfType(MANAGED_WEBSPACE); + persistHostingAssetsOfType(IPV4_NUMBER); + } + + @Test + @Order(99999) + void logErrors() { + super.logErrors(); + } + + private void persistRecursively(final Integer key, final HsBookingItemEntity bi) { + if (bi.getParentItem() != null) { + persistRecursively(key, HsBookingItemEntityValidatorRegistry.validated(bi.getParentItem())); + } + persist(key, HsBookingItemEntityValidatorRegistry.validated(bi)); + } + + private void persistHostingAssetsOfType(final HsHostingAssetType hsHostingAssetType) { + jpaAttempt.transacted(() -> { + context(rbacSuperuser); + hostingAssets.forEach((key, ha) -> { + if (ha.getType() == hsHostingAssetType) { + new HostingAssetEntitySaveProcessor(ha) + .preprocessEntity() + .validateEntity() + .prepareForSave() + .saveUsing(entity -> persist(key, entity)) + .validateContext(); + } + } + ); + }).assertSuccessful(); + } + + private void importIpNumbers(final String[] header, final List records) { + final var columns = new Columns(header); + records.stream() + .map(this::trimAll) + .map(row -> new Record(columns, row)) + .forEach(rec -> { + final var ipNumber = HsHostingAssetEntity.builder() + .type(IPV4_NUMBER) + .identifier(rec.getString("inet_addr")) + .caption(rec.getString("description")) + .build(); + hostingAssets.put(IP_NUMBER_ID_OFFSET + rec.getInteger("inet_addr_id"), ipNumber); + }); + } + + private void importHives(final String[] header, final List records) { + final var columns = new Columns(header); + records.stream() + .map(this::trimAll) + .map(row -> new Record(columns, row)) + .forEach(rec -> { + final var hive_id = rec.getInteger("hive_id"); + final var hive = new Hive( + hive_id, + rec.getString("hive_name"), + rec.getInteger("inet_addr_id"), + new AtomicReference<>()); + hives.put(HIVE_ID_OFFSET + hive_id, hive); + }); + } + + private void importPackets(final String[] header, final List records) { + final var columns = new Columns(header); + records.stream() + .map(this::trimAll) + .map(row -> new Record(columns, row)) + .forEach(rec -> { + final var packet_id = rec.getInteger("packet_id"); + final var basepacket_code = rec.getString("basepacket_code"); + final var packet_name = rec.getString("packet_name"); + final var bp_id = rec.getInteger("bp_id"); + final var hive_id = rec.getInteger("hive_id"); + final var created = rec.getLocalDate("created"); + final var cancelled = rec.getLocalDate("cancelled"); + final var cur_inet_addr_id = rec.getInteger("cur_inet_addr_id"); + final var old_inet_addr_id = rec.getInteger("old_inet_addr_id"); + final var free = rec.getBoolean("free"); + + assertThat(old_inet_addr_id) + .as("packet.old_inet_addr_id not supported, but is not null for " + packet_name) + .isNull(); + + final var biType = determineBiType(basepacket_code); + final var bookingItem = HsBookingItemEntity.builder() + .type(biType) + .caption("BI " + packet_name) + .project(bookingProjects.get(bp_id)) + .validity(toPostgresDateRange(created, cancelled)) + .build(); + bookingItems.put(PACKET_ID_OFFSET + packet_id, bookingItem); + final var haType = determineHaType(basepacket_code); + + logError(() -> assertThat(!free || haType == MANAGED_WEBSPACE || bookingItem.getRelatedProject().getDebitor().getDefaultPrefix().equals("hsh")) + .as("packet.free only supported for Hostsharing-Assets and ManagedWebspace in customer-ManagedServer, but is set for " + packet_name) + .isTrue()); + + final var asset = HsHostingAssetEntity.builder() + .isLoaded(haType == MANAGED_WEBSPACE) // this turns off identifier validation to accept former default prefixes + .type(haType) + .identifier(packet_name) + .bookingItem(bookingItem) + .caption("HA " + packet_name) + .build(); + hostingAssets.put(PACKET_ID_OFFSET + packet_id, asset); + if (haType == MANAGED_SERVER) { + hive(hive_id).serverRef.set(asset); + } + + if (cur_inet_addr_id != null) { + ipNumber(cur_inet_addr_id).setAssignedToAsset(asset); + } + }); + + // once we know all hosting assets, we can set the parentAsset for managed webspaces + records.stream() + .map(this::trimAll) + .map(row -> new Record(columns, row)) + .forEach(rec -> { + final var packet_id = rec.getInteger("packet_id"); + final var basepacket_code = rec.getString("basepacket_code"); + final var hive_id = rec.getInteger("hive_id"); + + final var haType = determineHaType(basepacket_code); + if (haType == MANAGED_WEBSPACE) { + final var managedWebspace = pac(packet_id); + final var parentAsset = hive(hive_id).serverRef.get(); + managedWebspace.setParentAsset(parentAsset); + managedWebspace.getBookingItem().setParentItem(parentAsset.getBookingItem()); + } + }); + } + + private void importPacketComponents(final String[] header, final List records) { + final var columns = new Columns(header); + records.stream() + .map(this::trimAll) + .map(row -> new Record(columns, row)) + .forEach(rec -> { + // final var packet_component_id = rec.getInteger("packet_component_id"); not needed + final var packet_id = rec.getInteger("packet_id"); + final var quantity = rec.getInteger("quantity"); + final var basecomponent_code = rec.getString("basecomponent_code"); + // final var created = rec.getLocalDate("created"); TODO.spec: can we do without? + // final var cancelled = rec.getLocalDate("cancelled"); TODO.spec: can we do without? + Function convert = (v -> v); + + final var asset = pac(packet_id); + final var name = switch (basecomponent_code) { + case "DAEMON" -> "Daemons"; + case "MULTI" -> "Multi"; + case "CPU" -> "CPU"; + case "RAM" -> returning("RAM", convert = v -> v/1024); + case "QUOTA" -> returning("SSD", convert = v -> v/1024); + case "STORAGE" -> returning("HDD", convert = v -> v/1024); + case "TRAFFIC" -> "Traffic"; + case "OFFICE" -> returning("Online Office Server", convert = v -> v == 1); + + case "SLABASIC" -> switch (asset.getType()) { + case CLOUD_SERVER -> "SLA-Infrastructure"; + case MANAGED_SERVER -> "SLA-Platform"; + case MANAGED_WEBSPACE -> "SLA-Platform"; + default -> throw new IllegalArgumentException("SLABASIC not defined for " + asset.getType()); + }; + + case "SLAINFR2H" -> "SLA-Infrastructure"; + case "SLAINFR4H" -> "SLA-Infrastructure"; + case "SLAINFR8H" -> "SLA-Infrastructure"; + + case "SLAEXT24H" -> "SLA-Platform"; + + case "SLAPLAT2H" -> "SLA-Platform"; + case "SLAPLAT4H" -> "SLA-Platform"; + case "SLAPLAT8H" -> "SLA-Platform"; + + case "SLAWEB2H" -> "SLA-Web"; + case "SLAWEB4H" -> "SLA-Web"; + case "SLAWEB8H" -> "SLA-Web"; + + case "SLAMAIL2H" -> "SLA-EMail"; + case "SLAMAIL4H" -> "SLA-EMail"; + case "SLAMAIL8H" -> "SLA-EMail"; + + case "SLAMARIA2H" -> "SLA-Maria"; + case "SLAMARIA4H" -> "SLA-Maria"; + case "SLAMARIA8H" -> "SLA-Maria"; + + case "SLAPGSQL2H" -> "SLA-PgSQL"; + case "SLAPGSQL4H" -> "SLA-PgSQL"; + case "SLAPGSQL8H" -> "SLA-PgSQL"; + + case "SLAOFFIC2H" -> "SLA-Office"; + case "SLAOFFIC4H" -> "SLA-Office"; + case "SLAOFFIC8H" -> "SLA-Office"; + + case "BANDWIDTH" -> "Bandwidth"; + default -> throw new IllegalArgumentException("unknown basecomponent_code: " + basecomponent_code); + }; + + if (name.equals("SLA-Infrastructure")) { + final var slaValue = switch (basecomponent_code) { + case "SLABASIC" -> "BASIC"; + case "SLAINFR2H" -> "EXT2H"; + case "SLAINFR4H" -> "EXT4H"; + case "SLAINFR8H" -> "EXT8H"; + default -> throw new IllegalArgumentException("unknown basecomponent_code: " + basecomponent_code); + }; + asset.getBookingItem().getResources().put(name, slaValue); + } else if (name.equals("SLA-Platform")) { + final var slaValue = switch (basecomponent_code) { + case "SLABASIC" -> "BASIC"; + case "SLAEXT24H" -> "EXT24H"; + case "SLAPLAT2H" -> "EXT2H"; + case "SLAPLAT4H" -> "EXT4H"; + case "SLAPLAT8H" -> "EXT8H"; + default -> throw new IllegalArgumentException("unknown basecomponent_code: " + basecomponent_code); + }; + if ( ofNullable(asset.getBookingItem().getResources().get(name)).map("BASIC"::equals).orElse(true) ) { + asset.getBookingItem().getResources().put(name, slaValue); + } + } else if (name.startsWith("SLA")) { + asset.getBookingItem().getResources().put(name, true); + } else if (quantity > 0) { + asset.getBookingItem().getResources().put(name, convert.apply(quantity)); + } + }); + } + + V returning(final V value, final Object... assignments) { + return value; + } + + private static @NotNull HsBookingItemType determineBiType(final String basepacket_code) { + return switch (basepacket_code) { + case "SRV/CLD" -> HsBookingItemType.CLOUD_SERVER; + case "SRV/MGD" -> HsBookingItemType.MANAGED_SERVER; + case "PAC/WEB" -> HsBookingItemType.MANAGED_WEBSPACE; + default -> throw new IllegalArgumentException( + "unknown basepacket_code: " + basepacket_code); + }; + } + + private static @NotNull HsHostingAssetType determineHaType(final String basepacket_code) { + return switch (basepacket_code) { + case "SRV/CLD" -> CLOUD_SERVER; + case "SRV/MGD" -> MANAGED_SERVER; + case "PAC/WEB" -> MANAGED_WEBSPACE; + default -> throw new IllegalArgumentException( + "unknown basepacket_code: " + basepacket_code); + }; + } + + private static HsHostingAssetEntity ipNumber(final Integer inet_addr_id) { + return inet_addr_id != null ? hostingAssets.get(IP_NUMBER_ID_OFFSET + inet_addr_id) : null; + } + + private static Hive hive(final Integer hive_id) { + return hive_id != null ? hives.get(HIVE_ID_OFFSET + hive_id) : null; + } + + private static HsHostingAssetEntity pac(final Integer packet_id) { + return packet_id != null ? hostingAssets.get(PACKET_ID_OFFSET + packet_id) : null; + } + + private String firstOfEachType( + final int maxCount, + final HsHostingAssetType... types) { + return toFormattedString(stream(types) + .flatMap(t -> + hostingAssets.entrySet().stream() + .filter(hae -> hae.getValue().getType() == t) + .limit(maxCount) + ) + .collect(toMap(Map.Entry::getKey, Map.Entry::getValue))); + } + + private String firstOfEachType( + final int maxCount, + final HsBookingItemType... types) { + return toFormattedString(stream(types) + .flatMap(t -> + bookingItems.entrySet().stream() + .filter(bie -> bie.getValue().getType() == t) + .limit(maxCount) + ) + .collect(toMap(Map.Entry::getKey, Map.Entry::getValue))); + } + + private Map first( + final int maxCount, + final Map entities) { + return entities.entrySet().stream() + .limit(maxCount) + .collect(toMap(Map.Entry::getKey, Map.Entry::getValue)); + } + + @Override + protected void assumeThatWeAreExplicitlyImportingOfficeData() { + assumeThat(false).isTrue(); + } + + protected static boolean isImportingControlledTestData() { + return MIGRATION_DATA_PATH.equals(TEST_DATA_MIGRATION_DATA_PATH); + } + + protected static void assumeThatWeAreImportingControlledTestData() { + assumeThat(isImportingControlledTestData()).isTrue(); + } +} diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/migration/ImportOfficeData.java b/src/test/java/net/hostsharing/hsadminng/hs/migration/ImportOfficeData.java similarity index 58% rename from src/test/java/net/hostsharing/hsadminng/hs/office/migration/ImportOfficeData.java rename to src/test/java/net/hostsharing/hsadminng/hs/migration/ImportOfficeData.java index 52188e79..dd1f7d2b 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/migration/ImportOfficeData.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/migration/ImportOfficeData.java @@ -1,10 +1,6 @@ -package net.hostsharing.hsadminng.hs.office.migration; +package net.hostsharing.hsadminng.hs.migration; -import com.opencsv.CSVParserBuilder; -import com.opencsv.CSVReader; -import com.opencsv.CSVReaderBuilder; import net.hostsharing.hsadminng.context.Context; -import net.hostsharing.hsadminng.rbac.context.ContextBasedTest; import net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountEntity; import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity; import net.hostsharing.hsadminng.hs.office.coopassets.HsOfficeCoopAssetsTransactionEntity; @@ -26,34 +22,17 @@ import net.hostsharing.hsadminng.rbac.test.JpaAttempt; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import org.junit.jupiter.api.*; -import org.junit.jupiter.api.extension.BeforeEachCallback; import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.TestWatcher; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; -import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Import; -import org.springframework.test.annotation.Commit; import org.springframework.test.annotation.DirtiesContext; -import org.springframework.transaction.support.TransactionTemplate; -import jakarta.persistence.EntityManager; -import jakarta.persistence.PersistenceContext; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.validation.constraints.NotNull; import java.io.*; -import java.math.BigDecimal; -import java.nio.file.Files; -import java.nio.file.Path; import java.time.LocalDate; import java.util.*; import java.util.stream.Collectors; -import static java.lang.Boolean.parseBoolean; import static java.util.Arrays.stream; -import static java.util.Objects.requireNonNull; import static java.util.Optional.ofNullable; import static net.hostsharing.hsadminng.mapper.PostgresDateRange.toPostgresDateRange; import static org.apache.commons.lang3.StringUtils.isBlank; @@ -96,9 +75,9 @@ import static org.assertj.core.api.Fail.fail; * To finally import the office data, run: * - * import-office-tables # comes from .aliases file and uses .environment + * gw-importOfficeTables # comes from .aliases file and uses .environment */ -@Tag("import") +@Tag("importOfficeData") @DataJpaTest(properties = { "spring.datasource.url=${HSADMINNG_POSTGRES_JDBC_URL:jdbc:tc:postgresql:15.5-bookworm:///spring_boot_testcontainers}", "spring.datasource.username=${HSADMINNG_POSTGRES_ADMIN_USERNAME:ADMIN}", @@ -109,7 +88,7 @@ import static org.assertj.core.api.Fail.fail; @Import({ Context.class, JpaAttempt.class }) @TestMethodOrder(MethodOrderer.OrderAnnotation.class) @ExtendWith(OrderedDependedTestsExtension.class) -public class ImportOfficeData extends ContextBasedTest { +public class ImportOfficeData extends CsvDataImport { private static final String[] SUBSCRIBER_ROLES = new String[] { "subscriber:operations-discussion", @@ -123,15 +102,16 @@ public class ImportOfficeData extends ContextBasedTest { new String[]{"partner", "vip-contact", "ex-partner", "billing", "contractual", "operation"}, SUBSCRIBER_ROLES); - // at least as the number of lines in business-partners.csv from test-data, but less than real data partner count + // at least as the number of lines in business_partners.csv from test-data, but less than real data partner count public static final int MAX_NUMBER_OF_TEST_DATA_PARTNERS = 100; - public static final String MIGRATION_DATA_PATH = ofNullable(System.getenv("HSADMINNG_MIGRATION_DATA_PATH")).orElse("migration") + "/"; static int relationId = 2000000; private static final List IGNORE_BUSINESS_PARTNERS = Arrays.asList( 512167, // 11139, partner without contractual contact 512170, // 11142, partner without contractual contact + 511725, // 10764, partner without contractual contact + // 512171, // 11143, partner without partner contact -- exc -1 ); @@ -140,44 +120,23 @@ public class ImportOfficeData extends ContextBasedTest { -1 ); - @Value("${spring.datasource.url}") - private String jdbcUrl; + static Map contacts = new WriteOnceMap<>(); + static Map persons = new WriteOnceMap<>(); + static Map partners = new WriteOnceMap<>(); + static Map debitors = new WriteOnceMap<>(); + static Map memberships = new WriteOnceMap<>(); - @Value("${spring.datasource.username}") - private String postgresAdminUser; - - @Value("${hsadminng.superuser}") - private String rbacSuperuser; - - private static Map contacts = new WriteOnceMap<>(); - private static Map persons = new WriteOnceMap<>(); - private static Map partners = new WriteOnceMap<>(); - private static Map debitors = new WriteOnceMap<>(); - private static Map memberships = new WriteOnceMap<>(); - - private static Map relations = new WriteOnceMap<>(); - private static Map sepaMandates = new WriteOnceMap<>(); - private static Map bankAccounts = new WriteOnceMap<>(); - private static Map coopShares = new WriteOnceMap<>(); - private static Map coopAssets = new WriteOnceMap<>(); - - @PersistenceContext - EntityManager em; - - @Autowired - TransactionTemplate txTemplate; - - @Autowired - JpaAttempt jpaAttempt; - - @MockBean - HttpServletRequest request; + static Map relations = new WriteOnceMap<>(); + static Map sepaMandates = new WriteOnceMap<>(); + static Map bankAccounts = new WriteOnceMap<>(); + static Map coopShares = new WriteOnceMap<>(); + static Map coopAssets = new WriteOnceMap<>(); @Test @Order(1010) void importBusinessPartners() { - try (Reader reader = resourceReader(MIGRATION_DATA_PATH + "business-partners.csv")) { + try (Reader reader = resourceReader(MIGRATION_DATA_PATH + "/office/business_partners.csv")) { final var lines = readAllLines(reader); importBusinessPartners(justHeader(lines), withoutHeader(lines)); } catch (Exception e) { @@ -193,28 +152,39 @@ public class ImportOfficeData extends ContextBasedTest { // no contacts yet => mostly null values assertThat(toFormattedString(partners)).isEqualToIgnoringWhitespace(""" { - 17=partner(P-10017: null null, null), - 20=partner(P-10020: null null, null), - 22=partner(P-11022: null null, null), - 90=partner(P-19090: null null, null), - 99=partner(P-19999: null null, null) + 100=partner(P-10003: null null, null), + 120=partner(P-10020: null null, null), + 122=partner(P-11022: null null, null), + 132=partner(P-10152: null null, null), + 190=partner(P-19090: null null, null), + 199=partner(P-19999: null null, null), + 213=partner(P-10000: null null, null), + 541=partner(P-11018: null null, null), + 542=partner(P-11019: null null, null) } """); assertThat(toFormattedString(contacts)).isEqualTo("{}"); assertThat(toFormattedString(debitors)).isEqualToIgnoringWhitespace(""" { - 17=debitor(D-1001700: rel(anchor='null null, null', type='DEBITOR'), mih), - 20=debitor(D-1002000: rel(anchor='null null, null', type='DEBITOR'), xyz), - 22=debitor(D-1102200: rel(anchor='null null, null', type='DEBITOR'), xxx), - 90=debitor(D-1909000: rel(anchor='null null, null', type='DEBITOR'), yyy), - 99=debitor(D-1999900: rel(anchor='null null, null', type='DEBITOR'), zzz) + 100=debitor(D-1000300: rel(anchor='null null, null', type='DEBITOR'), mim), + 120=debitor(D-1002000: rel(anchor='null null, null', type='DEBITOR'), xyz), + 122=debitor(D-1102200: rel(anchor='null null, null', type='DEBITOR'), xxx), + 132=debitor(D-1015200: rel(anchor='null null, null', type='DEBITOR'), rar), + 190=debitor(D-1909000: rel(anchor='null null, null', type='DEBITOR'), yyy), + 199=debitor(D-1999900: rel(anchor='null null, null', type='DEBITOR'), zzz), + 213=debitor(D-1000000: rel(anchor='null null, null', type='DEBITOR'), hsh), + 541=debitor(D-1101800: rel(anchor='null null, null', type='DEBITOR'), wws), + 542=debitor(D-1101900: rel(anchor='null null, null', type='DEBITOR'), dph) } """); assertThat(toFormattedString(memberships)).isEqualToIgnoringWhitespace(""" { - 17=Membership(M-1001700, P-10017, [2000-12-06,), ACTIVE), - 20=Membership(M-1002000, P-10020, [2000-12-06,2016-01-01), UNKNOWN), - 22=Membership(M-1102200, P-11022, [2021-04-01,), ACTIVE) + 100=Membership(M-1000300, P-10003, [2000-12-06,), ACTIVE), + 120=Membership(M-1002000, P-10020, [2000-12-06,2016-01-01), UNKNOWN), + 122=Membership(M-1102200, P-11022, [2021-04-01,), ACTIVE), + 132=Membership(M-1015200, P-10152, [2003-07-12,), ACTIVE), + 541=Membership(M-1101800, P-11018, [2021-05-17,), ACTIVE), + 542=Membership(M-1101900, P-11019, [2021-05-25,), ACTIVE) } """); } @@ -222,8 +192,7 @@ public class ImportOfficeData extends ContextBasedTest { @Test @Order(1020) void importContacts() { - - try (Reader reader = resourceReader(MIGRATION_DATA_PATH + "contacts.csv")) { + try (Reader reader = resourceReader(MIGRATION_DATA_PATH + "/office/contacts.csv")) { final var lines = readAllLines(reader); importContacts(justHeader(lines), withoutHeader(lines)); } catch (Exception e) { @@ -238,83 +207,151 @@ public class ImportOfficeData extends ContextBasedTest { assertThat(toFormattedString(partners)).isEqualToIgnoringWhitespace(""" { - 17=partner(P-10017: NP Mellies, Michael, Herr Michael Mellies ), - 20=partner(P-10020: LP JM GmbH, Herr Philip Meyer-Contract , JM GmbH), - 22=partner(P-11022: ?? Test PS, Petra Schmidt , Test PS), - 90=partner(P-19090: NP Camus, Cecilia, Frau Cecilia Camus ), - 99=partner(P-19999: null null, null) + 100=partner(P-10003: ?? Michael Mellis, Herr Michael Mellis , Michael Mellis), + 120=partner(P-10020: LP JM GmbH, Herr Philip Meyer-Contract , JM GmbH), + 122=partner(P-11022: ?? Test PS, Petra Schmidt , Test PS), + 132=partner(P-10152: ?? Ragnar IT-Beratung, Herr Ragnar Richter , Ragnar IT-Beratung), + 190=partner(P-19090: NP Camus, Cecilia, Frau Cecilia Camus ), + 199=partner(P-19999: null null, null), + 213=partner(P-10000: LP Hostsharing e.G., Firma Hostmaster Hostsharing , Hostsharing e.G.), + 541=partner(P-11018: ?? Wasserwerk Südholstein, Frau Christiane Milberg , Wasserwerk Südholstein), + 542=partner(P-11019: ?? Das Perfekte Haus, Herr Richard Wiese , Das Perfekte Haus) } """); assertThat(toFormattedString(contacts)).isEqualToIgnoringWhitespace(""" { - 1101=contact(caption='Herr Michael Mellies ', emailAddresses='{ "main": "mih@example.org"}'), - 1200=contact(caption='JM e.K.', emailAddresses='{ "main": "jm-ex-partner@example.org"}'), - 1201=contact(caption='Frau Dr. Jenny Meyer-Billing , JM GmbH', emailAddresses='{ "main": "jm-billing@example.org"}'), - 1202=contact(caption='Herr Andrew Meyer-Operation , JM GmbH', emailAddresses='{ "main": "am-operation@example.org"}'), - 1203=contact(caption='Herr Philip Meyer-Contract , JM GmbH', emailAddresses='{ "main": "pm-partner@example.org"}'), - 1204=contact(caption='Frau Tammy Meyer-VIP , JM GmbH', emailAddresses='{ "main": "tm-vip@example.org"}'), - 1301=contact(caption='Petra Schmidt , Test PS', emailAddresses='{ "main": "ps@example.com"}'), - 1401=contact(caption='Frau Frauke Fanninga ', emailAddresses='{ "main": "ff@example.org"}'), - 1501=contact(caption='Frau Cecilia Camus ', emailAddresses='{ "main": "cc@example.org"}') - } + 100=contact(caption='Herr Michael Mellis , Michael Mellis', emailAddresses='{ "main": "michael@Mellis.example.org"}'), + 1200=contact(caption='JM e.K.', emailAddresses='{ "main": "jm-ex-partner@example.org"}'), + 1201=contact(caption='Frau Dr. Jenny Meyer-Billing , JM GmbH', emailAddresses='{ "main": "jm-billing@example.org"}'), + 1202=contact(caption='Herr Andrew Meyer-Operation , JM GmbH', emailAddresses='{ "main": "am-operation@example.org"}'), + 1203=contact(caption='Herr Philip Meyer-Contract , JM GmbH', emailAddresses='{ "main": "pm-partner@example.org"}'), + 1204=contact(caption='Frau Tammy Meyer-VIP , JM GmbH', emailAddresses='{ "main": "tm-vip@example.org"}'), + 1301=contact(caption='Petra Schmidt , Test PS', emailAddresses='{ "main": "ps@example.com"}'), + 132=contact(caption='Herr Ragnar Richter , Ragnar IT-Beratung', emailAddresses='{ "main": "hostsharing@ragnar-richter.de"}'), + 1401=contact(caption='Frau Frauke Fanninga ', emailAddresses='{ "main": "ff@example.org"}'), + 1501=contact(caption='Frau Cecilia Camus ', emailAddresses='{ "main": "cc@example.org"}'), + 212=contact(caption='Firma Hostmaster Hostsharing , Hostsharing e.G.', emailAddresses='{ "main": "hostmaster@hostsharing.net"}'), + 90436=contact(caption='Frau Christiane Milberg , Wasserwerk Südholstein', emailAddresses='{ "main": "rechnung@ww-sholst.example.org"}'), + 90437=contact(caption='Herr Richard Wiese , Das Perfekte Haus', emailAddresses='{ "main": "admin@das-perfekte-haus.example.org"}'), + 90438=contact(caption='Herr Karim Metzger , Wasswerwerk Südholstein', emailAddresses='{ "main": "karim.metzger@ww-sholst.example.org"}'), + 90590=contact(caption='Herr Inhaber R. Wiese , Das Perfekte Haus', emailAddresses='{ "main": "515217@kkemail.example.org"}'), + 90629=contact(caption='Ragnar Richter ', emailAddresses='{ "main": "mail@ragnar-richter..example.org"}'), + 90677=contact(caption='Eike Henning ', emailAddresses='{ "main": "hostsharing@eike-henning..example.org"}'), + 90698=contact(caption='Jan Henning ', emailAddresses='{ "main": "mail@jan-henning.example.org"}') + } """); assertThat(toFormattedString(persons)).isEqualToIgnoringWhitespace(""" { - 1=person(personType='LP', tradeName='Hostsharing eG'), - 1101=person(personType='NP', familyName='Mellies', givenName='Michael'), - 1200=person(personType='LP', tradeName='JM e.K.'), - 1201=person(personType='LP', tradeName='JM GmbH', familyName='Meyer-Billing', givenName='Jenny'), - 1202=person(personType='LP', tradeName='JM GmbH', familyName='Meyer-Operation', givenName='Andrew'), - 1203=person(personType='LP', tradeName='JM GmbH', familyName='Meyer-Contract', givenName='Philip'), - 1204=person(personType='LP', tradeName='JM GmbH', familyName='Meyer-VIP', givenName='Tammy'), - 1301=person(personType='??', tradeName='Test PS', familyName='Schmidt', givenName='Petra'), - 1401=person(personType='NP', familyName='Fanninga', givenName='Frauke'), - 1501=person(personType='NP', familyName='Camus', givenName='Cecilia') - } + 100=person(personType='??', tradeName='Michael Mellis', familyName='Mellis', givenName='Michael'), + 1200=person(personType='LP', tradeName='JM e.K.'), + 1201=person(personType='LP', tradeName='JM GmbH', familyName='Meyer-Billing', givenName='Jenny'), + 1202=person(personType='LP', tradeName='JM GmbH', familyName='Meyer-Operation', givenName='Andrew'), + 1203=person(personType='LP', tradeName='JM GmbH', familyName='Meyer-Contract', givenName='Philip'), + 1204=person(personType='LP', tradeName='JM GmbH', familyName='Meyer-VIP', givenName='Tammy'), + 1301=person(personType='??', tradeName='Test PS', familyName='Schmidt', givenName='Petra'), + 132=person(personType='??', tradeName='Ragnar IT-Beratung', familyName='Richter', givenName='Ragnar'), + 1401=person(personType='NP', familyName='Fanninga', givenName='Frauke'), + 1501=person(personType='NP', familyName='Camus', givenName='Cecilia'), + 212=person(personType='LP', tradeName='Hostsharing e.G.', familyName='Hostsharing', givenName='Hostmaster'), + 90436=person(personType='??', tradeName='Wasserwerk Südholstein', familyName='Milberg', givenName='Christiane'), + 90437=person(personType='??', tradeName='Das Perfekte Haus', familyName='Wiese', givenName='Richard'), + 90438=person(personType='??', tradeName='Wasswerwerk Südholstein', familyName='Metzger', givenName='Karim'), + 90590=person(personType='??', tradeName='Das Perfekte Haus', familyName='Wiese', givenName='Inhaber R.'), + 90629=person(personType='NP', familyName='Richter', givenName='Ragnar'), + 90677=person(personType='NP', familyName='Henning', givenName='Eike'), + 90698=person(personType='NP', familyName='Henning', givenName='Jan') + } """); assertThat(toFormattedString(debitors)).isEqualToIgnoringWhitespace(""" { - 17=debitor(D-1001700: rel(anchor='NP Mellies, Michael', type='DEBITOR', holder='NP Mellies, Michael'), mih), - 20=debitor(D-1002000: rel(anchor='LP JM GmbH', type='DEBITOR', holder='LP JM GmbH'), xyz), - 22=debitor(D-1102200: rel(anchor='?? Test PS', type='DEBITOR', holder='?? Test PS'), xxx), - 90=debitor(D-1909000: rel(anchor='NP Camus, Cecilia', type='DEBITOR', holder='NP Camus, Cecilia'), yyy), - 99=debitor(D-1999900: rel(anchor='null null, null', type='DEBITOR'), zzz) + 100=debitor(D-1000300: rel(anchor='?? Michael Mellis', type='DEBITOR', holder='?? Michael Mellis'), mim), + 120=debitor(D-1002000: rel(anchor='LP JM GmbH', type='DEBITOR', holder='LP JM GmbH'), xyz), + 122=debitor(D-1102200: rel(anchor='?? Test PS', type='DEBITOR', holder='?? Test PS'), xxx), + 132=debitor(D-1015200: rel(anchor='?? Ragnar IT-Beratung', type='DEBITOR', holder='?? Ragnar IT-Beratung'), rar), + 190=debitor(D-1909000: rel(anchor='NP Camus, Cecilia', type='DEBITOR', holder='NP Camus, Cecilia'), yyy), + 199=debitor(D-1999900: rel(anchor='null null, null', type='DEBITOR'), zzz), + 213=debitor(D-1000000: rel(anchor='LP Hostsharing e.G.', type='DEBITOR', holder='LP Hostsharing e.G.'), hsh), + 541=debitor(D-1101800: rel(anchor='?? Wasserwerk Südholstein', type='DEBITOR', holder='?? Wasserwerk Südholstein'), wws), + 542=debitor(D-1101900: rel(anchor='?? Das Perfekte Haus', type='DEBITOR', holder='?? Das Perfekte Haus'), dph) } """); assertThat(toFormattedString(memberships)).isEqualToIgnoringWhitespace(""" { - 17=Membership(M-1001700, P-10017, [2000-12-06,), ACTIVE), - 20=Membership(M-1002000, P-10020, [2000-12-06,2016-01-01), UNKNOWN), - 22=Membership(M-1102200, P-11022, [2021-04-01,), ACTIVE) + 100=Membership(M-1000300, P-10003, [2000-12-06,), ACTIVE), + 120=Membership(M-1002000, P-10020, [2000-12-06,2016-01-01), UNKNOWN), + 122=Membership(M-1102200, P-11022, [2021-04-01,), ACTIVE), + 132=Membership(M-1015200, P-10152, [2003-07-12,), ACTIVE), + 541=Membership(M-1101800, P-11018, [2021-05-17,), ACTIVE), + 542=Membership(M-1101900, P-11019, [2021-05-25,), ACTIVE) } """); assertThat(toFormattedString(relations)).isEqualToIgnoringWhitespace(""" { - 2000000=rel(anchor='LP Hostsharing eG', type='PARTNER', holder='NP Mellies, Michael', contact='Herr Michael Mellies '), - 2000001=rel(anchor='NP Mellies, Michael', type='DEBITOR', holder='NP Mellies, Michael', contact='Herr Michael Mellies '), - 2000002=rel(anchor='LP Hostsharing eG', type='PARTNER', holder='LP JM GmbH', contact='Herr Philip Meyer-Contract , JM GmbH'), - 2000003=rel(anchor='LP JM GmbH', type='DEBITOR', holder='LP JM GmbH', contact='Frau Dr. Jenny Meyer-Billing , JM GmbH'), - 2000004=rel(anchor='LP Hostsharing eG', type='PARTNER', holder='?? Test PS', contact='Petra Schmidt , Test PS'), - 2000005=rel(anchor='?? Test PS', type='DEBITOR', holder='?? Test PS', contact='Petra Schmidt , Test PS'), - 2000006=rel(anchor='LP Hostsharing eG', type='PARTNER', holder='NP Camus, Cecilia', contact='Frau Cecilia Camus '), - 2000007=rel(anchor='NP Camus, Cecilia', type='DEBITOR', holder='NP Camus, Cecilia', contact='Frau Cecilia Camus '), - 2000008=rel(anchor='LP Hostsharing eG', type='PARTNER', holder='null null, null'), - 2000009=rel(anchor='null null, null', type='DEBITOR'), - 2000010=rel(anchor='NP Mellies, Michael', type='OPERATIONS', holder='NP Mellies, Michael', contact='Herr Michael Mellies '), - 2000011=rel(anchor='NP Mellies, Michael', type='REPRESENTATIVE', holder='NP Mellies, Michael', contact='Herr Michael Mellies '), - 2000012=rel(anchor='LP JM GmbH', type='EX_PARTNER', holder='LP JM e.K.', contact='JM e.K.'), - 2000013=rel(anchor='LP JM GmbH', type='OPERATIONS', holder='LP JM GmbH', contact='Herr Andrew Meyer-Operation , JM GmbH'), - 2000014=rel(anchor='LP JM GmbH', type='VIP_CONTACT', holder='LP JM GmbH', contact='Herr Andrew Meyer-Operation , JM GmbH'), - 2000015=rel(anchor='LP JM GmbH', type='SUBSCRIBER', mark='operations-announce', holder='LP JM GmbH', contact='Herr Andrew Meyer-Operation , JM GmbH'), - 2000016=rel(anchor='LP JM GmbH', type='REPRESENTATIVE', holder='LP JM GmbH', contact='Herr Philip Meyer-Contract , JM GmbH'), - 2000017=rel(anchor='LP JM GmbH', type='SUBSCRIBER', mark='members-announce', holder='LP JM GmbH', contact='Herr Philip Meyer-Contract , JM GmbH'), - 2000018=rel(anchor='LP JM GmbH', type='SUBSCRIBER', mark='customers-announce', holder='LP JM GmbH', contact='Herr Philip Meyer-Contract , JM GmbH'), - 2000019=rel(anchor='LP JM GmbH', type='VIP_CONTACT', holder='LP JM GmbH', contact='Frau Tammy Meyer-VIP , JM GmbH'), - 2000020=rel(anchor='?? Test PS', type='OPERATIONS', holder='?? Test PS', contact='Petra Schmidt , Test PS'), - 2000021=rel(anchor='?? Test PS', type='REPRESENTATIVE', holder='?? Test PS', contact='Petra Schmidt , Test PS'), - 2000022=rel(anchor='NP Mellies, Michael', type='SUBSCRIBER', mark='operations-announce', holder='NP Fanninga, Frauke', contact='Frau Frauke Fanninga '), - 2000023=rel(anchor='NP Camus, Cecilia', type='OPERATIONS', holder='NP Camus, Cecilia', contact='Frau Cecilia Camus '), - 2000024=rel(anchor='NP Camus, Cecilia', type='REPRESENTATIVE', holder='NP Camus, Cecilia', contact='Frau Cecilia Camus ') + 2000000=rel(anchor='LP Hostsharing e.G.', type='PARTNER', holder='?? Michael Mellis', contact='Herr Michael Mellis , Michael Mellis'), + 2000001=rel(anchor='?? Michael Mellis', type='DEBITOR', holder='?? Michael Mellis', contact='Herr Michael Mellis , Michael Mellis'), + 2000002=rel(anchor='LP Hostsharing e.G.', type='PARTNER', holder='?? Ragnar IT-Beratung', contact='Herr Ragnar Richter , Ragnar IT-Beratung'), + 2000003=rel(anchor='?? Ragnar IT-Beratung', type='DEBITOR', holder='?? Ragnar IT-Beratung', contact='Herr Ragnar Richter , Ragnar IT-Beratung'), + 2000004=rel(anchor='LP Hostsharing e.G.', type='PARTNER', holder='LP Hostsharing e.G.', contact='Firma Hostmaster Hostsharing , Hostsharing e.G.'), + 2000005=rel(anchor='LP Hostsharing e.G.', type='DEBITOR', holder='LP Hostsharing e.G.', contact='Firma Hostmaster Hostsharing , Hostsharing e.G.'), + 2000006=rel(anchor='LP Hostsharing e.G.', type='PARTNER', holder='?? Wasserwerk Südholstein', contact='Frau Christiane Milberg , Wasserwerk Südholstein'), + 2000007=rel(anchor='?? Wasserwerk Südholstein', type='DEBITOR', holder='?? Wasserwerk Südholstein', contact='Frau Christiane Milberg , Wasserwerk Südholstein'), + 2000008=rel(anchor='LP Hostsharing e.G.', type='PARTNER', holder='?? Das Perfekte Haus', contact='Herr Richard Wiese , Das Perfekte Haus'), + 2000009=rel(anchor='?? Das Perfekte Haus', type='DEBITOR', holder='?? Das Perfekte Haus', contact='Herr Inhaber R. Wiese , Das Perfekte Haus'), + 2000010=rel(anchor='LP Hostsharing e.G.', type='PARTNER', holder='LP JM GmbH', contact='Herr Philip Meyer-Contract , JM GmbH'), + 2000011=rel(anchor='LP JM GmbH', type='DEBITOR', holder='LP JM GmbH', contact='Frau Dr. Jenny Meyer-Billing , JM GmbH'), + 2000012=rel(anchor='LP Hostsharing e.G.', type='PARTNER', holder='?? Test PS', contact='Petra Schmidt , Test PS'), + 2000013=rel(anchor='?? Test PS', type='DEBITOR', holder='?? Test PS', contact='Petra Schmidt , Test PS'), + 2000014=rel(anchor='LP Hostsharing e.G.', type='PARTNER', holder='NP Camus, Cecilia', contact='Frau Cecilia Camus '), + 2000015=rel(anchor='NP Camus, Cecilia', type='DEBITOR', holder='NP Camus, Cecilia', contact='Frau Cecilia Camus '), + 2000016=rel(anchor='LP Hostsharing e.G.', type='PARTNER', holder='null null, null'), + 2000017=rel(anchor='null null, null', type='DEBITOR'), + 2000018=rel(anchor='LP Hostsharing e.G.', type='OPERATIONS', holder='LP Hostsharing e.G.', contact='Firma Hostmaster Hostsharing , Hostsharing e.G.'), + 2000019=rel(anchor='LP Hostsharing e.G.', type='REPRESENTATIVE', holder='LP Hostsharing e.G.', contact='Firma Hostmaster Hostsharing , Hostsharing e.G.'), + 2000020=rel(anchor='?? Michael Mellis', type='OPERATIONS', holder='?? Michael Mellis', contact='Herr Michael Mellis , Michael Mellis'), + 2000021=rel(anchor='?? Michael Mellis', type='REPRESENTATIVE', holder='?? Michael Mellis', contact='Herr Michael Mellis , Michael Mellis'), + 2000022=rel(anchor='?? Michael Mellis', type='SUBSCRIBER', mark='operations-discussion', holder='?? Michael Mellis', contact='Herr Michael Mellis , Michael Mellis'), + 2000023=rel(anchor='?? Michael Mellis', type='SUBSCRIBER', mark='operations-announce', holder='?? Michael Mellis', contact='Herr Michael Mellis , Michael Mellis'), + 2000024=rel(anchor='?? Michael Mellis', type='SUBSCRIBER', mark='generalversammlung', holder='?? Michael Mellis', contact='Herr Michael Mellis , Michael Mellis'), + 2000025=rel(anchor='?? Michael Mellis', type='SUBSCRIBER', mark='members-announce', holder='?? Michael Mellis', contact='Herr Michael Mellis , Michael Mellis'), + 2000026=rel(anchor='?? Michael Mellis', type='SUBSCRIBER', mark='members-discussion', holder='?? Michael Mellis', contact='Herr Michael Mellis , Michael Mellis'), + 2000027=rel(anchor='?? Ragnar IT-Beratung', type='OPERATIONS', holder='?? Ragnar IT-Beratung', contact='Herr Ragnar Richter , Ragnar IT-Beratung'), + 2000028=rel(anchor='?? Ragnar IT-Beratung', type='SUBSCRIBER', mark='operations-discussion', holder='?? Ragnar IT-Beratung', contact='Herr Ragnar Richter , Ragnar IT-Beratung'), + 2000029=rel(anchor='?? Ragnar IT-Beratung', type='SUBSCRIBER', mark='operations-announce', holder='?? Ragnar IT-Beratung', contact='Herr Ragnar Richter , Ragnar IT-Beratung'), + 2000030=rel(anchor='LP JM GmbH', type='EX_PARTNER', holder='LP JM e.K.', contact='JM e.K.'), + 2000031=rel(anchor='LP JM GmbH', type='OPERATIONS', holder='LP JM GmbH', contact='Herr Andrew Meyer-Operation , JM GmbH'), + 2000032=rel(anchor='LP JM GmbH', type='VIP_CONTACT', holder='LP JM GmbH', contact='Herr Andrew Meyer-Operation , JM GmbH'), + 2000033=rel(anchor='LP JM GmbH', type='SUBSCRIBER', mark='operations-announce', holder='LP JM GmbH', contact='Herr Andrew Meyer-Operation , JM GmbH'), + 2000034=rel(anchor='LP JM GmbH', type='REPRESENTATIVE', holder='LP JM GmbH', contact='Herr Philip Meyer-Contract , JM GmbH'), + 2000035=rel(anchor='LP JM GmbH', type='SUBSCRIBER', mark='members-announce', holder='LP JM GmbH', contact='Herr Philip Meyer-Contract , JM GmbH'), + 2000036=rel(anchor='LP JM GmbH', type='SUBSCRIBER', mark='customers-announce', holder='LP JM GmbH', contact='Herr Philip Meyer-Contract , JM GmbH'), + 2000037=rel(anchor='LP JM GmbH', type='VIP_CONTACT', holder='LP JM GmbH', contact='Frau Tammy Meyer-VIP , JM GmbH'), + 2000038=rel(anchor='?? Test PS', type='OPERATIONS', holder='?? Test PS', contact='Petra Schmidt , Test PS'), + 2000039=rel(anchor='?? Test PS', type='REPRESENTATIVE', holder='?? Test PS', contact='Petra Schmidt , Test PS'), + 2000040=rel(anchor='LP JM GmbH', type='SUBSCRIBER', mark='operations-announce', holder='NP Fanninga, Frauke', contact='Frau Frauke Fanninga '), + 2000041=rel(anchor='NP Camus, Cecilia', type='OPERATIONS', holder='NP Camus, Cecilia', contact='Frau Cecilia Camus '), + 2000042=rel(anchor='NP Camus, Cecilia', type='REPRESENTATIVE', holder='NP Camus, Cecilia', contact='Frau Cecilia Camus '), + 2000043=rel(anchor='?? Wasserwerk Südholstein', type='REPRESENTATIVE', holder='?? Wasserwerk Südholstein', contact='Frau Christiane Milberg , Wasserwerk Südholstein'), + 2000044=rel(anchor='?? Wasserwerk Südholstein', type='SUBSCRIBER', mark='generalversammlung', holder='?? Wasserwerk Südholstein', contact='Frau Christiane Milberg , Wasserwerk Südholstein'), + 2000045=rel(anchor='?? Wasserwerk Südholstein', type='SUBSCRIBER', mark='members-announce', holder='?? Wasserwerk Südholstein', contact='Frau Christiane Milberg , Wasserwerk Südholstein'), + 2000046=rel(anchor='?? Wasserwerk Südholstein', type='SUBSCRIBER', mark='members-discussion', holder='?? Wasserwerk Südholstein', contact='Frau Christiane Milberg , Wasserwerk Südholstein'), + 2000047=rel(anchor='?? Das Perfekte Haus', type='OPERATIONS', holder='?? Das Perfekte Haus', contact='Herr Richard Wiese , Das Perfekte Haus'), + 2000048=rel(anchor='?? Das Perfekte Haus', type='REPRESENTATIVE', holder='?? Das Perfekte Haus', contact='Herr Richard Wiese , Das Perfekte Haus'), + 2000049=rel(anchor='?? Das Perfekte Haus', type='SUBSCRIBER', mark='operations-discussion', holder='?? Das Perfekte Haus', contact='Herr Richard Wiese , Das Perfekte Haus'), + 2000050=rel(anchor='?? Das Perfekte Haus', type='SUBSCRIBER', mark='operations-announce', holder='?? Das Perfekte Haus', contact='Herr Richard Wiese , Das Perfekte Haus'), + 2000051=rel(anchor='?? Das Perfekte Haus', type='SUBSCRIBER', mark='generalversammlung', holder='?? Das Perfekte Haus', contact='Herr Richard Wiese , Das Perfekte Haus'), + 2000052=rel(anchor='?? Das Perfekte Haus', type='SUBSCRIBER', mark='members-announce', holder='?? Das Perfekte Haus', contact='Herr Richard Wiese , Das Perfekte Haus'), + 2000053=rel(anchor='?? Das Perfekte Haus', type='SUBSCRIBER', mark='members-discussion', holder='?? Das Perfekte Haus', contact='Herr Richard Wiese , Das Perfekte Haus'), + 2000054=rel(anchor='?? Wasserwerk Südholstein', type='OPERATIONS', holder='?? Wasswerwerk Südholstein', contact='Herr Karim Metzger , Wasswerwerk Südholstein'), + 2000055=rel(anchor='?? Wasserwerk Südholstein', type='SUBSCRIBER', mark='operations-discussion', holder='?? Wasswerwerk Südholstein', contact='Herr Karim Metzger , Wasswerwerk Südholstein'), + 2000056=rel(anchor='?? Wasserwerk Südholstein', type='SUBSCRIBER', mark='operations-announce', holder='?? Wasswerwerk Südholstein', contact='Herr Karim Metzger , Wasswerwerk Südholstein'), + 2000057=rel(anchor='?? Ragnar IT-Beratung', type='REPRESENTATIVE', holder='NP Richter, Ragnar', contact='Ragnar Richter '), + 2000058=rel(anchor='?? Ragnar IT-Beratung', type='SUBSCRIBER', mark='generalversammlung', holder='NP Richter, Ragnar', contact='Ragnar Richter '), + 2000059=rel(anchor='?? Ragnar IT-Beratung', type='SUBSCRIBER', mark='members-announce', holder='NP Richter, Ragnar', contact='Ragnar Richter '), + 2000060=rel(anchor='?? Ragnar IT-Beratung', type='SUBSCRIBER', mark='members-discussion', holder='NP Richter, Ragnar', contact='Ragnar Richter '), + 2000061=rel(anchor='?? Ragnar IT-Beratung', type='OPERATIONS', holder='NP Henning, Eike', contact='Eike Henning '), + 2000062=rel(anchor='?? Ragnar IT-Beratung', type='SUBSCRIBER', mark='operations-discussion', holder='NP Henning, Eike', contact='Eike Henning '), + 2000063=rel(anchor='?? Ragnar IT-Beratung', type='SUBSCRIBER', mark='operations-announce', holder='NP Henning, Eike', contact='Eike Henning '), + 2000064=rel(anchor='?? Ragnar IT-Beratung', type='OPERATIONS', holder='NP Henning, Jan', contact='Jan Henning ') } """); } @@ -322,8 +359,9 @@ public class ImportOfficeData extends ContextBasedTest { @Test @Order(1030) void importSepaMandates() { + assumeThatWeAreExplicitlyImportingOfficeData(); - try (Reader reader = resourceReader(MIGRATION_DATA_PATH + "sepa-mandates.csv")) { + try (Reader reader = resourceReader(MIGRATION_DATA_PATH + "/office/sepa_mandates.csv")) { final var lines = readAllLines(reader); importSepaMandates(justHeader(lines), withoutHeader(lines)); } catch (Exception e) { @@ -334,20 +372,29 @@ public class ImportOfficeData extends ContextBasedTest { @Test @Order(1039) void verifySepaMandates() { + assumeThatWeAreExplicitlyImportingOfficeData(); assumeThatWeAreImportingControlledTestData(); assertThat(toFormattedString(bankAccounts)).isEqualToIgnoringWhitespace(""" { - 234234=bankAccount(DE37500105177419788228: holder='Michael Mellies', bic='INGDDEFFXXX'), - 235600=bankAccount(DE02300209000106531065: holder='JM e.K.', bic='CMCIDEDD'), - 235662=bankAccount(DE49500105174516484892: holder='JM GmbH', bic='INGDDEFFXXX') + 132=bankAccount(DE37500105177419788228: holder='Michael Mellis', bic='GENODEF1HH2'), + 234234=bankAccount(DE37500105177419788228: holder='Michael Mellis', bic='INGDDEFFXXX'), + 235600=bankAccount(DE02300209000106531065: holder='JM e.K.', bic='CMCIDEDD'), + 235662=bankAccount(DE49500105174516484892: holder='JM GmbH', bic='INGDDEFFXXX'), + 30=bankAccount(DE02300209000106531065: holder='Ragnar Richter', bic='GENODEM1GLS'), + 386=bankAccount(DE49500105174516484892: holder='Wasserwerk Suedholstein', bic='NOLADE21WHO'), + 387=bankAccount(DE89370400440532013000: holder='Richard Wiese Das Perfekte Haus', bic='COBADEFFXXX') } """); assertThat(toFormattedString(sepaMandates)).isEqualToIgnoringWhitespace(""" { - 234234=SEPA-Mandate(DE37500105177419788228, MH12345, 2004-06-12, [2004-06-15,)), - 235600=SEPA-Mandate(DE02300209000106531065, JM33344, 2004-01-15, [2004-01-20,2005-06-28)), - 235662=SEPA-Mandate(DE49500105174516484892, JM33344, 2005-06-28, [2005-07-01,)) + 132=SEPA-Mandate(DE37500105177419788228, HS-10003-20140801, 2013-12-01, [2013-12-01,)), + 234234=SEPA-Mandate(DE37500105177419788228, MH12345, 2004-06-12, [2004-06-15,)), + 235600=SEPA-Mandate(DE02300209000106531065, JM33344, 2004-01-15, [2004-01-20,2005-06-28)), + 235662=SEPA-Mandate(DE49500105174516484892, JM33344, 2005-06-28, [2005-07-01,)), + 30=SEPA-Mandate(DE02300209000106531065, HS-10152-20140801, 2013-12-01, [2013-12-01,2016-02-16)), + 386=SEPA-Mandate(DE49500105174516484892, HS-11018-20210512, 2021-05-12, [2021-05-17,)), + 387=SEPA-Mandate(DE89370400440532013000, HS-11019-20210519, 2021-05-19, [2021-05-25,)) } """); } @@ -355,7 +402,9 @@ public class ImportOfficeData extends ContextBasedTest { @Test @Order(1040) void importCoopShares() { - try (Reader reader = resourceReader(MIGRATION_DATA_PATH + "share-transactions.csv")) { + assumeThatWeAreExplicitlyImportingOfficeData(); + + try (Reader reader = resourceReader(MIGRATION_DATA_PATH + "/office/share_transactions.csv")) { final var lines = readAllLines(reader); importCoopShares(justHeader(lines), withoutHeader(lines)); } catch (Exception e) { @@ -366,23 +415,32 @@ public class ImportOfficeData extends ContextBasedTest { @Test @Order(1041) void verifyCoopShares() { + assumeThatWeAreExplicitlyImportingOfficeData(); assumeThatWeAreImportingControlledTestData(); assertThat(toFormattedString(coopShares)).isEqualToIgnoringWhitespace(""" { - 33443=CoopShareTransaction(M-1001700: 2000-12-06, SUBSCRIPTION, 20, 1001700, initial share subscription), - 33451=CoopShareTransaction(M-1002000: 2000-12-06, SUBSCRIPTION, 2, 1002000, initial share subscription), - 33701=CoopShareTransaction(M-1001700: 2005-01-10, SUBSCRIPTION, 40, 1001700, increase), - 33810=CoopShareTransaction(M-1002000: 2016-12-31, CANCELLATION, 22, 1002000, membership ended) - } + 241=CoopShareTransaction(M-1000300: 2011-12-05, SUBSCRIPTION, 16, 1000300), + 279=CoopShareTransaction(M-1015200: 2013-10-21, SUBSCRIPTION, 1, 1015200), + 33451=CoopShareTransaction(M-1002000: 2000-12-06, SUBSCRIPTION, 2, 1002000, initial share subscription), + 33701=CoopShareTransaction(M-1000300: 2005-01-10, SUBSCRIPTION, 40, 1000300, increase), + 33810=CoopShareTransaction(M-1002000: 2016-12-31, CANCELLATION, 22, 1002000, membership ended), + 3=CoopShareTransaction(M-1000300: 2000-12-06, SUBSCRIPTION, 80, 1000300, initial share subscription), + 523=CoopShareTransaction(M-1000300: 2020-12-08, SUBSCRIPTION, 96, 1000300, Kapitalerhoehung), + 562=CoopShareTransaction(M-1101800: 2021-05-17, SUBSCRIPTION, 4, 1101800, Beitritt), + 563=CoopShareTransaction(M-1101900: 2021-05-25, SUBSCRIPTION, 1, 1101900, Beitritt), + 721=CoopShareTransaction(M-1000300: 2023-10-10, SUBSCRIPTION, 96, 1000300, Kapitalerhoehung), + 90=CoopShareTransaction(M-1015200: 2003-07-12, SUBSCRIPTION, 1, 1015200) + } """); } @Test @Order(1050) void importCoopAssets() { + assumeThatWeAreExplicitlyImportingOfficeData(); - try (Reader reader = resourceReader(MIGRATION_DATA_PATH + "asset-transactions.csv")) { + try (Reader reader = resourceReader(MIGRATION_DATA_PATH + "/office/asset_transactions.csv")) { final var lines = readAllLines(reader); importCoopAssets(justHeader(lines), withoutHeader(lines)); } catch (Exception e) { @@ -393,20 +451,29 @@ public class ImportOfficeData extends ContextBasedTest { @Test @Order(1059) void verifyCoopAssets() { + assumeThatWeAreExplicitlyImportingOfficeData(); assumeThatWeAreImportingControlledTestData(); assertThat(toFormattedString(coopAssets)).isEqualToIgnoringWhitespace(""" { - 30000=CoopAssetsTransaction(M-1001700: 2000-12-06, DEPOSIT, 1280.00, 1001700, for subscription A), - 31000=CoopAssetsTransaction(M-1002000: 2000-12-06, DEPOSIT, 128.00, 1002000, for subscription B), - 32000=CoopAssetsTransaction(M-1001700: 2005-01-10, DEPOSIT, 2560.00, 1001700, for subscription C), - 33001=CoopAssetsTransaction(M-1001700: 2005-01-10, TRANSFER, -512.00, 1001700, for transfer to 10), - 33002=CoopAssetsTransaction(M-1002000: 2005-01-10, ADOPTION, 512.00, 1002000, for transfer from 7), - 34001=CoopAssetsTransaction(M-1002000: 2016-12-31, CLEARING, -8.00, 1002000, for cancellation D), - 34002=CoopAssetsTransaction(M-1002000: 2016-12-31, DISBURSAL, -100.00, 1002000, for cancellation D), - 34003=CoopAssetsTransaction(M-1002000: 2016-12-31, LOSS, -20.00, 1002000, for cancellation D), - 35001=CoopAssetsTransaction(M-1909000: 2024-01-15, DEPOSIT, 128.00, 1909000, for subscription E), - 35002=CoopAssetsTransaction(M-1909000: 2024-01-20, ADJUSTMENT, -128.00, 1909000, chargeback for subscription E, M-1909000:DEP:+128.00) + 1093=CoopAssetsTransaction(M-1000300: 2023-10-05, DEPOSIT, 3072, 1000300, Kapitalerhoehung - Ueberweisung), + 1094=CoopAssetsTransaction(M-1000300: 2023-10-06, DEPOSIT, 3072, 1000300, Kapitalerhoehung - Ueberweisung), + 31000=CoopAssetsTransaction(M-1002000: 2000-12-06, DEPOSIT, 128.00, 1002000, for subscription B), + 32000=CoopAssetsTransaction(M-1000300: 2005-01-10, DEPOSIT, 2560.00, 1000300, for subscription C), + 33001=CoopAssetsTransaction(M-1000300: 2005-01-10, TRANSFER, -512.00, 1000300, for transfer to 10), + 33002=CoopAssetsTransaction(M-1002000: 2005-01-10, ADOPTION, 512.00, 1002000, for transfer from 7), + 34001=CoopAssetsTransaction(M-1002000: 2016-12-31, CLEARING, -8.00, 1002000, for cancellation D), + 34002=CoopAssetsTransaction(M-1002000: 2016-12-31, DISBURSAL, -100.00, 1002000, for cancellation D), + 34003=CoopAssetsTransaction(M-1002000: 2016-12-31, LOSS, -20.00, 1002000, for cancellation D), + 35001=CoopAssetsTransaction(M-1909000: 2024-01-15, DEPOSIT, 128.00, 1909000, for subscription E), + 35002=CoopAssetsTransaction(M-1909000: 2024-01-20, ADJUSTMENT, -128.00, 1909000, chargeback for subscription E, M-1909000:DEP:+128.00), + 358=CoopAssetsTransaction(M-1000300: 2000-12-06, DEPOSIT, 5120, 1000300, for subscription A), + 442=CoopAssetsTransaction(M-1015200: 2003-07-07, DEPOSIT, 64, 1015200), + 577=CoopAssetsTransaction(M-1000300: 2011-12-12, DEPOSIT, 1024, 1000300), + 632=CoopAssetsTransaction(M-1015200: 2013-10-21, DEPOSIT, 64, 1015200), + 885=CoopAssetsTransaction(M-1000300: 2020-12-15, DEPOSIT, 6144, 1000300, Einzahlung), + 924=CoopAssetsTransaction(M-1101800: 2021-05-21, DEPOSIT, 256, 1101800, Beitritt - Lastschrift), + 925=CoopAssetsTransaction(M-1101900: 2021-05-31, DEPOSIT, 64, 1101900, Beitritt - Lastschrift) } """); } @@ -414,13 +481,18 @@ public class ImportOfficeData extends ContextBasedTest { @Test @Order(1099) void verifyMemberships() { + assumeThatWeAreExplicitlyImportingOfficeData(); assumeThatWeAreImportingControlledTestData(); + assertThat(toFormattedString(memberships)).isEqualToIgnoringWhitespace(""" { - 17=Membership(M-1001700, P-10017, [2000-12-06,), ACTIVE), - 20=Membership(M-1002000, P-10020, [2000-12-06,2016-01-01), UNKNOWN), - 22=Membership(M-1102200, P-11022, [2021-04-01,), ACTIVE), - 90=Membership(M-1909000, P-19090, empty, INVALID) + 100=Membership(M-1000300, P-10003, [2000-12-06,), ACTIVE), + 120=Membership(M-1002000, P-10020, [2000-12-06,2016-01-01), UNKNOWN), + 122=Membership(M-1102200, P-11022, [2021-04-01,), ACTIVE), + 132=Membership(M-1015200, P-10152, [2003-07-12,), ACTIVE), + 190=Membership(M-1909000, P-19090, empty, INVALID), + 541=Membership(M-1101800, P-11018, [2021-05-17,), ACTIVE), + 542=Membership(M-1101900, P-11019, [2021-05-25,), ACTIVE) } """); } @@ -431,11 +503,11 @@ public class ImportOfficeData extends ContextBasedTest { partners.forEach((id, p) -> { final var partnerRel = p.getPartnerRel(); assertThat(partnerRel).describedAs("partner " + id + " without partnerRel").isNotNull(); - if ( id != 99 ) { - assertThat(partnerRel.getContact()).describedAs("partner " + id + " without partnerRel.contact").isNotNull(); - assertThat(partnerRel.getContact().getCaption()).describedAs("partner " + id + " without valid partnerRel.contact").isNotNull(); - assertThat(partnerRel.getHolder()).describedAs("partner " + id + " without partnerRel.relHolder").isNotNull(); - assertThat(partnerRel.getHolder().getPersonType()).describedAs("partner " + id + " without valid partnerRel.relHolder").isNotNull(); + if ( id != 199 ) { + logError( () -> assertThat(partnerRel.getContact()).describedAs("partner " + id + " without partnerRel.contact").isNotNull()); + logError( () -> assertThat(partnerRel.getContact().getCaption()).describedAs("partner " + id + " without valid partnerRel.contact").isNotNull()); + logError( () -> assertThat(partnerRel.getHolder()).describedAs("partner " + id + " without partnerRel.relHolder").isNotNull()); + logError( () -> assertThat(partnerRel.getHolder().getPersonType()).describedAs("partner " + id + " without valid partnerRel.relHolder").isNotNull()); } }); } @@ -523,13 +595,29 @@ public class ImportOfficeData extends ContextBasedTest { } @Test - @Order(9000) - @Commit - void persistEntities() { + @Order(3005) + void removeEmptyPersons() { + // avoid a error when persisting the deliberately invalid partner entry #99 + final var idsToRemove = new HashSet(); + persons.forEach( (id, p) -> { + if ( p.getPersonType() == null || + (p.getFamilyName() == null && p.getGivenName() == null && p.getTradeName() == null) ) { + idsToRemove.add(id); + } + }); + idsToRemove.forEach(id -> persons.remove(id)); - System.out.println("PERSISTING to database '" + jdbcUrl + "' as user '" + postgresAdminUser + "'"); + assumeThatWeAreImportingControlledTestData(); + assertThat(idsToRemove.size()).isEqualTo(0); + } + + @Test + @Order(9000) + void persistOfficeEntities() { + + System.out.println("PERSISTING office data to database '" + jdbcUrl + "' as user '" + postgresAdminUser + "'"); deleteTestDataFromHsOfficeTables(); - resetFromHsOfficeSequences(); + resetHsOfficeSequences(); deleteFromTestTables(); deleteFromRbacTables(); @@ -542,6 +630,8 @@ public class ImportOfficeData extends ContextBasedTest { jpaAttempt.transacted(() -> { context(rbacSuperuser); persons.forEach(this::persist); + relations.forEach( (id, rel) -> this.persist(id, rel.getAnchor()) ); + relations.forEach( (id, rel) -> this.persist(id, rel.getHolder()) ); }).assertSuccessful(); jpaAttempt.transacted(() -> { @@ -602,18 +692,8 @@ public class ImportOfficeData extends ContextBasedTest { } - private void persist(final Integer id, final RbacObject entity) { - try { - //System.out.println("persisting #" + entity.hashCode() + ": " + entity); - em.persist(entity); - // uncomment for debugging purposes - // em.flush(); - // System.out.println("persisted #" + entity.hashCode() + " as " + entity.getUuid()); - } catch (Exception exc) { - System.err.println("failed to persist #" + entity.hashCode() + ": " + entity); - System.err.println(exc); - } - + protected void assumeThatWeAreExplicitlyImportingOfficeData() { + // not throwing AssumptionException } private static boolean isImportingControlledTestData() { @@ -624,62 +704,6 @@ public class ImportOfficeData extends ContextBasedTest { assumeThat(partners.size()).isLessThanOrEqualTo(MAX_NUMBER_OF_TEST_DATA_PARTNERS); } - private void deleteTestDataFromHsOfficeTables() { - jpaAttempt.transacted(() -> { - context(rbacSuperuser); - em.createNativeQuery("delete from hs_hosting_asset where true").executeUpdate(); - em.createNativeQuery("delete from hs_booking_item where true").executeUpdate(); - em.createNativeQuery("delete from hs_booking_project where true").executeUpdate(); - em.createNativeQuery("delete from hs_office_coopassetstransaction where true").executeUpdate(); - em.createNativeQuery("delete from hs_office_coopassetstransaction_legacy_id where true").executeUpdate(); - em.createNativeQuery("delete from hs_office_coopsharestransaction where true").executeUpdate(); - em.createNativeQuery("delete from hs_office_coopsharestransaction_legacy_id where true").executeUpdate(); - em.createNativeQuery("delete from hs_office_membership where true").executeUpdate(); - em.createNativeQuery("delete from hs_office_sepamandate where true").executeUpdate(); - em.createNativeQuery("delete from hs_office_sepamandate_legacy_id where true").executeUpdate(); - em.createNativeQuery("delete from hs_office_debitor where true").executeUpdate(); - em.createNativeQuery("delete from hs_office_bankaccount where true").executeUpdate(); - em.createNativeQuery("delete from hs_office_partner where true").executeUpdate(); - em.createNativeQuery("delete from hs_office_partner_details where true").executeUpdate(); - em.createNativeQuery("delete from hs_office_relation where true").executeUpdate(); - em.createNativeQuery("delete from hs_office_contact where true").executeUpdate(); - em.createNativeQuery("delete from hs_office_person where true").executeUpdate(); - }).assertSuccessful(); - } - - private void resetFromHsOfficeSequences() { - jpaAttempt.transacted(() -> { - context(rbacSuperuser); - em.createNativeQuery("alter sequence hs_office_contact_legacy_id_seq restart with 1000000000;").executeUpdate(); - em.createNativeQuery("alter sequence hs_office_coopassetstransaction_legacy_id_seq restart with 1000000000;") - .executeUpdate(); - em.createNativeQuery("alter sequence public.hs_office_coopsharestransaction_legacy_id_seq restart with 1000000000;") - .executeUpdate(); - em.createNativeQuery("alter sequence public.hs_office_partner_legacy_id_seq restart with 1000000000;") - .executeUpdate(); - em.createNativeQuery("alter sequence public.hs_office_sepamandate_legacy_id_seq restart with 1000000000;") - .executeUpdate(); - }); - } - - private void deleteFromTestTables() { - jpaAttempt.transacted(() -> { - context(rbacSuperuser); - em.createNativeQuery("delete from test_domain where true").executeUpdate(); - em.createNativeQuery("delete from test_package where true").executeUpdate(); - em.createNativeQuery("delete from test_customer where true").executeUpdate(); - }).assertSuccessful(); - } - - private void deleteFromRbacTables() { - jpaAttempt.transacted(() -> { - context(rbacSuperuser); - em.createNativeQuery("delete from rbacuser_rv where name not like 'superuser-%'").executeUpdate(); - em.createNativeQuery("delete from tx_journal where true").executeUpdate(); - em.createNativeQuery("delete from tx_context where true").executeUpdate(); - }).assertSuccessful(); - } - private void updateLegacyIds( Map entities, final String legacyIdTable, @@ -698,59 +722,25 @@ public class ImportOfficeData extends ContextBasedTest { ); } - public List readAllLines(Reader reader) throws Exception { - - final var parser = new CSVParserBuilder() - .withSeparator(';') - .withQuoteChar('"') - .build(); - - final var filteredReader = skippingEmptyAndCommentLines(reader); - try (CSVReader csvReader = new CSVReaderBuilder(filteredReader) - .withCSVParser(parser) - .build()) { - return csvReader.readAll(); - } - } - - public static Reader skippingEmptyAndCommentLines(Reader reader) throws IOException { - try (var bufferedReader = new BufferedReader(reader); - StringWriter writer = new StringWriter()) { - - String line; - while ((line = bufferedReader.readLine()) != null) { - if (!line.isBlank() && !line.startsWith("#")) { - writer.write(line); - writer.write("\n"); - } - } - - return new StringReader(writer.toString()); - } - } - private void importBusinessPartners(final String[] header, final List records) { final var columns = new Columns(header); - final var mandant = HsOfficePersonEntity.builder() - .personType(HsOfficePersonType.LEGAL_PERSON) - .tradeName("Hostsharing eG") - .build(); - persons.put(1, mandant); - records.stream() .map(this::trimAll) .map(row -> new Record(columns, row)) .forEach(rec -> { - if (this.IGNORE_BUSINESS_PARTNERS.contains(rec.getInteger("bp_id"))) { + final Integer bpId = rec.getInteger("bp_id"); + if (IGNORE_BUSINESS_PARTNERS.contains(bpId)) { return; } final var person = HsOfficePersonEntity.builder().build(); final var partnerRel = addRelation( - HsOfficeRelationType.PARTNER, mandant, person, + HsOfficeRelationType.PARTNER, + null, // is set after contacts when the person for 'Hostsharing eG' is known + person, null // is set during contacts import depending on assigned roles ); @@ -759,7 +749,7 @@ public class ImportOfficeData extends ContextBasedTest { .details(HsOfficePartnerDetailsEntity.builder().build()) .partnerRel(partnerRel) .build(); - partners.put(rec.getInteger("bp_id"), partner); + partners.put(bpId, partner); final var debitorRel = addRelation( HsOfficeRelationType.DEBITOR, partnerRel.getHolder(), // partner person @@ -777,7 +767,7 @@ public class ImportOfficeData extends ContextBasedTest { .vatBusiness("GROSS".equals(rec.getString("indicator_vat"))) // TODO: remove .vatId(rec.getString("uid_vat")) .build(); - debitors.put(rec.getInteger("bp_id"), debitor); + debitors.put(bpId, debitor); if (isNotBlank(rec.getString("member_since"))) { assertThat(rec.getInteger("member_id")).isEqualTo(partner.getPartnerNumber()); @@ -793,7 +783,7 @@ public class ImportOfficeData extends ContextBasedTest { ? HsOfficeMembershipStatus.ACTIVE : HsOfficeMembershipStatus.UNKNOWN) .build(); - memberships.put(rec.getInteger("bp_id"), membership); + memberships.put(bpId, membership); } }); } @@ -807,6 +797,10 @@ public class ImportOfficeData extends ContextBasedTest { .map(row -> new Record(columns, row)) .forEach(rec -> { final var bpId = rec.getInteger("bp_id"); + if (IGNORE_BUSINESS_PARTNERS.contains(bpId)) { + return; + } + final var member = ofNullable(memberships.get(bpId)) .orElseGet(() -> createOnDemandMembership(bpId)); @@ -958,10 +952,10 @@ public class ImportOfficeData extends ContextBasedTest { final var contactId = rec.getInteger("contact_id"); final var bpId = rec.getInteger("bp_id"); - if (this.IGNORE_CONTACTS.contains(contactId)) { + if (IGNORE_CONTACTS.contains(contactId)) { return; } - if (this.IGNORE_BUSINESS_PARTNERS.contains(bpId)) { + if (IGNORE_BUSINESS_PARTNERS.contains(bpId)) { return; } @@ -1019,6 +1013,7 @@ public class ImportOfficeData extends ContextBasedTest { }); assertNoMissingContractualRelations(); + useHostsharingAsPartnerAnchor(); } private static void assertNoMissingContractualRelations() { @@ -1038,6 +1033,16 @@ public class ImportOfficeData extends ContextBasedTest { } } + private static void useHostsharingAsPartnerAnchor() { + final var mandant = persons.values().stream() + .filter(p -> p.getTradeName().startsWith("Hostsharing e")) + .findFirst() + .orElseThrow(); + relations.values().stream() + .filter(r -> r.getType() == HsOfficeRelationType.PARTNER) + .forEach(r -> r.setAnchor(mandant)); + } + private static boolean containsRole(final Record rec, final String role) { final var roles = rec.getString("roles"); return ("," + roles + ",").contains("," + role + ","); @@ -1128,27 +1133,6 @@ public class ImportOfficeData extends ContextBasedTest { return contact; } - private String toFormattedString(final Map map) { - if ( map.isEmpty() ) { - return "{}"; - } - return "{\n" + - map.keySet().stream() - .map(id -> " " + id + "=" + map.get(id).toString()) - .map(e -> e.replaceAll("\n ", " ").replace("\n", "")) - .collect(Collectors.joining(",\n")) + - "\n}\n"; - } - - private String[] trimAll(final String[] record) { - for (int i = 0; i < record.length; ++i) { - if (record[i] != null) { - record[i] = record[i].trim(); - } - } - return record; - } - private Map toPhoneNumbers(final Record rec) { final var phoneNumbers = new LinkedHashMap(); if (isNotBlank(rec.getString("phone_private"))) @@ -1218,104 +1202,4 @@ public class ImportOfficeData extends ContextBasedTest { private String toName(final String salut, final String title, final String firstname, final String lastname) { return toCaption(salut, title, firstname, lastname, null); } - - private Reader resourceReader(@NotNull final String resourcePath) { - return new InputStreamReader(requireNonNull(getClass().getClassLoader().getResourceAsStream(resourcePath))); - } - - private static String[] justHeader(final List lines) { - return stream(lines.getFirst()).map(String::trim).toArray(String[]::new); - } - - private List withoutHeader(final List records) { - return records.subList(1, records.size()); - } - -} - -class Columns { - - private final List columnNames; - - public Columns(final String[] header) { - columnNames = List.of(header); - } - - int indexOf(final String columnName) { - int index = columnNames.indexOf(columnName); - if (index < 0) { - throw new RuntimeException("column name '" + columnName + "' not found in: " + columnNames); - } - return index; - } -} - -class Record { - - private final Columns columns; - private final String[] row; - - public Record(final Columns columns, final String[] row) { - this.columns = columns; - this.row = row; - } - - String getString(final String columnName) { - return row[columns.indexOf(columnName)]; - } - - boolean isEmpty(final String columnName) { - final String value = getString(columnName); - return value == null || value.isBlank(); - } - - boolean getBoolean(final String columnName) { - final String value = getString(columnName); - return isNotBlank(value) && - ( parseBoolean(value.trim()) || value.trim().startsWith("t")); - } - - Integer getInteger(final String columnName) { - final String value = getString(columnName); - return isNotBlank(value) ? Integer.parseInt(value.trim()) : 0; - } - - BigDecimal getBigDecimal(final String columnName) { - final String value = getString(columnName); - if (isNotBlank(value)) { - return new BigDecimal(value); - } - return null; - } - - LocalDate getLocalDate(final String columnName) { - final String dateString = getString(columnName); - if (isNotBlank(dateString)) { - return LocalDate.parse(dateString); - } - return null; - } -} - -class OrderedDependedTestsExtension implements TestWatcher, BeforeEachCallback { - - private static boolean previousTestsPassed = true; - - public void testFailed(ExtensionContext context, Throwable cause) { - previousTestsPassed = false; - } - - @Override - public void beforeEach(final ExtensionContext extensionContext) { - assumeThat(previousTestsPassed).isTrue(); - } -} - -class WriteOnceMap extends TreeMap { - - @Override - public V put(final K k, final V v) { - assertThat(containsKey(k)).describedAs("overwriting " + get(k) + " index " + k + " with " + v).isFalse(); - return super.put(k, v); - } } diff --git a/src/test/resources/migration/asset-transactions.csv b/src/test/resources/migration/asset-transactions.csv deleted file mode 100644 index 8c47e68e..00000000 --- a/src/test/resources/migration/asset-transactions.csv +++ /dev/null @@ -1,11 +0,0 @@ -member_asset_id; bp_id; date; action; amount; comment -30000; 17; 2000-12-06; PAYMENT; 1280.00; for subscription A -31000; 20; 2000-12-06; PAYMENT; 128.00; for subscription B -32000; 17; 2005-01-10; PAYMENT; 2560.00; for subscription C -33001; 17; 2005-01-10; HANDOVER; -512.00; for transfer to 10 -33002; 20; 2005-01-10; ADOPTION; 512.00; for transfer from 7 -34001; 20; 2016-12-31; CLEARING; -8.00; for cancellation D -34002; 20; 2016-12-31; PAYBACK; -100.00; for cancellation D -34003; 20; 2016-12-31; LOSS; -20.00; for cancellation D -35001; 90; 2024-01-15; PAYMENT; 128.00; for subscription E -35002; 90; 2024-01-20; ADJUSTMENT;-128.00; chargeback for subscription E diff --git a/src/test/resources/migration/business-partners.csv b/src/test/resources/migration/business-partners.csv deleted file mode 100644 index a28ead25..00000000 --- a/src/test/resources/migration/business-partners.csv +++ /dev/null @@ -1,6 +0,0 @@ -bp_id;member_id;member_code;member_since;member_until;member_role;author_contract;nondisc_contract;free;exempt_vat;indicator_vat;uid_vat -17;10017;hsh00-mih;2000-12-06;;Aufsichtsrat;2006-10-15;2001-10-15;false;false;NET;DE-VAT-007 -20;10020;hsh00-xyz;2000-12-06;2015-12-31;;;;false;false;GROSS; -22;11022;hsh00-xxx;2021-04-01;;;;;true;true;GROSS; -90;19090;hsh00-yyy;;;;;;true;true;GROSS; -99;19999;hsh00-zzz;;;;;;false;false;GROSS; diff --git a/src/test/resources/migration/contacts.csv b/src/test/resources/migration/contacts.csv deleted file mode 100644 index afcefdf9..00000000 --- a/src/test/resources/migration/contacts.csv +++ /dev/null @@ -1,20 +0,0 @@ -contact_id; bp_id; salut; first_name; last_name; title; firma; co; street; zipcode;city; country; phone_private; phone_office; phone_mobile; fax; email; roles - -# eine natürliche Person, implizites contractual -1101; 17; Herr; Michael; Mellies; ; ; ; Kleine Freiheit 50; 26524; Hage; DE; ; +49 4931 123456; +49 1522 123456;; mih@example.org; partner,contractual,billing,operation - -# eine juristische Person mit drei separaten Ansprechpartnern, vip-contact und ex-partner -1200; 20;; ; ; ; JM e.K.;; Wiesenweg 15; 12335; Berlin; DE; +49 30 6666666; +49 30 5555555; ; +49 30 6666666; jm-ex-partner@example.org; ex-partner -1201; 20; Frau; Jenny; Meyer-Billing; Dr.; JM GmbH;; Waldweg 5; 11001; Berlin; DE; +49 30 7777777; +49 30 1111111; ; +49 30 2222222; jm-billing@example.org; billing -1202; 20; Herr; Andrew; Meyer-Operation; ; JM GmbH;; Waldweg 5; 11001; Berlin; DE; +49 30 6666666; +49 30 3333333; ; +49 30 4444444; am-operation@example.org; operation,vip-contact,subscriber:operations-announce -1203; 20; Herr; Philip; Meyer-Contract; ; JM GmbH;; Waldweg 5; 11001; Berlin; DE; +49 30 6666666; +49 30 5555555; ; +49 30 6666666; pm-partner@example.org; partner,contractual,subscriber:members-announce,subscriber:customers-announce -1204; 20; Frau; Tammy; Meyer-VIP; ; JM GmbH;; Waldweg 5; 11001; Berlin; DE; +49 30 999999; +49 30 999999; ; +49 30 6666666; tm-vip@example.org; vip-contact - -# eine juristische Person mit nur einem Ansprechpartner und explizitem contractual -1301; 22; ; Petra; Schmidt; ; Test PS;; ; ; ; ; ; ; ; ; ps@example.com; partner,billing,contractual,operation - -# eine natürliche Person, die nur Subscriber ist -1401; 17; Frau; Frauke; Fanninga; ; ; ; Am Walde 1; 29456; Hitzacker; DE; ; ; ;; ff@example.org; subscriber:operations-announce - -# eine natürliche Person als Partner -1501; 90; Frau; Cecilia; Camus; ; ; ; Rue d'Avignion 60; 45000; Orléans; FR; ; ; ;; cc@example.org; partner,contractual,billing,operation diff --git a/src/test/resources/migration/dump.sh b/src/test/resources/migration/dump.sh index 29318b89..aa9cc0e3 100644 --- a/src/test/resources/migration/dump.sh +++ b/src/test/resources/migration/dump.sh @@ -20,58 +20,58 @@ dump() { dump "select bp_id, member_id, member_code, member_since, member_until, member_role, author_contract, nondisc_contract, free, exempt_vat, indicator_vat, uid_vat from business_partner order by bp_id" \ - "business-partners.csv" + "office/business_partners.csv" dump "select contact_id, bp_id, salut, first_name, last_name, title, firma, co, street, zipcode, city, country, phone_private, phone_office, phone_mobile, fax, email, array_to_string(array_agg(role), ',') as roles from contact left join contactrole_ref using(contact_id) group by contact_id order by contact_id" \ - "contacts.csv" + "office/contacts.csv" dump "select sepa_mandat_id, bp_id, bank_customer, bank_name, bank_iban, bank_bic, mandat_ref, mandat_signed, mandat_since, mandat_until, mandat_used from sepa_mandat order by sepa_mandat_id" \ - "sepa-mandates.csv" + "office/sepa_mandates.csv" dump "select member_asset_id, bp_id, date, action, amount, comment from member_asset WHERE bp_id NOT IN (511912) order by member_asset_id" \ - "asset-transactions.csv" + "office/asset_transactions.csv" dump "select member_share_id, bp_id, date, action, quantity, comment from member_share WHERE bp_id NOT IN (511912) order by member_share_id" \ - "share-transactions.csv" + "office/share_transactions.csv" dump "select inet_addr_id, inet_addr, description from inet_addr order by inet_addr_id" \ - "inet_addr.csv" + "hosting/inet_addr.csv" dump "select hive_id, hive_name, inet_addr_id, description from hive order by hive_id" \ - "hive.csv" + "hosting/hive.csv" dump "select packet_id, basepacket_code, packet_name, bp_id, hive_id, created, cancelled, cur_inet_addr_id, old_inet_addr_id, free from packet left join basepacket using (basepacket_id) order by packet_id" \ - "packet.csv" + "hosting/packet.csv" dump "select packet_component_id, packet_id, quantity, basecomponent_code, created, cancelled from packet_component left join basecomponent using (basecomponent_id) order by packet_component_id" \ - "packet_component.csv" + "hosting/packet_component.csv" dump "select unixuser_id, name, comment, shell, homedir, locked, packet_id, userid, quota_softlimit, quota_hardlimit, storage_softlimit, storage_hardlimit from unixuser order by unixuser_id" \ - "unixuser.csv" + "hosting/unixuser.csv" # weil das fehlt, muss group by komplett gesetzt werden: alter table domain add constraint PK_domain primary key (domain_id); dump "select domain_id, domain_name, domain_since, domain_dns_master, domain_owner, valid_subdomain_names, passenger_python, passenger_nodejs, passenger_ruby, fcgi_php_bin, array_to_string(array_agg(domain_option_name), ',') as domainoptions @@ -80,7 +80,7 @@ dump "select domain_id, domain_name, domain_since, domain_dns_master, domain_own left join domain_option using (domain_option_id) group by domain.domain_id, domain.domain_name, domain_since, domain_dns_master, domain_owner, valid_subdomain_names, passenger_python, passenger_nodejs, passenger_ruby, fcgi_php_bin order by domain.domain_id" \ - "domain.csv" + "hosting/domain.csv" dump "select emailaddr_id, domain_id, localpart, subdomain, target from emailaddr @@ -90,14 +90,14 @@ dump "select emailaddr_id, domain_id, localpart, subdomain, target dump "select emailalias_id, pac_id, name, target from emailalias order by emailalias_id" \ - "emailalias.csv" + "hosting/emailalias.csv" dump "select dbuser_id, engine, packet_id, name from database_user order by dbuser_id" \ - "database_user.csv" + "hosting/database_user.csv" dump "select database_id, engine, packet_id, name, owner, encoding from database order by database_id" \ - "database.csv" + "hosting/database.csv" diff --git a/src/test/resources/migration/hosting/hive.csv b/src/test/resources/migration/hosting/hive.csv new file mode 100644 index 00000000..fe23e0d3 --- /dev/null +++ b/src/test/resources/migration/hosting/hive.csv @@ -0,0 +1,26 @@ +hive_id;hive_name;inet_addr_id;description +1;h00;358; +2;h01;359; +4;h02;360; +7;h03;361; +13;h04;430; +14;h50;433; +20;h05;354; +21;h06;355; +22;h07;357; +28;h60;363; +31;h63;431; +37;h67;381; +38;h97;537; +39;h96;536; +45;h74;485; +50;h82;514; +128;h19;565; +148;h50;522; +163;h92;457; +173;h25;1759; +192;h93;1778; +193;h95;1779; +205;vm1107;1861; +208;vm1110;1864; +210;vm1112;1833; diff --git a/src/test/resources/migration/hosting/inet_addr.csv b/src/test/resources/migration/hosting/inet_addr.csv new file mode 100644 index 00000000..15bab1fb --- /dev/null +++ b/src/test/resources/migration/hosting/inet_addr.csv @@ -0,0 +1,10 @@ +inet_addr_id;inet_addr;description +363;83.223.95.34; +381;83.223.95.52; +402;83.223.95.73; +433;83.223.95.104; +457;83.223.95.128; +473;83.223.95.144; +574;83.223.95.245; +1168;83.223.79.72; +1790;83.223.94.179; diff --git a/src/test/resources/migration/hosting/packet.csv b/src/test/resources/migration/hosting/packet.csv new file mode 100644 index 00000000..92383a80 --- /dev/null +++ b/src/test/resources/migration/hosting/packet.csv @@ -0,0 +1,10 @@ +packet_id;basepacket_code;packet_name;bp_id;hive_id;created;cancelled;cur_inet_addr_id;old_inet_addr_id;free +630;PAC/WEB;hsh00;213;14;2001-06-01;;473;;1 +968;SRV/MGD;vm1061;132;28;2013-04-01;;363;;0 +978;SRV/MGD;vm1050;213;14;2013-04-01;;433;;1 +1061;SRV/MGD;vm1068;100;37;2013-08-19;;381;;f +1094;PAC/WEB;lug00;100;37;2013-09-10;;1168;;1 +1112;PAC/WEB;mim00;100;37;2013-09-17;;402;;1 +1447;SRV/MGD;vm1093;213;163;2014-11-28;;457;;t +19959;PAC/WEB;dph00;542;163;2021-06-02;;574;;0 +23611;SRV/CLD;vm2097;541;;2022-08-10;;1790;;0 diff --git a/src/test/resources/migration/hosting/packet_component.csv b/src/test/resources/migration/hosting/packet_component.csv new file mode 100644 index 00000000..74004918 --- /dev/null +++ b/src/test/resources/migration/hosting/packet_component.csv @@ -0,0 +1,143 @@ +packet_component_id;packet_id;quantity;basecomponent_code;created;cancelled +46105;1094;10;TRAFFIC;2017-03-27; +46109;1094;5;MULTI;2017-03-27; +46111;1094;0;DAEMON;2017-03-27; +46113;1094;1024;QUOTA;2017-03-27; +46117;1112;0;DAEMON;2017-03-27; +46121;1112;20;TRAFFIC;2017-03-27; +46122;1112;5;MULTI;2017-03-27; +46123;1112;3072;QUOTA;2017-03-27; +143133;1094;1;SLABASIC;2017-09-01; +143483;1112;1;SLABASIC;2017-09-01; +757383;1112;0;SLAEXT24H;; +770533;1094;0;SLAEXT24H;; +784283;1112;0;OFFICE;; +797433;1094;0;OFFICE;; +1228033;1112;0;STORAGE;; +1241433;1094;0;STORAGE;; +1266451;978;0;SLAPLAT4H;2021-10-05; +1266452;978;250;TRAFFIC;2021-10-05; +1266453;978;0;SLAPLAT8H;2021-10-05; +1266454;978;0;SLAMAIL4H;2021-10-05; +1266455;978;0;SLAMARIA8H;2021-10-05; +1266456;978;0;SLAPGSQL4H;2021-10-05; +1266457;978;0;SLAWEB4H;2021-10-05; +1266458;978;0;SLAMARIA4H;2021-10-05; +1266459;978;0;SLAPGSQL8H;2021-10-05; +1266460;978;0;SLAOFFIC8H;2021-10-05; +1266461;978;0;SLAWEB8H;2021-10-05; +1266462;978;256000;STORAGE;2021-10-05; +1266463;978;153600;QUOTA;2021-10-05; +1266464;978;0;SLAOFFIC4H;2021-10-05; +1266465;978;32768;RAM;2021-10-05; +1266466;978;4;CPU;2021-10-05; +1266467;978;1;SLABASIC;2021-10-05; +1266468;978;0;SLAMAIL8H;2021-10-05; +1275583;978;0;SLAPLAT2H;2022-04-20; +1280533;978;0;SLAWEB2H;2022-04-20; +1285483;978;0;SLAMARIA2H;2022-04-20; +1290433;978;0;SLAPGSQL2H;2022-04-20; +1295383;978;0;SLAMAIL2H;2022-04-20; +1300333;978;0;SLAOFFIC2H;2022-04-20; +1305933;1447;0;SLAWEB2H;2022-05-02; +1305934;1447;0;SLAPLAT4H;2022-05-02; +1305935;1447;0;SLAWEB8H;2022-05-02; +1305936;1447;0;SLAOFFIC4H;2022-05-02; +1305937;1447;0;SLAMARIA4H;2022-05-02; +1305938;1447;0;SLAOFFIC8H;2022-05-02; +1305939;1447;1;SLABASIC;2022-05-02; +1305940;1447;0;SLAMAIL8H;2022-05-02; +1305941;1447;0;SLAPGSQL4H;2022-05-02; +1305942;1447;6;CPU;2022-05-02; +1305943;1447;250;TRAFFIC;2022-05-02; +1305944;1447;0;SLAOFFIC2H;2022-05-02; +1305945;1447;0;SLAMAIL4H;2022-05-02; +1305946;1447;0;SLAPGSQL2H;2022-05-02; +1305947;1447;0;SLAMARIA2H;2022-05-02; +1305948;1447;0;SLAMARIA8H;2022-05-02; +1305949;1447;0;SLAWEB4H;2022-05-02; +1305950;1447;16384;RAM;2022-05-02; +1305951;1447;0;SLAPGSQL8H;2022-05-02; +1305952;1447;512000;STORAGE;2022-05-02; +1305953;1447;0;SLAMAIL2H;2022-05-02; +1305954;1447;0;SLAPLAT2H;2022-05-02; +1305955;1447;0;SLAPLAT8H;2022-05-02; +1305956;1447;307200;QUOTA;2022-05-02; +1312013;23611;1;SLABASIC;2022-08-10; +1312014;23611;0;BANDWIDTH;2022-08-10; +1312015;23611;12288;RAM;2022-08-10; +1312016;23611;25600;QUOTA;2022-08-10; +1312017;23611;0;SLAINFR8H;2022-08-10; +1312018;23611;0;STORAGE;2022-08-10; +1312019;23611;0;SLAINFR2H;2022-08-10; +1312020;23611;8;CPU;2022-08-10; +1312021;23611;250;TRAFFIC;2022-08-10; +1312022;23611;0;SLAINFR4H;2022-08-10; +1313883;978;0;BANDWIDTH;; +1316583;1447;0;BANDWIDTH;; +1338074;968;0;SLAMARIA2H;2023-09-05; +1338075;968;384000;QUOTA;2023-09-05; +1338076;968;1;SLAMAIL8H;2023-09-05; +1338077;968;0;BANDWIDTH;2023-09-05; +1338078;968;0;SLAWEB2H;2023-09-05; +1338079;968;0;SLAOFFIC4H;2023-09-05; +1338080;968;256000;STORAGE;2023-09-05; +1338081;968;0;SLAPLAT4H;2023-09-05; +1338082;968;0;SLAPGSQL2H;2023-09-05; +1338083;968;0;SLAPLAT2H;2023-09-05; +1338084;968;250;TRAFFIC;2023-09-05; +1338085;968;1;SLAMARIA8H;2023-09-05; +1338086;968;0;SLAPGSQL4H;2023-09-05; +1338087;968;0;SLAMAIL2H;2023-09-05; +1338088;968;1;SLAPLAT8H;2023-09-05; +1338089;968;0;SLAWEB4H;2023-09-05; +1338090;968;6;CPU;2023-09-05; +1338091;968;1;SLAPGSQL8H;2023-09-05; +1338092;968;0;SLAMARIA4H;2023-09-05; +1338093;968;0;SLAMAIL4H;2023-09-05; +1338094;968;14336;RAM;2023-09-05; +1338095;968;0;SLAOFFIC2H;2023-09-05; +1338096;968;0;SLAOFFIC8H;2023-09-05; +1338097;968;1;SLABASIC;2023-09-05; +1338098;968;1;SLAWEB8H;2023-09-05; +1339228;19959;20;TRAFFIC;2023-10-27; +1339229;19959;1;SLABASIC;2023-10-27; +1339230;19959;0;DAEMON;2023-10-27; +1339231;19959;25600;QUOTA;2023-10-27; +1339232;19959;0;STORAGE;2023-10-27; +1339233;19959;0;SLAEXT24H;2023-10-27; +1339234;19959;0;OFFICE;2023-10-27; +1339235;19959;1;MULTI;2023-10-27; +1341088;1061;0;SLAOFFIC2H;2023-12-14; +1341089;1061;0;SLAOFFIC8H;2023-12-14; +1341090;1061;256000;STORAGE;2023-12-14; +1341091;1061;0;SLAMAIL4H;2023-12-14; +1341092;1061;0;SLAMAIL2H;2023-12-14; +1341093;1061;0;SLAPLAT2H;2023-12-14; +1341094;1061;4096;RAM;2023-12-14; +1341095;1061;0;SLAPLAT4H;2023-12-14; +1341096;1061;1;SLAPGSQL8H;2023-12-14; +1341097;1061;2;CPU;2023-12-14; +1341098;1061;0;QUOTA;2023-12-14; +1341099;1061;0;SLAMAIL8H;2023-12-14; +1341100;1061;1;SLABASIC;2023-12-14; +1341101;1061;1;SLAMARIA8H;2023-12-14; +1341102;1061;0;SLAPGSQL4H;2023-12-14; +1341103;1061;0;SLAPGSQL2H;2023-12-14; +1341104;1061;0;SLAMARIA4H;2023-12-14; +1341105;1061;0;SLAOFFIC4H;2023-12-14; +1341106;1061;1;SLAPLAT8H;2023-12-14; +1341107;1061;0;BANDWIDTH;2023-12-14; +1341108;1061;1;SLAWEB8H;2023-12-14; +1341109;1061;0;SLAWEB2H;2023-12-14; +1341110;1061;0;SLAMARIA2H;2023-12-14; +1341111;1061;250;TRAFFIC;2023-12-14; +1341112;1061;0;SLAWEB4H;2023-12-14; +1346628;630;0;SLAEXT24H;2024-03-19; +1346629;630;0;OFFICE;2024-03-19; +1346630;630;16384;QUOTA;2024-03-19; +1346631;630;0;DAEMON;2024-03-19; +1346632;630;10240;STORAGE;2024-03-19; +1346633;630;1;SLABASIC;2024-03-19; +1346634;630;50;TRAFFIC;2024-03-19; +1346635;630;25;MULTI;2024-03-19; diff --git a/src/test/resources/migration/office/asset_transactions.csv b/src/test/resources/migration/office/asset_transactions.csv new file mode 100644 index 00000000..a0a83e02 --- /dev/null +++ b/src/test/resources/migration/office/asset_transactions.csv @@ -0,0 +1,19 @@ +member_asset_id; bp_id; date; action; amount; comment +358; 100; 2000-12-06; PAYMENT; 5120; for subscription A +442; 132; 2003-07-07; PAYMENT; 64; +577; 100; 2011-12-12; PAYMENT; 1024; +632; 132; 2013-10-21; PAYMENT; 64; +885; 100; 2020-12-15; PAYMENT; 6144; Einzahlung +924; 541; 2021-05-21; PAYMENT; 256; Beitritt - Lastschrift +925; 542; 2021-05-31; PAYMENT; 64; Beitritt - Lastschrift +1093; 100; 2023-10-05; PAYMENT; 3072; Kapitalerhoehung - Ueberweisung +1094; 100; 2023-10-06; PAYMENT; 3072; Kapitalerhoehung - Ueberweisung +31000; 120; 2000-12-06; PAYMENT; 128.00; for subscription B +32000; 100; 2005-01-10; PAYMENT; 2560.00; for subscription C +33001; 100; 2005-01-10; HANDOVER; -512.00; for transfer to 10 +33002; 120; 2005-01-10; ADOPTION; 512.00; for transfer from 7 +34001; 120; 2016-12-31; CLEARING; -8.00; for cancellation D +34002; 120; 2016-12-31; PAYBACK; -100.00; for cancellation D +34003; 120; 2016-12-31; LOSS; -20.00; for cancellation D +35001; 190; 2024-01-15; PAYMENT; 128.00; for subscription E +35002; 190; 2024-01-20; ADJUSTMENT;-128.00; chargeback for subscription E diff --git a/src/test/resources/migration/office/business_partners.csv b/src/test/resources/migration/office/business_partners.csv new file mode 100644 index 00000000..708f1d47 --- /dev/null +++ b/src/test/resources/migration/office/business_partners.csv @@ -0,0 +1,10 @@ +bp_id;member_id;member_code;member_since;member_until;member_role;author_contract;nondisc_contract;free;exempt_vat;indicator_vat;uid_vat +100;10003;hsh00-mim;2000-12-06;;Aufsichtsrat;;2001-04-24;0;0;GROSS;DE217249198 +132;10152;hsh00-rar;2003-07-12;;;;;0;0;GROSS;DE 236 109 136 +213;10000;hsh00-hsh;;;Hostsharing eG;;;1;0;GROSS; +541;11018;hsh00-wws;2021-05-17;;;;;0;0;GROSS; +542;11019;hsh00-dph;2021-05-25;;;;;0;0;GROSS; +120;10020;hsh00-xyz;2000-12-06;2015-12-31;;;;false;false;GROSS; +122;11022;hsh00-xxx;2021-04-01;;;;;true;true;GROSS; +190;19090;hsh00-yyy;;;;;;true;true;GROSS; +199;19999;hsh00-zzz;;;;;;false;false;GROSS; diff --git a/src/test/resources/migration/office/contacts.csv b/src/test/resources/migration/office/contacts.csv new file mode 100644 index 00000000..eb58efae --- /dev/null +++ b/src/test/resources/migration/office/contacts.csv @@ -0,0 +1,35 @@ +contact_id; bp_id; salut; first_name; last_name; title; firma; co; street; zipcode; city; country; phone_private; phone_office; phone_mobile; fax; email; roles + +# Hostsharing, the mandate itself +212; 213; Firma; Hostmaster; Hostsharing; ; Hostsharing e.G.; ; ; ; ; Germany; ; ; ; ; hostmaster@hostsharing.net; billing,operation,contractual,partner + +# some natural persons +100; 100; Herr; Michael; Mellis; ; Michael Mellis; ; Dr. Bolte Str. 50; 26524; Hage; Germany; ; +49 4931/1234567; +49/1522123455; +49 40 912345-9; michael@Mellis.example.org; billing,operation,contractual,partner,subscriber:members-announce,subscriber:operations-announce,subscriber:operations-discussion,subscriber:members-discussion,subscriber:generalversammlung +132; 132; Herr; Ragnar; Richter; ; Ragnar IT-Beratung; ; Vioktoriastraße 114; 70197; Stuttgart; Germany; +49 711 987654-1; +49 711 987654-2; ; +49 711 87654-3; hostsharing@ragnar-richter.de; billing,operation,partner,subscriber:operations-announce,subscriber:operations-discussion + +# eine juristische Person mit drei separaten Ansprechpartnern, vip-contact und ex-partner +1200; 120; ; ; ; ; JM e.K.; ; Wiesenweg 15; 12335; Berlin; DE; +49 30 6666666; +49 30 5555555; ; +49 30 6666666; jm-ex-partner@example.org; ex-partner +1201; 120; Frau; Jenny; Meyer-Billing; Dr.; JM GmbH; ; Waldweg 5; 11001; Berlin; DE; +49 30 7777777; +49 30 1111111; ; +49 30 2222222; jm-billing@example.org; billing +1202; 120; Herr; Andrew; Meyer-Operation; ; JM GmbH; ; Waldweg 5; 11001; Berlin; DE; +49 30 6666666; +49 30 3333333; ; +49 30 4444444; am-operation@example.org; operation,vip-contact,subscriber:operations-announce +1203; 120; Herr; Philip; Meyer-Contract; ; JM GmbH; ; Waldweg 5; 11001; Berlin; DE; +49 30 6666666; +49 30 5555555; ; +49 30 6666666; pm-partner@example.org; partner,contractual,subscriber:members-announce,subscriber:customers-announce +1204; 120; Frau; Tammy; Meyer-VIP; ; JM GmbH; ; Waldweg 5; 11001; Berlin; DE; +49 30 999999; +49 30 999999; ; +49 30 6666666; tm-vip@example.org; vip-contact + +# eine juristische Person mit nur einem Ansprechpartner und explizitem contractual +1301; 122; ; Petra; Schmidt; ; Test PS; ; ; ; ; ; ; ; ; ; ps@example.com; partner,billing,contractual,operation + +# eine natürliche Person, die nur Subscriber ist +1401; 120; Frau; Frauke; Fanninga; ; ; ; Am Walde 1; 29456; Hitzacker; DE; ; ; ; ; ff@example.org; subscriber:operations-announce + +# eine natürliche Person als Partner +1501; 190; Frau; Cecilia; Camus; ; ; ; Rue d'Avignion 60; 45000; Orléans; FR; ; ; ; ; cc@example.org; partner,contractual,billing,operation + +# some more contacts of realistic business partners + +90436; 541; Frau; Christiane; Milberg; ; Wasserwerk Südholstein; ; Am Wasserwerk 1-3; 25491; Hetlingen; Germany; ; ; +49 4103 12345-1; ; rechnung@ww-sholst.example.org; billing,partner,subscriber:members-discussion,contractual,subscriber:members-announce,subscriber:generalversammlung +90437; 542; Herr; Richard; Wiese; ; Das Perfekte Haus; ; Kennedyplatz 11; 45279; Essen; Germany; ; ; +49-172-12345; ; admin@das-perfekte-haus.example.org; operation,partner,subscriber:members-discussion,contractual,subscriber:operations-announce,subscriber:operations-discussion,subscriber:members-announce,subscriber:generalversammlung +90438; 541; Herr; Karim; Metzger; ; Wasswerwerk Südholstein; ; Am Wasserwerk 1-3; 25491; Hetlingen; Germany; ; +49 4103 12345-2; ; ; karim.metzger@ww-sholst.example.org; operation,subscriber:operations-announce,subscriber:operations-discussion +90590; 542; Herr; Inhaber R.; Wiese; ; Das Perfekte Haus; Client-ID 515217; Essen, Kastanienallee 81; 30127; Hannover; Germany; ; ; ; ; 515217@kkemail.example.org; billing +90629; 132; ; Ragnar; Richter; ; ; ; ; ; ; ; ; ; ; ; mail@ragnar-richter..example.org; contractual,subscriber:members-announce,subscriber:members-discussion,subscriber:generalversammlung +90677; 132; ; Eike; Henning; ; ; ; ; ; ; ; ; ; ; ; hostsharing@eike-henning..example.org; operation,subscriber:operations-announce,subscriber:operations-discussion +90698; 132; ; Jan; Henning; ; ; ; ; ; ; ; ; 01577 12345678; ; ; mail@jan-henning.example.org; operation + diff --git a/src/test/resources/migration/office/sepa_mandates.csv b/src/test/resources/migration/office/sepa_mandates.csv new file mode 100644 index 00000000..c2b8d936 --- /dev/null +++ b/src/test/resources/migration/office/sepa_mandates.csv @@ -0,0 +1,8 @@ +sepa_mandat_id;bp_id;bank_customer;bank_name;bank_iban;bank_bic;mandat_ref;mandat_signed;mandat_since;mandat_until;mandat_used +30;132;Ragnar Richter;GLS Gemeinschaftsbank eG;DE02300209000106531065;GENODEM1GLS;HS-10152-20140801;2013-12-01;2013-12-01;2016-02-15;2014-01-20 +132;100;Michael Mellis;Hamburger Volksbank;DE37500105177419788228;GENODEF1HH2;HS-10003-20140801;2013-12-01;2013-12-01;;2022-12-31 +386;541;Wasserwerk Suedholstein;Sparkasse Westholstein;DE49500105174516484892;NOLADE21WHO;HS-11018-20210512;2021-05-12;2021-05-17;;2022-12-31 +387;542;Richard Wiese Das Perfekte Haus;Commerzbank Wuppertal;DE89370400440532013000;COBADEFFXXX;HS-11019-20210519;2021-05-19;2021-05-25;;2022-12-31 +234234;100;Michael Mellis;ING Bank AG;DE37500105177419788228;INGDDEFFXXX;MH12345;2004-06-12;2004-06-15;;2022-10-20 +235600;120;JM e.K.;Targobank AG;DE02300209000106531065;CMCIDEDD;JM33344;2004-01-15;2004-01-20;2005-06-27 ;2016-01-18 +235662;120;JM GmbH;ING Bank AG;DE49500105174516484892;INGDDEFFXXX;JM33344;2005-06-28;2005-07-01;;2016-01-18 diff --git a/src/test/resources/migration/office/share_transactions.csv b/src/test/resources/migration/office/share_transactions.csv new file mode 100644 index 00000000..d42f9597 --- /dev/null +++ b/src/test/resources/migration/office/share_transactions.csv @@ -0,0 +1,12 @@ +member_share_id;bp_id;date;action;quantity;comment +3;100;2000-12-06;SUBSCRIPTION;80;initial share subscription +90;132;2003-07-12;SUBSCRIPTION;1; +241;100;2011-12-05;SUBSCRIPTION;16; +279;132;2013-10-21;SUBSCRIPTION;1; +523;100;2020-12-08;SUBSCRIPTION;96;Kapitalerhoehung +562;541;2021-05-17;SUBSCRIPTION;4;Beitritt +563;542;2021-05-25;SUBSCRIPTION;1;Beitritt +721;100;2023-10-10;SUBSCRIPTION;96;Kapitalerhoehung +33451;120;2000-12-06;SUBSCRIPTION;2;initial share subscription +33701;100;2005-01-10;SUBSCRIPTION;40;increase +33810;120;2016-12-31;UNSUBSCRIPTION;22;membership ended diff --git a/src/test/resources/migration/sepa-mandates.csv b/src/test/resources/migration/sepa-mandates.csv deleted file mode 100644 index a76adc16..00000000 --- a/src/test/resources/migration/sepa-mandates.csv +++ /dev/null @@ -1,4 +0,0 @@ -sepa_mandat_id; bp_id; bank_customer; bank_name; bank_iban; bank_bic; mandat_ref; mandat_signed; mandat_since; mandat_until; mandat_used -234234; 17; Michael Mellies; ING Bank AG; DE37500105177419788228; INGDDEFFXXX; MH12345; 2004-06-12; 2004-06-15; ; 2022-10-20 -235600; 20; JM e.K.; Targobank AG; DE02300209000106531065; CMCIDEDD; JM33344; 2004-01-15; 2004-01-20;2005-06-27 ;2016-01-18 -235662; 20; JM GmbH; ING Bank AG; DE49500105174516484892; INGDDEFFXXX; JM33344; 2005-06-28; 2005-07-01; ; 2016-01-18 diff --git a/src/test/resources/migration/share-transactions.csv b/src/test/resources/migration/share-transactions.csv deleted file mode 100644 index fa561419..00000000 --- a/src/test/resources/migration/share-transactions.csv +++ /dev/null @@ -1,5 +0,0 @@ -member_share_id;bp_id; date; action; quantity; comment -33443; 17; 2000-12-06; SUBSCRIPTION; 20; initial share subscription -33451; 20; 2000-12-06; SUBSCRIPTION; 2; initial share subscription -33701; 17; 2005-01-10; SUBSCRIPTION; 40; increase -33810; 20; 2016-12-31; UNSUBSCRIPTION; 22; membership ended