add DomainSetup-HostingAssets for new BookingItem via created-event (#111)
Co-authored-by: Michael Hoennig <michael@hoennig.de> Reviewed-on: https://dev.hostsharing.net/hostsharing/hs.hsadmin.ng/pulls/111 Reviewed-by: Timotheus Pokorra <timotheus.pokorra@hostsharing.net>
This commit is contained in:
@@ -17,7 +17,7 @@ public class JsonObjectMapperConfiguration {
|
||||
public Jackson2ObjectMapperBuilder customObjectMapper() {
|
||||
return new Jackson2ObjectMapperBuilder()
|
||||
.modules(new JsonNullableModule(), new JavaTimeModule())
|
||||
.featuresToEnable(JsonParser.Feature.ALLOW_COMMENTS)
|
||||
.featuresToEnable(JsonParser.Feature.ALLOW_COMMENTS, JsonParser.Feature.ALLOW_COMMENTS)
|
||||
.featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ public final class HashGenerator {
|
||||
"abcdefghijklmnopqrstuvwxyz" +
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZ" +
|
||||
"0123456789/.";
|
||||
private static boolean couldBeHashEnabled; // TODO.impl: remove after legacy data is migrated
|
||||
private static boolean couldBeHashEnabled; // TODO.legacy: remove after legacy data is migrated
|
||||
|
||||
public enum Algorithm {
|
||||
LINUX_SHA512(LinuxEtcShadowHashGenerator::hash, "6"),
|
||||
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package net.hostsharing.hsadminng.hs.booking.item;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
|
||||
@Getter
|
||||
public class BookingItemCreatedAppEvent extends ApplicationEvent {
|
||||
|
||||
private BookingItemCreatedEventEntity entity;
|
||||
|
||||
public BookingItemCreatedAppEvent(
|
||||
@NotNull final Object source,
|
||||
@NotNull final HsBookingItemRealEntity newBookingItem,
|
||||
final String assetJson) {
|
||||
super(source);
|
||||
this.entity = new BookingItemCreatedEventEntity(newBookingItem, assetJson);
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package net.hostsharing.hsadminng.hs.booking.item;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
|
||||
@Getter
|
||||
public class BookingItemCreatedEvent extends ApplicationEvent {
|
||||
private final @NotNull HsBookingItem newBookingItem;
|
||||
|
||||
public BookingItemCreatedEvent(@NotNull HsBookingItemController source, @NotNull final HsBookingItem newBookingItem) {
|
||||
super(source);
|
||||
this.newBookingItem = newBookingItem;
|
||||
}
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
package net.hostsharing.hsadminng.hs.booking.item;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import lombok.experimental.SuperBuilder;
|
||||
import net.hostsharing.hsadminng.persistence.BaseEntity;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.MapsId;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.persistence.Version;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import java.util.UUID;
|
||||
|
||||
|
||||
|
||||
@Entity
|
||||
@Table(schema = "hs_booking", name = "item_created_event")
|
||||
@SuperBuilder(toBuilder = true)
|
||||
@Getter
|
||||
@ToString
|
||||
@NoArgsConstructor
|
||||
public class BookingItemCreatedEventEntity implements BaseEntity {
|
||||
@Id
|
||||
@Column(name="bookingitemuuid")
|
||||
private UUID uuid;
|
||||
|
||||
@MapsId
|
||||
@ManyToOne(optional = false)
|
||||
@JoinColumn(name = "bookingitemuuid", nullable = false)
|
||||
private HsBookingItemRealEntity bookingItem;
|
||||
|
||||
@Version
|
||||
private int version;
|
||||
|
||||
@Column(name = "assetjson")
|
||||
private String assetJson;
|
||||
|
||||
@Setter
|
||||
@Column(name = "statusmessage")
|
||||
private String statusMessage;
|
||||
|
||||
public BookingItemCreatedEventEntity(
|
||||
@NotNull final HsBookingItemRealEntity newBookingItem,
|
||||
final String assetJson) {
|
||||
this.bookingItem = newBookingItem;
|
||||
this.assetJson = assetJson;
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
package net.hostsharing.hsadminng.hs.booking.item;
|
||||
|
||||
import org.springframework.data.repository.Repository;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public interface BookingItemCreatedEventRepository extends Repository<BookingItemCreatedEventEntity, UUID> {
|
||||
|
||||
BookingItemCreatedEventEntity save(HsBookingItemRealEntity current);
|
||||
|
||||
BookingItemCreatedEventEntity findByBookingItem(HsBookingItemRealEntity newBookingItem);
|
||||
}
|
||||
@@ -14,7 +14,7 @@ import net.hostsharing.hsadminng.hs.booking.project.HsBookingProject;
|
||||
import net.hostsharing.hsadminng.hs.booking.project.HsBookingProjectRealEntity;
|
||||
import net.hostsharing.hsadminng.hs.validation.PropertiesProvider;
|
||||
import net.hostsharing.hsadminng.mapper.PatchableMapWrapper;
|
||||
import net.hostsharing.hsadminng.rbac.object.BaseEntity;
|
||||
import net.hostsharing.hsadminng.persistence.BaseEntity;
|
||||
import net.hostsharing.hsadminng.stringify.Stringify;
|
||||
import net.hostsharing.hsadminng.stringify.Stringifyable;
|
||||
import org.hibernate.annotations.Type;
|
||||
|
||||
+20
-2
@@ -1,5 +1,7 @@
|
||||
package net.hostsharing.hsadminng.hs.booking.item;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import net.hostsharing.hsadminng.context.Context;
|
||||
import net.hostsharing.hsadminng.hs.booking.generated.api.v1.api.HsBookingItemsApi;
|
||||
import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsBookingItemInsertResource;
|
||||
@@ -23,6 +25,7 @@ import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import static java.util.Optional.ofNullable;
|
||||
import static net.hostsharing.hsadminng.mapper.PostgresDateRange.toPostgresDateRange;
|
||||
|
||||
@RestController
|
||||
@@ -40,6 +43,9 @@ public class HsBookingItemController implements HsBookingItemsApi {
|
||||
@Autowired
|
||||
private HsBookingItemRbacRepository bookingItemRepo;
|
||||
|
||||
@Autowired
|
||||
private ObjectMapper jsonMapper;
|
||||
|
||||
@Autowired
|
||||
private EntityManagerWrapper em;
|
||||
|
||||
@@ -76,8 +82,7 @@ public class HsBookingItemController implements HsBookingItemsApi {
|
||||
.validateContext()
|
||||
.mapUsing(e -> mapper.map(e, HsBookingItemResource.class, ITEM_TO_RESOURCE_POSTMAPPER))
|
||||
.revampProperties();
|
||||
|
||||
applicationEventPublisher.publishEvent(new BookingItemCreatedEvent(this, saveProcessor.getEntity()));
|
||||
publishSavedEvent(saveProcessor, body);
|
||||
|
||||
final var uri =
|
||||
MvcUriComponentsBuilder.fromController(getClass())
|
||||
@@ -137,6 +142,16 @@ public class HsBookingItemController implements HsBookingItemsApi {
|
||||
return ResponseEntity.ok(mapped);
|
||||
}
|
||||
|
||||
private void publishSavedEvent(final BookingItemEntitySaveProcessor saveProcessor, final HsBookingItemInsertResource body) {
|
||||
try {
|
||||
final var bookingItemRealEntity = em.getReference(HsBookingItemRealEntity.class, saveProcessor.getEntity().getUuid());
|
||||
applicationEventPublisher.publishEvent(new BookingItemCreatedAppEvent(
|
||||
this, bookingItemRealEntity, jsonMapper.writeValueAsString(body.getHostingAsset())));
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
final BiConsumer<HsBookingItem, HsBookingItemResource> ITEM_TO_RESOURCE_POSTMAPPER = (entity, resource) -> {
|
||||
resource.setValidFrom(entity.getValidity().lower());
|
||||
if (entity.getValidity().hasUpperBound()) {
|
||||
@@ -148,6 +163,9 @@ public class HsBookingItemController implements HsBookingItemsApi {
|
||||
|
||||
final BiConsumer<HsBookingItemInsertResource, HsBookingItemRbacEntity> RESOURCE_TO_ENTITY_POSTMAPPER = (resource, entity) -> {
|
||||
entity.setProject(em.find(HsBookingProjectRealEntity.class, resource.getProjectUuid()));
|
||||
ofNullable(resource.getParentItemUuid())
|
||||
.map(parentItemUuid -> em.find(HsBookingItemRealEntity.class, parentItemUuid))
|
||||
.ifPresent(entity::setParentItem);
|
||||
entity.setValidity(toPostgresDateRange(LocalDate.now(), resource.getValidTo()));
|
||||
entity.putResources(KeyValueMap.from(resource.getResources()));
|
||||
};
|
||||
|
||||
+1
-1
@@ -48,7 +48,7 @@ public class BookingItemEntitySaveProcessor {
|
||||
return this;
|
||||
}
|
||||
|
||||
// TODO.impl: remove once the migration of legacy data is done
|
||||
// TODO.legacy: remove once the migration of legacy data is done
|
||||
/// validates the entity itself including its properties, but ignoring some error messages for import of legacy data
|
||||
public BookingItemEntitySaveProcessor validateEntityIgnoring(final String... ignoreRegExp) {
|
||||
step("validateEntity", "prepareForSave");
|
||||
|
||||
+2
-8
@@ -13,27 +13,21 @@ import static net.hostsharing.hsadminng.hs.hosting.asset.validators.Dns.REGISTRA
|
||||
import static net.hostsharing.hsadminng.hs.validation.StringProperty.stringProperty;
|
||||
|
||||
class HsDomainSetupBookingItemValidator extends HsBookingItemEntityValidator {
|
||||
public static final String DOMAIN_NAME_PROPERTY_NAME = "domainName";
|
||||
|
||||
public static final String FQDN_REGEX = "^((?!-)[A-Za-z0-9-]{1,63}(?<!-)\\.)+[A-Za-z]{2,12}";
|
||||
public static final String DOMAIN_NAME_PROPERTY_NAME = "domainName";
|
||||
public static final String TARGET_UNIX_USER_PROPERTY_NAME = "targetUnixUser";
|
||||
public static final String WEBSPACE_NAME_REGEX = "[a-z][a-z0-9]{2}[0-9]{2}";
|
||||
public static final String TARGET_UNIX_USER_NAME_REGEX = "^"+WEBSPACE_NAME_REGEX+"$|^"+WEBSPACE_NAME_REGEX+"-[a-z0-9\\._-]+$";
|
||||
public static final String VERIFICATION_CODE_PROPERTY_NAME = "verificationCode";
|
||||
|
||||
HsDomainSetupBookingItemValidator() {
|
||||
super(
|
||||
// TODO.spec: feels wrong
|
||||
stringProperty(DOMAIN_NAME_PROPERTY_NAME).writeOnce()
|
||||
.maxLength(253)
|
||||
.matchesRegEx(FQDN_REGEX).describedAs("is not a (non-top-level) fully qualified domain name")
|
||||
.notMatchesRegEx(REGISTRAR_LEVEL_DOMAINS).describedAs("is a forbidden registrar-level domain name")
|
||||
.required(),
|
||||
// TODO.legacy: remove the following property once we give up legacy compatibility
|
||||
stringProperty(TARGET_UNIX_USER_PROPERTY_NAME).writeOnce()
|
||||
.maxLength(253)
|
||||
.matchesRegEx(TARGET_UNIX_USER_NAME_REGEX).describedAs("is not a valid unix-user name")
|
||||
.writeOnce()
|
||||
.required(),
|
||||
stringProperty(VERIFICATION_CODE_PROPERTY_NAME)
|
||||
.minLength(12)
|
||||
.maxLength(64)
|
||||
|
||||
@@ -7,7 +7,7 @@ import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRbacEntity;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacView;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacView.SQL;
|
||||
import net.hostsharing.hsadminng.rbac.object.BaseEntity;
|
||||
import net.hostsharing.hsadminng.persistence.BaseEntity;
|
||||
import net.hostsharing.hsadminng.stringify.Stringify;
|
||||
import net.hostsharing.hsadminng.stringify.Stringifyable;
|
||||
|
||||
|
||||
-56
@@ -1,56 +0,0 @@
|
||||
package net.hostsharing.hsadminng.hs.hosting.asset;
|
||||
|
||||
import net.hostsharing.hsadminng.hs.booking.item.BookingItemCreatedEvent;
|
||||
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemRealEntity;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.validators.HostingAssetEntitySaveProcessor;
|
||||
import net.hostsharing.hsadminng.persistence.EntityManagerWrapper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Component
|
||||
public class HsBookingItemCreatedListener implements ApplicationListener<BookingItemCreatedEvent> {
|
||||
|
||||
@Autowired
|
||||
private EntityManagerWrapper emw;
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(final BookingItemCreatedEvent event) {
|
||||
System.out.println("Received newly created booking item: " + event.getNewBookingItem());
|
||||
final var newBookingItemRealEntity =
|
||||
emw.getReference(HsBookingItemRealEntity.class, event.getNewBookingItem().getUuid());
|
||||
final var newHostingAsset = switch (newBookingItemRealEntity.getType()) {
|
||||
case PRIVATE_CLOUD -> null;
|
||||
case CLOUD_SERVER -> null;
|
||||
case MANAGED_SERVER -> null;
|
||||
case MANAGED_WEBSPACE -> null;
|
||||
case DOMAIN_SETUP -> createDomainSetupHostingAsset(newBookingItemRealEntity);
|
||||
};
|
||||
if (newHostingAsset != null) {
|
||||
try {
|
||||
new HostingAssetEntitySaveProcessor(emw, newHostingAsset)
|
||||
.preprocessEntity()
|
||||
.validateEntity()
|
||||
.prepareForSave()
|
||||
.save()
|
||||
.validateContext();
|
||||
} catch (final Exception e) {
|
||||
// TODO.impl: store status in a separate field, maybe enum+message
|
||||
newBookingItemRealEntity.getResources().put("status", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private HsHostingAsset createDomainSetupHostingAsset(final HsBookingItemRealEntity fromBookingItem) {
|
||||
return HsHostingAssetRbacEntity.builder()
|
||||
.bookingItem(fromBookingItem)
|
||||
.type(HsHostingAssetType.DOMAIN_SETUP)
|
||||
.identifier(fromBookingItem.getDirectValue("domainName", String.class))
|
||||
.subHostingAssets(List.of(
|
||||
// TARGET_UNIX_USER_PROPERTY_NAME
|
||||
))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ import net.hostsharing.hsadminng.hs.booking.project.HsBookingProject;
|
||||
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRealEntity;
|
||||
import net.hostsharing.hsadminng.hs.validation.PropertiesProvider;
|
||||
import net.hostsharing.hsadminng.mapper.PatchableMapWrapper;
|
||||
import net.hostsharing.hsadminng.rbac.object.BaseEntity;
|
||||
import net.hostsharing.hsadminng.persistence.BaseEntity;
|
||||
import net.hostsharing.hsadminng.stringify.Stringify;
|
||||
import net.hostsharing.hsadminng.stringify.Stringifyable;
|
||||
import org.hibernate.annotations.Type;
|
||||
@@ -89,10 +89,9 @@ public abstract class HsHostingAsset implements Stringifyable, BaseEntity<HsHost
|
||||
@JoinColumn(name = "alarmcontactuuid")
|
||||
private HsOfficeContactRealEntity alarmContact;
|
||||
|
||||
@Builder.Default
|
||||
@OneToMany(cascade = CascadeType.REFRESH, orphanRemoval = true, fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "parentassetuuid", referencedColumnName = "uuid")
|
||||
private List<HsHostingAssetRealEntity> subHostingAssets = new ArrayList<>();
|
||||
private List<HsHostingAssetRealEntity> subHostingAssets;
|
||||
|
||||
@Column(name = "identifier")
|
||||
private String identifier; // e.g. vm1234, xyz00, example.org, xyz00_abc
|
||||
@@ -125,6 +124,13 @@ public abstract class HsHostingAsset implements Stringifyable, BaseEntity<HsHost
|
||||
PatchableMapWrapper.of(configWrapper, (newWrapper) -> {configWrapper = newWrapper;}, config).assign(newConfig);
|
||||
}
|
||||
|
||||
public List<HsHostingAssetRealEntity> getSubHostingAssets() {
|
||||
if (subHostingAssets == null) {
|
||||
subHostingAssets = new ArrayList<>();
|
||||
}
|
||||
return subHostingAssets;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PatchableMapWrapper<Object> directProps() {
|
||||
return getConfig();
|
||||
|
||||
+13
@@ -3,6 +3,7 @@ package net.hostsharing.hsadminng.hs.hosting.asset;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.Repository;
|
||||
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
@@ -13,6 +14,18 @@ public interface HsHostingAssetRealRepository extends HsHostingAssetRepository<H
|
||||
|
||||
List<HsHostingAssetRealEntity> findByIdentifier(String assetIdentifier);
|
||||
|
||||
default List<HsHostingAssetRealEntity> findByTypeAndIdentifier(@NotNull HsHostingAssetType type, @NotNull String identifier) {
|
||||
return findByTypeAndIdentifierImpl(type.name(), identifier);
|
||||
}
|
||||
|
||||
@Query("""
|
||||
select ha
|
||||
from HsHostingAssetRealEntity ha
|
||||
where cast(ha.type as String) = :type
|
||||
and ha.identifier = :identifier
|
||||
""")
|
||||
List<HsHostingAssetRealEntity> findByTypeAndIdentifierImpl(@NotNull String type, @NotNull String identifier);
|
||||
|
||||
@Query(value = """
|
||||
select ha.uuid,
|
||||
ha.alarmcontactuuid,
|
||||
|
||||
+153
@@ -0,0 +1,153 @@
|
||||
package net.hostsharing.hsadminng.hs.hosting.asset.factories;
|
||||
|
||||
import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsHostingAssetAutoInsertResource;
|
||||
import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsHostingAssetSubInsertResource;
|
||||
import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsHostingAssetTypeResource;
|
||||
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemRealEntity;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAsset;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetRealEntity;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType;
|
||||
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRealEntity;
|
||||
import net.hostsharing.hsadminng.lambda.Reducer;
|
||||
import net.hostsharing.hsadminng.mapper.StandardMapper;
|
||||
import net.hostsharing.hsadminng.persistence.EntityManagerWrapper;
|
||||
|
||||
import jakarta.validation.ValidationException;
|
||||
import java.net.IDN;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.DOMAIN_DNS_SETUP;
|
||||
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.DOMAIN_HTTP_SETUP;
|
||||
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.DOMAIN_MBOX_SETUP;
|
||||
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.DOMAIN_SMTP_SETUP;
|
||||
|
||||
public class DomainSetupHostingAssetFactory extends HostingAssetFactory {
|
||||
|
||||
public DomainSetupHostingAssetFactory(
|
||||
final EntityManagerWrapper emw,
|
||||
final HsBookingItemRealEntity newBookingItemRealEntity,
|
||||
final HsHostingAssetAutoInsertResource asset,
|
||||
final StandardMapper standardMapper) {
|
||||
super(emw, newBookingItemRealEntity, asset, standardMapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HsHostingAsset create() {
|
||||
final var domainSetupAsset = createDomainSetupAsset(getDomainName());
|
||||
final var subHostingAssets = domainSetupAsset.getSubHostingAssets();
|
||||
|
||||
// TODO.legacy: as long as we need to be compatible, we always do all technical domain-setups
|
||||
|
||||
final var domainHttpSetupAssetResource = findSubHostingAssetResource(HsHostingAssetTypeResource.DOMAIN_HTTP_SETUP);
|
||||
final var assignedToUnixUserAssetEntity = domainHttpSetupAssetResource
|
||||
.map(HsHostingAssetSubInsertResource::getAssignedToAssetUuid)
|
||||
.map(uuid -> emw.find(HsHostingAssetRealEntity.class, uuid))
|
||||
.orElseThrow(() -> new ValidationException("DOMAIN_HTTP_SETUP subAsset with assignedToAssetUuid required in compatibility mode"));
|
||||
|
||||
subHostingAssets.add(
|
||||
createDomainSubSetupAssetEntity(
|
||||
domainSetupAsset,
|
||||
DOMAIN_HTTP_SETUP,
|
||||
builder -> builder
|
||||
.assignedToAsset(assignedToUnixUserAssetEntity)
|
||||
.identifier(getDomainName() + "|HTTP")
|
||||
.caption("HTTP-Setup für " + IDN.toUnicode(getDomainName())))
|
||||
);
|
||||
|
||||
// Do not add to subHostingAssets in compatibility mode, in this case, DNS setup works via file system.
|
||||
// The entity is created just for validation purposes.
|
||||
createDomainSubSetupAssetEntity(
|
||||
domainSetupAsset,
|
||||
DOMAIN_DNS_SETUP,
|
||||
builder -> builder
|
||||
.assignedToAsset(assignedToUnixUserAssetEntity.getParentAsset())
|
||||
.identifier(getDomainName() + "|DNS")
|
||||
.caption("DNS-Setup für " + IDN.toUnicode(getDomainName())));
|
||||
|
||||
subHostingAssets.add(
|
||||
createDomainSubSetupAssetEntity(
|
||||
domainSetupAsset,
|
||||
DOMAIN_MBOX_SETUP,
|
||||
builder -> builder
|
||||
.assignedToAsset(assignedToUnixUserAssetEntity.getParentAsset())
|
||||
.identifier(getDomainName() + "|MBOX")
|
||||
.caption("MBOX-Setup für " + IDN.toUnicode(getDomainName())))
|
||||
);
|
||||
|
||||
subHostingAssets.add(
|
||||
createDomainSubSetupAssetEntity(
|
||||
domainSetupAsset,
|
||||
DOMAIN_SMTP_SETUP,
|
||||
builder -> builder
|
||||
.assignedToAsset(assignedToUnixUserAssetEntity.getParentAsset())
|
||||
.identifier(getDomainName() + "|SMTP")
|
||||
.caption("SMTP-Setup für " + IDN.toUnicode(getDomainName())))
|
||||
);
|
||||
|
||||
return domainSetupAsset;
|
||||
}
|
||||
|
||||
private HsHostingAssetRealEntity createDomainSetupAsset(final String domainName) {
|
||||
return HsHostingAssetRealEntity.builder()
|
||||
.bookingItem(fromBookingItem)
|
||||
.type(HsHostingAssetType.DOMAIN_SETUP)
|
||||
.identifier(domainName)
|
||||
.caption(asset.getCaption() != null ? asset.getCaption() : domainName)
|
||||
.alarmContact(ref(HsOfficeContactRealEntity.class, asset.getAlarmContactUuid()))
|
||||
// the sub-hosting-assets get added later
|
||||
.build();
|
||||
}
|
||||
|
||||
private HsHostingAssetRealEntity createDomainSubSetupAssetEntity(
|
||||
final HsHostingAssetRealEntity domainSetupAsset,
|
||||
final HsHostingAssetType subAssetType,
|
||||
final Function<HsHostingAssetRealEntity.HsHostingAssetRealEntityBuilder<?, ?>, HsHostingAssetRealEntity.HsHostingAssetRealEntityBuilder<?, ?>> builderTransformer) {
|
||||
final var resourceType = HsHostingAssetTypeResource.valueOf(subAssetType.name());
|
||||
|
||||
final var subAssetResourceOptional = findSubHostingAssetResource(resourceType);
|
||||
|
||||
subAssetResourceOptional.ifPresentOrElse(
|
||||
subAssetResource -> verifyNotOverspecified(subAssetResource),
|
||||
() -> { throw new ValidationException("sub-asset of type " + resourceType.name() + " required in legacy mode, but missing"); }
|
||||
);
|
||||
|
||||
return builderTransformer.apply(
|
||||
HsHostingAssetRealEntity.builder()
|
||||
.type(subAssetType)
|
||||
.parentAsset(domainSetupAsset))
|
||||
.build();
|
||||
}
|
||||
|
||||
private Optional<HsHostingAssetSubInsertResource> findSubHostingAssetResource(final HsHostingAssetTypeResource resourceType) {
|
||||
return getSubHostingAssetResources().stream()
|
||||
.filter(ha -> ha.getType() == resourceType)
|
||||
.reduce(Reducer::toSingleElement);
|
||||
}
|
||||
|
||||
// TODO.legacy: while we need to stay compatible, only default values can be used, thus only the type can be specified
|
||||
private void verifyNotOverspecified(final HsHostingAssetSubInsertResource givenSubAssetResource) {
|
||||
final var convert = new ToStringConverter().ignoring("assignedToAssetUuid");
|
||||
final var expectedSubAssetResource = new HsHostingAssetSubInsertResource();
|
||||
expectedSubAssetResource.setType(givenSubAssetResource.getType());
|
||||
if ( !convert.from(givenSubAssetResource).equals(convert.from(expectedSubAssetResource)) ) {
|
||||
throw new ValidationException("sub asset " + givenSubAssetResource.getType() + " is over-specified, in compatibility mode, only default values allowed");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private String getDomainName() {
|
||||
return asset.getIdentifier();
|
||||
}
|
||||
|
||||
private List<HsHostingAssetSubInsertResource> getSubHostingAssetResources() {
|
||||
return asset.getSubHostingAssets();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void persist(final HsHostingAsset newHostingAsset) {
|
||||
super.persist(newHostingAsset);
|
||||
newHostingAsset.getSubHostingAssets().forEach(super::persist);
|
||||
}
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
package net.hostsharing.hsadminng.hs.hosting.asset.factories;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsHostingAssetAutoInsertResource;
|
||||
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemRealEntity;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAsset;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.validators.HostingAssetEntitySaveProcessor;
|
||||
import net.hostsharing.hsadminng.mapper.StandardMapper;
|
||||
import net.hostsharing.hsadminng.persistence.EntityManagerWrapper;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
abstract class HostingAssetFactory {
|
||||
|
||||
final EntityManagerWrapper emw;
|
||||
final HsBookingItemRealEntity fromBookingItem;
|
||||
final HsHostingAssetAutoInsertResource asset;
|
||||
final StandardMapper standardMapper;
|
||||
|
||||
protected abstract HsHostingAsset create();
|
||||
|
||||
public String performSaveProcess() {
|
||||
try {
|
||||
final var newHostingAsset = create();
|
||||
persist(newHostingAsset);
|
||||
return null;
|
||||
} catch (final Exception e) {
|
||||
return e.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
protected void persist(final HsHostingAsset newHostingAsset) {
|
||||
new HostingAssetEntitySaveProcessor(emw, newHostingAsset)
|
||||
.preprocessEntity()
|
||||
.validateEntity()
|
||||
.prepareForSave()
|
||||
.save()
|
||||
.validateContext();
|
||||
}
|
||||
|
||||
protected <T> T ref(final Class<T> entityClass, final UUID uuid) {
|
||||
return uuid != null ? emw.getReference(entityClass, uuid) : null;
|
||||
}
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
package net.hostsharing.hsadminng.hs.hosting.asset.factories;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.SneakyThrows;
|
||||
import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsHostingAssetAutoInsertResource;
|
||||
import net.hostsharing.hsadminng.hs.booking.item.BookingItemCreatedAppEvent;
|
||||
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemRealEntity;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAsset;
|
||||
import net.hostsharing.hsadminng.mapper.StandardMapper;
|
||||
import net.hostsharing.hsadminng.persistence.EntityManagerWrapper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
|
||||
@Component
|
||||
public class HsBookingItemCreatedListener implements ApplicationListener<BookingItemCreatedAppEvent> {
|
||||
|
||||
@Autowired
|
||||
private EntityManagerWrapper emw;
|
||||
|
||||
@Autowired
|
||||
private ObjectMapper jsonMapper;
|
||||
|
||||
@Autowired
|
||||
private StandardMapper standardMapper;
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void onApplicationEvent(final BookingItemCreatedAppEvent bookingItemCreatedAppEvent) {
|
||||
if (containsAssetJson(bookingItemCreatedAppEvent)) {
|
||||
createRelatedHostingAsset(bookingItemCreatedAppEvent);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean containsAssetJson(final BookingItemCreatedAppEvent bookingItemCreatedAppEvent) {
|
||||
return bookingItemCreatedAppEvent.getEntity().getAssetJson() != null;
|
||||
}
|
||||
|
||||
private void createRelatedHostingAsset(final BookingItemCreatedAppEvent event) throws JsonProcessingException {
|
||||
final var newBookingItemRealEntity = event.getEntity().getBookingItem();
|
||||
final var asset = jsonMapper.readValue(event.getEntity().getAssetJson(), HsHostingAssetAutoInsertResource.class);
|
||||
final var factory = switch (newBookingItemRealEntity.getType()) {
|
||||
case PRIVATE_CLOUD, CLOUD_SERVER, MANAGED_SERVER ->
|
||||
forNowNoAutomaticHostingAssetCreationPossible(emw, newBookingItemRealEntity, asset, standardMapper);
|
||||
case MANAGED_WEBSPACE -> new ManagedWebspaceHostingAssetFactory(emw, newBookingItemRealEntity, asset, standardMapper);
|
||||
case DOMAIN_SETUP -> new DomainSetupHostingAssetFactory(emw, newBookingItemRealEntity, asset, standardMapper);
|
||||
};
|
||||
if (factory != null) {
|
||||
final var statusMessage = factory.performSaveProcess();
|
||||
// TODO.impl: once we implement retry, we need to amend this code (persist/merge/delete)
|
||||
if (statusMessage != null) {
|
||||
event.getEntity().setStatusMessage(statusMessage);
|
||||
emw.persist(event.getEntity());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private HostingAssetFactory forNowNoAutomaticHostingAssetCreationPossible(
|
||||
final EntityManagerWrapper emw,
|
||||
final HsBookingItemRealEntity fromBookingItem,
|
||||
final HsHostingAssetAutoInsertResource asset,
|
||||
final StandardMapper standardMapper
|
||||
) {
|
||||
return new HostingAssetFactory(emw, fromBookingItem, asset, standardMapper) {
|
||||
|
||||
@Override
|
||||
protected HsHostingAsset create() {
|
||||
// TODO.impl: we should validate the asset JSON, but some violations are un-avoidable at that stage
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String performSaveProcess() {
|
||||
return "waiting for manual setup of hosting asset for booking item of type " + fromBookingItem.getType();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
package net.hostsharing.hsadminng.hs.hosting.asset.factories;
|
||||
|
||||
import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsHostingAssetAutoInsertResource;
|
||||
import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsHostingAssetTypeResource;
|
||||
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemRealEntity;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAsset;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetRealEntity;
|
||||
import net.hostsharing.hsadminng.mapper.StandardMapper;
|
||||
import net.hostsharing.hsadminng.persistence.EntityManagerWrapper;
|
||||
|
||||
import jakarta.validation.ValidationException;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
|
||||
public class ManagedWebspaceHostingAssetFactory extends HostingAssetFactory {
|
||||
|
||||
public ManagedWebspaceHostingAssetFactory(
|
||||
final EntityManagerWrapper emw,
|
||||
final HsBookingItemRealEntity newBookingItemRealEntity,
|
||||
final HsHostingAssetAutoInsertResource asset,
|
||||
final StandardMapper standardMapper) {
|
||||
super(emw, newBookingItemRealEntity, asset, standardMapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HsHostingAsset create() {
|
||||
if (asset.getType() != HsHostingAssetTypeResource.MANAGED_WEBSPACE) {
|
||||
throw new ValidationException("requires MANAGED_WEBSPACE hosting asset, but got " +
|
||||
Optional.of(asset)
|
||||
.map(HsHostingAssetAutoInsertResource::getType)
|
||||
.map(Enum::name)
|
||||
.orElse(null));
|
||||
}
|
||||
final var managedWebspaceHostingAsset = standardMapper.map(asset, HsHostingAssetRealEntity.class);
|
||||
managedWebspaceHostingAsset.setBookingItem(fromBookingItem);
|
||||
emw.createQuery(
|
||||
"SELECT asset FROM HsHostingAssetRealEntity asset WHERE asset.bookingItem.uuid=:bookingItemUuid",
|
||||
HsHostingAssetRealEntity.class)
|
||||
.setParameter("bookingItemUuid", fromBookingItem.getParentItem().getUuid())
|
||||
.getResultStream().findFirst()
|
||||
.ifPresent(managedWebspaceHostingAsset::setParentAsset);
|
||||
|
||||
return managedWebspaceHostingAsset;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void persist(final HsHostingAsset newManagedWebspaceHostingAsset) {
|
||||
super.persist(newManagedWebspaceHostingAsset);
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
package net.hostsharing.hsadminng.hs.hosting.asset.factories;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import static java.util.stream.Collectors.joining;
|
||||
|
||||
public class ToStringConverter {
|
||||
|
||||
final public Set<String> ignoredFields = new HashSet<>();
|
||||
|
||||
public ToStringConverter ignoring(final String fieldName) {
|
||||
ignoredFields.add(fieldName);
|
||||
return this;
|
||||
}
|
||||
|
||||
public String from(Object obj) {
|
||||
StringBuilder result = new StringBuilder();
|
||||
return "{ " +
|
||||
Arrays.stream(obj.getClass().getDeclaredFields())
|
||||
.filter(f -> !ignoredFields.contains(f.getName()))
|
||||
.map(field -> {
|
||||
try {
|
||||
field.setAccessible(true);
|
||||
return field.getName() + ": " + field.get(obj);
|
||||
} catch (IllegalAccessException e) {
|
||||
// ignore inaccessible fields
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.filter(Objects::nonNull)
|
||||
.collect(joining(", "))
|
||||
+ " }";
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -42,7 +42,7 @@ public class HostingAssetEntitySaveProcessor {
|
||||
return this;
|
||||
}
|
||||
|
||||
// TODO.impl: remove once the migration of legacy data is done
|
||||
// TODO.legacy: remove once the migration of legacy data is done
|
||||
/// validates the entity itself including its properties, but ignoring some error messages for import of legacy data
|
||||
public HostingAssetEntitySaveProcessor validateEntityIgnoring(final String... ignoreRegExp) {
|
||||
step("validateEntity", "prepareForSave");
|
||||
|
||||
+2
-2
@@ -15,7 +15,7 @@ import static net.hostsharing.hsadminng.hs.validation.BooleanProperty.booleanPro
|
||||
import static net.hostsharing.hsadminng.hs.validation.IntegerProperty.integerProperty;
|
||||
import static net.hostsharing.hsadminng.hs.validation.StringProperty.stringProperty;
|
||||
|
||||
// TODO.impl: make package private once we've migrated the legacy data
|
||||
// TODO.legacy: make package private once we've migrated the legacy data
|
||||
public class HsDomainDnsSetupHostingAssetValidator extends HostingAssetEntityValidator {
|
||||
|
||||
// according to RFC 1035 (section 5) and RFC 1034
|
||||
@@ -33,7 +33,7 @@ public class HsDomainDnsSetupHostingAssetValidator extends HostingAssetEntityVal
|
||||
RR_REGEX_NAME + RR_REGEX_IN + RR_REGEX_TTL + RR_RECORD_TYPE + RR_RECORD_DATA + RR_COMMENT;
|
||||
public static final String IDENTIFIER_SUFFIX = "|DNS";
|
||||
|
||||
private static List<String> zoneFileErrors = null; // TODO.impl: remove once legacy data is migrated
|
||||
private static List<String> zoneFileErrors = null; // TODO.legacy: remove once legacy data is migrated
|
||||
|
||||
HsDomainDnsSetupHostingAssetValidator() {
|
||||
super(
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ package net.hostsharing.hsadminng.hs.office.bankaccount;
|
||||
import lombok.*;
|
||||
import lombok.experimental.FieldNameConstants;
|
||||
import net.hostsharing.hsadminng.errors.DisplayAs;
|
||||
import net.hostsharing.hsadminng.rbac.object.BaseEntity;
|
||||
import net.hostsharing.hsadminng.persistence.BaseEntity;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacView;
|
||||
import net.hostsharing.hsadminng.stringify.Stringify;
|
||||
import net.hostsharing.hsadminng.stringify.Stringifyable;
|
||||
|
||||
@@ -11,7 +11,7 @@ import lombok.experimental.FieldNameConstants;
|
||||
import lombok.experimental.SuperBuilder;
|
||||
import net.hostsharing.hsadminng.errors.DisplayAs;
|
||||
import net.hostsharing.hsadminng.mapper.PatchableMapWrapper;
|
||||
import net.hostsharing.hsadminng.rbac.object.BaseEntity;
|
||||
import net.hostsharing.hsadminng.persistence.BaseEntity;
|
||||
import net.hostsharing.hsadminng.stringify.Stringify;
|
||||
import net.hostsharing.hsadminng.stringify.Stringifyable;
|
||||
import org.hibernate.annotations.GenericGenerator;
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@ import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import net.hostsharing.hsadminng.errors.DisplayAs;
|
||||
import net.hostsharing.hsadminng.hs.office.membership.HsOfficeMembershipEntity;
|
||||
import net.hostsharing.hsadminng.rbac.object.BaseEntity;
|
||||
import net.hostsharing.hsadminng.persistence.BaseEntity;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacView;
|
||||
import net.hostsharing.hsadminng.stringify.Stringify;
|
||||
import net.hostsharing.hsadminng.stringify.Stringifyable;
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@ import lombok.Setter;
|
||||
import net.hostsharing.hsadminng.errors.DisplayAs;
|
||||
import net.hostsharing.hsadminng.hs.office.membership.HsOfficeMembershipEntity;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacView;
|
||||
import net.hostsharing.hsadminng.rbac.object.BaseEntity;
|
||||
import net.hostsharing.hsadminng.persistence.BaseEntity;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacView.SQL;
|
||||
import net.hostsharing.hsadminng.stringify.Stringify;
|
||||
import net.hostsharing.hsadminng.stringify.Stringifyable;
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelation;
|
||||
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRbacEntity;
|
||||
import net.hostsharing.hsadminng.rbac.object.BaseEntity;
|
||||
import net.hostsharing.hsadminng.persistence.BaseEntity;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacView;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacView.SQL;
|
||||
import net.hostsharing.hsadminng.stringify.Stringify;
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import net.hostsharing.hsadminng.errors.DisplayAs;
|
||||
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRbacEntity;
|
||||
import net.hostsharing.hsadminng.rbac.object.BaseEntity;
|
||||
import net.hostsharing.hsadminng.persistence.BaseEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacView;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacView.SQL;
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@ import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealRepository;
|
||||
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationType;
|
||||
import net.hostsharing.hsadminng.mapper.StandardMapper;
|
||||
import net.hostsharing.hsadminng.rbac.object.BaseEntity;
|
||||
import net.hostsharing.hsadminng.persistence.BaseEntity;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ package net.hostsharing.hsadminng.hs.office.partner;
|
||||
|
||||
import lombok.*;
|
||||
import net.hostsharing.hsadminng.errors.DisplayAs;
|
||||
import net.hostsharing.hsadminng.rbac.object.BaseEntity;
|
||||
import net.hostsharing.hsadminng.persistence.BaseEntity;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacView;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacView.SQL;
|
||||
import net.hostsharing.hsadminng.stringify.Stringify;
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContact;
|
||||
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRbacEntity;
|
||||
import net.hostsharing.hsadminng.rbac.object.BaseEntity;
|
||||
import net.hostsharing.hsadminng.persistence.BaseEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelation;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacView;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacView.SQL;
|
||||
|
||||
@@ -3,7 +3,7 @@ package net.hostsharing.hsadminng.hs.office.person;
|
||||
import lombok.*;
|
||||
import lombok.experimental.FieldNameConstants;
|
||||
import net.hostsharing.hsadminng.errors.DisplayAs;
|
||||
import net.hostsharing.hsadminng.rbac.object.BaseEntity;
|
||||
import net.hostsharing.hsadminng.persistence.BaseEntity;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacView;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacView.SQL;
|
||||
import net.hostsharing.hsadminng.stringify.Stringify;
|
||||
|
||||
@@ -5,7 +5,7 @@ import lombok.experimental.FieldNameConstants;
|
||||
import lombok.experimental.SuperBuilder;
|
||||
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRealEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity;
|
||||
import net.hostsharing.hsadminng.rbac.object.BaseEntity;
|
||||
import net.hostsharing.hsadminng.persistence.BaseEntity;
|
||||
import net.hostsharing.hsadminng.stringify.Stringify;
|
||||
import net.hostsharing.hsadminng.stringify.Stringifyable;
|
||||
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@ import net.hostsharing.hsadminng.errors.DisplayAs;
|
||||
import net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRbacEntity;
|
||||
import net.hostsharing.hsadminng.rbac.object.BaseEntity;
|
||||
import net.hostsharing.hsadminng.persistence.BaseEntity;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacView;
|
||||
import net.hostsharing.hsadminng.stringify.Stringify;
|
||||
import net.hostsharing.hsadminng.stringify.Stringifyable;
|
||||
|
||||
@@ -31,7 +31,7 @@ public class PasswordProperty extends StringProperty<PasswordProperty> {
|
||||
|
||||
@Override
|
||||
protected void validate(final List<String> result, final String propValue, final PropertiesProvider propProvider) {
|
||||
// TODO.impl: remove after legacy data is migrated
|
||||
// TODO.legacy: remove after legacy data is migrated
|
||||
if (HashGenerator.using(hashedUsing).couldBeHash(propValue) && propValue.length() > this.maxLength()) {
|
||||
// already hashed => do not validate
|
||||
return;
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
package net.hostsharing.hsadminng.lambda;
|
||||
|
||||
public class Reducer {
|
||||
public static <T> T toSingleElement(T last, T next) {
|
||||
throw new AssertionError("only a single entity expected");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
package net.hostsharing.hsadminng.mapper;
|
||||
|
||||
import net.hostsharing.hsadminng.persistence.EntityManagerWrapper;
|
||||
import org.modelmapper.ModelMapper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import jakarta.persistence.EntityManager;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.PersistenceContext;
|
||||
import jakarta.validation.ValidationException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.List;
|
||||
@@ -21,10 +21,10 @@ import static net.hostsharing.hsadminng.errors.DisplayAs.DisplayName;
|
||||
*/
|
||||
abstract class Mapper extends ModelMapper {
|
||||
|
||||
@PersistenceContext
|
||||
EntityManager em;
|
||||
EntityManagerWrapper em;
|
||||
|
||||
Mapper() {
|
||||
Mapper(@Autowired final EntityManagerWrapper em) {
|
||||
this.em = em;
|
||||
getConfiguration().setAmbiguityIgnored(true);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package net.hostsharing.hsadminng.mapper;
|
||||
|
||||
import net.hostsharing.hsadminng.persistence.EntityManagerWrapper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
@@ -8,7 +10,8 @@ import org.springframework.stereotype.Component;
|
||||
@Component
|
||||
public class StandardMapper extends Mapper {
|
||||
|
||||
public StandardMapper() {
|
||||
public StandardMapper(@Autowired final EntityManagerWrapper em) {
|
||||
super(em);
|
||||
getConfiguration().setAmbiguityIgnored(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package net.hostsharing.hsadminng.mapper;
|
||||
|
||||
import net.hostsharing.hsadminng.persistence.EntityManagerWrapper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import static org.modelmapper.convention.MatchingStrategies.STRICT;
|
||||
@@ -13,7 +15,8 @@ import static org.modelmapper.convention.MatchingStrategies.STRICT;
|
||||
@Component
|
||||
public class StrictMapper extends Mapper {
|
||||
|
||||
public StrictMapper() {
|
||||
public StrictMapper(@Autowired final EntityManagerWrapper em) {
|
||||
super(em);
|
||||
getConfiguration().setMatchingStrategy(STRICT);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-2
@@ -1,11 +1,10 @@
|
||||
package net.hostsharing.hsadminng.rbac.object;
|
||||
package net.hostsharing.hsadminng.persistence;
|
||||
|
||||
|
||||
import org.hibernate.Hibernate;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
// TODO.impl: this class does not really belong into this package, but there is no right place yet
|
||||
public interface BaseEntity<T extends BaseEntity<?>> {
|
||||
UUID getUuid();
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package net.hostsharing.hsadminng.persistence;
|
||||
|
||||
import net.hostsharing.hsadminng.errors.DisplayAs.DisplayName;
|
||||
import net.hostsharing.hsadminng.rbac.object.BaseEntity;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ package net.hostsharing.hsadminng.rbac.generator;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import net.hostsharing.hsadminng.rbac.object.BaseEntity;
|
||||
import net.hostsharing.hsadminng.persistence.BaseEntity;
|
||||
import org.reflections.Reflections;
|
||||
import org.reflections.scanners.TypeAnnotationsScanner;
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import net.hostsharing.hsadminng.rbac.object.BaseEntity;
|
||||
import net.hostsharing.hsadminng.persistence.BaseEntity;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacView;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacView.SQL;
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import net.hostsharing.hsadminng.rbac.object.BaseEntity;
|
||||
import net.hostsharing.hsadminng.persistence.BaseEntity;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacView;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacView.SQL;
|
||||
import net.hostsharing.hsadminng.rbac.test.pac.TestPackageEntity;
|
||||
|
||||
@@ -4,7 +4,7 @@ import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import net.hostsharing.hsadminng.rbac.object.BaseEntity;
|
||||
import net.hostsharing.hsadminng.persistence.BaseEntity;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacView;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacView.SQL;
|
||||
import net.hostsharing.hsadminng.rbac.test.cust.TestCustomerEntity;
|
||||
|
||||
@@ -56,6 +56,10 @@ components:
|
||||
type: string
|
||||
format: uuid
|
||||
nullable: false
|
||||
parentItemUuid:
|
||||
type: string
|
||||
format: uuid
|
||||
nullable: false
|
||||
type:
|
||||
$ref: '#/components/schemas/HsBookingItemType'
|
||||
caption:
|
||||
@@ -69,6 +73,8 @@ components:
|
||||
nullable: true
|
||||
resources:
|
||||
$ref: '#/components/schemas/BookingResources'
|
||||
hostingAsset:
|
||||
$ref: '../hs-hosting/hs-hosting-asset-schemas.yaml#/components/schemas/HsHostingAssetAutoInsert'
|
||||
required:
|
||||
- caption
|
||||
- projectUuid
|
||||
|
||||
@@ -94,7 +94,68 @@ components:
|
||||
- type
|
||||
- identifier
|
||||
- caption
|
||||
- config
|
||||
additionalProperties: false
|
||||
|
||||
HsHostingAssetAutoInsert:
|
||||
type: object
|
||||
properties:
|
||||
parentAssetUuid:
|
||||
type: string
|
||||
format: uuid
|
||||
nullable: true
|
||||
assignedToAssetUuid:
|
||||
type: string
|
||||
format: uuid
|
||||
type:
|
||||
$ref: '#/components/schemas/HsHostingAssetType'
|
||||
identifier:
|
||||
type: string
|
||||
minLength: 3
|
||||
maxLength: 80
|
||||
nullable: false
|
||||
caption:
|
||||
type: string
|
||||
minLength: 3
|
||||
maxLength: 80
|
||||
nullable: false
|
||||
alarmContactUuid:
|
||||
type: string
|
||||
format: uuid
|
||||
nullable: true
|
||||
config:
|
||||
$ref: '#/components/schemas/HsHostingAssetConfiguration'
|
||||
subHostingAssets:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/HsHostingAssetSubInsert'
|
||||
required:
|
||||
- identifier
|
||||
additionalProperties: false
|
||||
|
||||
HsHostingAssetSubInsert:
|
||||
type: object
|
||||
properties:
|
||||
type:
|
||||
$ref: '#/components/schemas/HsHostingAssetType'
|
||||
identifier:
|
||||
type: string
|
||||
minLength: 3
|
||||
maxLength: 80
|
||||
nullable: false
|
||||
caption:
|
||||
type: string
|
||||
minLength: 3
|
||||
maxLength: 80
|
||||
nullable: false
|
||||
assignedToAssetUuid:
|
||||
type: string
|
||||
format: uuid
|
||||
alarmContactUuid:
|
||||
type: string
|
||||
format: uuid
|
||||
nullable: true
|
||||
config:
|
||||
$ref: '#/components/schemas/HsHostingAssetConfiguration'
|
||||
additionalProperties: false
|
||||
|
||||
HsHostingAssetConfiguration:
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
--liquibase formatted sql
|
||||
|
||||
-- TODO: These changesets are just for the external remote views to simulate the legacy tables.
|
||||
-- TODO.legacy: These changesets are just for the external remote views to simulate the legacy tables.
|
||||
-- Once we don't need the external remote views anymore, create revert changesets.
|
||||
|
||||
-- ============================================================================
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
--liquibase formatted sql
|
||||
|
||||
-- TODO: These changesets are just for the external remote views to simulate the legacy tables.
|
||||
-- TODO.legacy: These changesets are just for the external remote views to simulate the legacy tables.
|
||||
-- Once we don't need the external remote views anymore, create revert changesets.
|
||||
|
||||
-- ============================================================================
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
--liquibase formatted sql
|
||||
|
||||
-- TODO: These changesets are just for the external remote views to simulate the legacy tables.
|
||||
-- TODO.legacy: These changesets are just for the external remote views to simulate the legacy tables.
|
||||
-- Once we don't need the external remote views anymore, create revert changesets.
|
||||
|
||||
-- ============================================================================
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
--liquibase formatted sql
|
||||
|
||||
-- TODO: These changesets are just for the external remote views to simulate the legacy tables.
|
||||
-- TODO.legacy: These changesets are just for the external remote views to simulate the legacy tables.
|
||||
-- Once we don't need the external remote views anymore, create revert changesets.
|
||||
|
||||
-- ============================================================================
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
--liquibase formatted sql
|
||||
|
||||
-- TODO: These changesets are just for the external remote views to simulate the legacy tables.
|
||||
-- TODO.legacy: These changesets are just for the external remote views to simulate the legacy tables.
|
||||
-- Once we don't need the external remote views anymore, create revert changesets.
|
||||
|
||||
-- ============================================================================
|
||||
|
||||
+14
@@ -31,6 +31,20 @@ create table if not exists hs_booking.item
|
||||
--//
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
--changeset michael.hoennig:hs-booking-item-EVENT-TABLE endDelimiter:--//
|
||||
-- ----------------------------------------------------------------------------
|
||||
|
||||
create table if not exists hs_booking.item_created_event
|
||||
(
|
||||
bookingItemUuid uuid unique references hs_booking.item (uuid),
|
||||
version int not null default 0,
|
||||
assetJson text,
|
||||
statusMessage text
|
||||
);
|
||||
--//
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
--changeset michael.hoennig:hs-booking-item-MAIN-TABLE-JOURNAL endDelimiter:--//
|
||||
-- ----------------------------------------------------------------------------
|
||||
|
||||
+1
-1
@@ -41,7 +41,7 @@ create table if not exists hs_hosting.asset
|
||||
config jsonb not null,
|
||||
alarmContactUuid uuid null references hs_office.contact(uuid) initially deferred,
|
||||
|
||||
unique (type, identifier), -- at least as long as we need to be compatible to the legacy system
|
||||
unique (type, identifier), -- TODO.legacy: at least as long as we need to be compatible to the legacy system
|
||||
|
||||
constraint hosting_asset_has_booking_item_or_parent_asset
|
||||
check (bookingItemUuid is not null or parentAssetUuid is not null or type in ('DOMAIN_SETUP', 'IPV4_NUMBER', 'IPV6_NUMBER'))
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
--liquibase formatted sql
|
||||
|
||||
-- TODO: These changesets are just for the external remote views to simulate the legacy tables.
|
||||
-- TODO.legacy: These changesets are just for the external remote views to simulate the legacy tables.
|
||||
-- Once we don't need the external remote views anymore, create revert changesets.
|
||||
|
||||
-- ============================================================================
|
||||
|
||||
@@ -43,6 +43,7 @@ public class ArchitectureTest {
|
||||
"..test.dom",
|
||||
"..context",
|
||||
"..hash",
|
||||
"..lambda",
|
||||
"..generated..",
|
||||
"..persistence..",
|
||||
"..system..",
|
||||
@@ -64,6 +65,7 @@ public class ArchitectureTest {
|
||||
"..hs.booking.item.validators",
|
||||
"..hs.hosting.asset",
|
||||
"..hs.hosting.asset.validators",
|
||||
"..hs.hosting.asset.factories",
|
||||
"..errors",
|
||||
"..mapper",
|
||||
"..ping",
|
||||
|
||||
+171
-45
@@ -5,12 +5,15 @@ import io.restassured.RestAssured;
|
||||
import io.restassured.http.ContentType;
|
||||
import net.hostsharing.hsadminng.HsadminNgApplication;
|
||||
import net.hostsharing.hsadminng.hs.booking.debitor.HsBookingDebitorRepository;
|
||||
import net.hostsharing.hsadminng.hs.booking.project.HsBookingProjectRealEntity;
|
||||
import net.hostsharing.hsadminng.hs.booking.project.HsBookingProjectRealRepository;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAsset;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetRealRepository;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.validators.Dns;
|
||||
import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup;
|
||||
import net.hostsharing.hsadminng.rbac.test.JpaAttempt;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.ClassOrderer;
|
||||
import org.junit.jupiter.api.MethodOrderer;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
@@ -31,6 +34,8 @@ import java.util.UUID;
|
||||
import static java.util.Map.entry;
|
||||
import static java.util.Optional.ofNullable;
|
||||
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.MANAGED_WEBSPACE;
|
||||
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_SERVER;
|
||||
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.UNIX_USER;
|
||||
import static net.hostsharing.hsadminng.rbac.test.JsonMatcher.lenientlyEquals;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.hamcrest.Matchers.matchesRegex;
|
||||
@@ -58,6 +63,9 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
|
||||
@Autowired
|
||||
HsHostingAssetRealRepository realHostingAssetRepo;
|
||||
|
||||
@Autowired
|
||||
BookingItemCreatedEventRepository bookingItemCreationEventRepo;
|
||||
|
||||
@Autowired
|
||||
JpaAttempt jpaAttempt;
|
||||
|
||||
@@ -70,11 +78,7 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
|
||||
|
||||
// given
|
||||
context("superuser-alex@hostsharing.net");
|
||||
final var givenProject = debitorRepo.findByDebitorNumber(1000111).stream()
|
||||
.map(d -> realProjectRepo.findAllByDebitorUuid(d.getUuid()))
|
||||
.flatMap(List::stream)
|
||||
.findFirst()
|
||||
.orElseThrow();
|
||||
final var givenProject = findDefaultProjectOfDebitorNumber(1000111);
|
||||
|
||||
RestAssured // @formatter:off
|
||||
.given()
|
||||
@@ -138,11 +142,7 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
|
||||
void globalAdmin_canAddBookingItem() {
|
||||
|
||||
context.define("superuser-alex@hostsharing.net");
|
||||
final var givenProject = debitorRepo.findByDebitorNumber(1000111).stream()
|
||||
.map(d -> realProjectRepo.findAllByDebitorUuid(d.getUuid()))
|
||||
.flatMap(List::stream)
|
||||
.findFirst()
|
||||
.orElseThrow();
|
||||
final var givenProject = findDefaultProjectOfDebitorNumber(1000111);
|
||||
|
||||
final var location = RestAssured // @formatter:off
|
||||
.given()
|
||||
@@ -186,37 +186,121 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
|
||||
}
|
||||
|
||||
@Test
|
||||
void projectAgent_canAddBookingItemWithHostingAsset() {
|
||||
void projectAgent_canAddManagedWebspaceBookingItemWithHostingAsset() {
|
||||
|
||||
context.define("superuser-alex@hostsharing.net", "hs_booking.project#D-1000111-D-1000111defaultproject:AGENT");
|
||||
final var givenProject = debitorRepo.findByDebitorNumber(1000111).stream()
|
||||
.map(d -> realProjectRepo.findAllByDebitorUuid(d.getUuid()))
|
||||
.flatMap(List::stream)
|
||||
.findFirst()
|
||||
.orElseThrow();
|
||||
|
||||
Dns.fakeResultForDomain("example.org",
|
||||
Dns.Result.fromRecords("Hostsharing-domain-setup-verification-code=just-a-fake-verification-code"));
|
||||
final var givenProject = findDefaultProjectOfDebitorNumber(1000111);
|
||||
final var givenManagedServer = realHostingAssetRepo.findByTypeAndIdentifier(MANAGED_SERVER, "vm1011").stream()
|
||||
.map(HsHostingAsset::getBookingItem)
|
||||
.findFirst().orElseThrow();
|
||||
|
||||
final var location = RestAssured // @formatter:off
|
||||
.given()
|
||||
.header("current-subject", "superuser-alex@hostsharing.net")
|
||||
.contentType(ContentType.JSON)
|
||||
.body("""
|
||||
{
|
||||
"projectUuid": "{projectUuid}",
|
||||
"type": "DOMAIN_SETUP",
|
||||
"caption": "some new domain-setup booking",
|
||||
"resources": {
|
||||
"domainName": "example.org",
|
||||
"targetUnixUser": "fir01-web",
|
||||
"verificationCode": "just-a-fake-verification-code"
|
||||
{
|
||||
"projectUuid": "{projectUuid}",
|
||||
"parentItemUuid": "{managedServerUuid}",
|
||||
"type": "MANAGED_WEBSPACE",
|
||||
"caption": "some managed webspace",
|
||||
"resources": {
|
||||
"SSD": 25,
|
||||
"Traffic": 250
|
||||
},
|
||||
"hostingAsset": {
|
||||
"type": "MANAGED_WEBSPACE",
|
||||
"identifier": "fir00"
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
"""
|
||||
.replace("{projectUuid}", givenProject.getUuid().toString())
|
||||
.replace("{managedServerUuid}", givenManagedServer.getUuid().toString())
|
||||
)
|
||||
.port(port)
|
||||
.when()
|
||||
.post("http://localhost/api/hs/booking/items")
|
||||
.then().log().all().assertThat()
|
||||
.statusCode(201)
|
||||
.contentType(ContentType.JSON)
|
||||
.body("", lenientlyEquals("""
|
||||
{
|
||||
"type": "MANAGED_WEBSPACE",
|
||||
"caption": "some managed webspace",
|
||||
"validFrom": "{today}",
|
||||
"validTo": null
|
||||
}
|
||||
"""
|
||||
.replace("{today}", LocalDate.now().toString())
|
||||
.replace("{todayPlus1Month}", LocalDate.now().plusMonths(1).toString()))
|
||||
)
|
||||
.header("Location", matchesRegex("http://localhost:[1-9][0-9]*/api/hs/booking/items/[^/]*"))
|
||||
.extract().header("Location"); // @formatter:on
|
||||
|
||||
// then, the new BookingItem can be accessed under the generated UUID
|
||||
final var newBookingItem = fetchRealBookingItemFromURI(location);
|
||||
assertThat(newBookingItem)
|
||||
.extracting(HsBookingItem::getCaption)
|
||||
.isEqualTo("some managed webspace");
|
||||
|
||||
// and the related HostingAssets are also got created
|
||||
final var domainSetupHostingAsset = realHostingAssetRepo.findByIdentifier("fir00");
|
||||
assertThat(domainSetupHostingAsset).isNotEmpty()
|
||||
.map(HsHostingAsset::getBookingItem)
|
||||
.contains(newBookingItem);
|
||||
final var event = bookingItemCreationEventRepo.findByBookingItem(newBookingItem);
|
||||
assertThat(event).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void projectAgent_canAddDomainSetupBookingItemWithHostingAsset() {
|
||||
|
||||
context.define("superuser-alex@hostsharing.net", "hs_booking.project#D-1000111-D-1000111defaultproject:AGENT");
|
||||
final var givenProject = findDefaultProjectOfDebitorNumber(1000111);
|
||||
// TODO.impl: "sec01-web" should not work, but does
|
||||
final var givenUnixUser = realHostingAssetRepo.findByTypeAndIdentifier(UNIX_USER, "fir01-web").stream()
|
||||
.findFirst().orElseThrow();
|
||||
|
||||
Dns.fakeResultForDomain("example.org",
|
||||
Dns.Result.fromRecords("Hostsharing-domain-setup-verification-code=just-a-fake-verification-code"));
|
||||
|
||||
final var location = RestAssured // @formatter:off
|
||||
.given()
|
||||
.header("current-subject", "superuser-alex@hostsharing.net")
|
||||
.contentType(ContentType.JSON)
|
||||
.body("""
|
||||
{
|
||||
"projectUuid": "{projectUuid}",
|
||||
"type": "DOMAIN_SETUP",
|
||||
"caption": "Domain-Setup for example.org",
|
||||
"resources": {
|
||||
"domainName": "example.org",
|
||||
"verificationCode": "just-a-fake-verification-code"
|
||||
},
|
||||
"hostingAsset": {
|
||||
"identifier": "example.org", // also as default for all subAssets
|
||||
"subHostingAssets": [
|
||||
{
|
||||
"type": "DOMAIN_DNS_SETUP"
|
||||
},
|
||||
{
|
||||
"type": "DOMAIN_HTTP_SETUP",
|
||||
"assignedToAssetUuid": "{unixUserUuid}"
|
||||
},
|
||||
{
|
||||
"type": "DOMAIN_MBOX_SETUP"
|
||||
},
|
||||
{
|
||||
"type": "DOMAIN_SMTP_SETUP"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
"""
|
||||
.replace("{projectUuid}", givenProject.getUuid().toString())
|
||||
.replace("{unixUserUuid}", givenUnixUser.getUuid().toString())
|
||||
)
|
||||
.port(port)
|
||||
.when()
|
||||
.post("http://localhost/api/hs/booking/items")
|
||||
.then().log().all().assertThat()
|
||||
@@ -225,10 +309,9 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
|
||||
.body("", lenientlyEquals("""
|
||||
{
|
||||
"type": "DOMAIN_SETUP",
|
||||
"caption": "some new domain-setup booking",
|
||||
"caption": "Domain-Setup for example.org",
|
||||
"validFrom": "{today}",
|
||||
"validTo": null,
|
||||
"resources": { "domainName": "example.org", "targetUnixUser": "fir01-web" }
|
||||
"validTo": null
|
||||
}
|
||||
"""
|
||||
.replace("{today}", LocalDate.now().toString())
|
||||
@@ -240,24 +323,34 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
|
||||
// then, the new BookingItem can be accessed under the generated UUID
|
||||
final var newBookingItem = fetchRealBookingItemFromURI(location);
|
||||
assertThat(newBookingItem)
|
||||
.extracting(bi -> bi.getDirectValue("domainName", String.class))
|
||||
.isEqualTo("example.org");
|
||||
.extracting(HsBookingItem::getCaption)
|
||||
.isEqualTo("Domain-Setup for example.org");
|
||||
|
||||
// and the related HostingAsset also got created
|
||||
assertThat(realHostingAssetRepo.findByIdentifier("example.org")).isNotEmpty()
|
||||
// and the related HostingAssets are also got created
|
||||
final var domainSetupHostingAsset = realHostingAssetRepo.findByIdentifier("example.org");
|
||||
assertThat(domainSetupHostingAsset).isNotEmpty()
|
||||
.map(HsHostingAsset::getBookingItem)
|
||||
.contains(newBookingItem);
|
||||
// TODO.legacy: add check for example.org|DNS
|
||||
assertThat(realHostingAssetRepo.findByIdentifier("example.org|HTTP")).isNotEmpty()
|
||||
.map(HsHostingAsset::getParentAsset)
|
||||
.isEqualTo(domainSetupHostingAsset);
|
||||
assertThat(realHostingAssetRepo.findByIdentifier("example.org|MBOX")).isNotEmpty()
|
||||
.map(HsHostingAsset::getParentAsset)
|
||||
.isEqualTo(domainSetupHostingAsset);
|
||||
assertThat(realHostingAssetRepo.findByIdentifier("example.org|SMTP")).isNotEmpty()
|
||||
.map(HsHostingAsset::getParentAsset)
|
||||
.isEqualTo(domainSetupHostingAsset);
|
||||
final var event = bookingItemCreationEventRepo.findByBookingItem(newBookingItem);
|
||||
assertThat(event).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void projectAgent_canAddBookingItemEvenIfHostingAssetCreationFails() {
|
||||
|
||||
context.define("superuser-alex@hostsharing.net", "hs_booking.project#D-1000111-D-1000111defaultproject:AGENT");
|
||||
final var givenProject = debitorRepo.findByDebitorNumber(1000111).stream()
|
||||
.map(d -> realProjectRepo.findAllByDebitorUuid(d.getUuid()))
|
||||
.flatMap(List::stream)
|
||||
.findFirst()
|
||||
.orElseThrow();
|
||||
final var givenProject = findDefaultProjectOfDebitorNumber(1000111);
|
||||
final var givenUnixUser = realHostingAssetRepo.findByIdentifier("fir01-web").stream().findFirst().orElseThrow();
|
||||
|
||||
Dns.fakeResultForDomain("example.org", Dns.Result.fromRecords()); // without valid verificationCode
|
||||
|
||||
@@ -272,12 +365,30 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
|
||||
"caption": "some new domain-setup booking",
|
||||
"resources": {
|
||||
"domainName": "example.org",
|
||||
"targetUnixUser": "fir01-web",
|
||||
"verificationCode": "just-a-fake-verification-code"
|
||||
},
|
||||
"hostingAsset": {
|
||||
"identifier": "example.org", // also as default for all subAssets
|
||||
"subHostingAssets": [
|
||||
{
|
||||
"type": "DOMAIN_DNS_SETUP"
|
||||
},
|
||||
{
|
||||
"type": "DOMAIN_HTTP_SETUP",
|
||||
"assignedToAssetUuid": "{unixUserUuid}"
|
||||
},
|
||||
{
|
||||
"type": "DOMAIN_MBOX_SETUP"
|
||||
},
|
||||
{
|
||||
"type": "DOMAIN_SMTP_SETUP"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
"""
|
||||
.replace("{projectUuid}", givenProject.getUuid().toString())
|
||||
.replace("{unixUserUuid}", givenUnixUser.getUuid().toString())
|
||||
)
|
||||
.port(port)
|
||||
.when()
|
||||
@@ -291,7 +402,7 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
|
||||
"caption": "some new domain-setup booking",
|
||||
"validFrom": "{today}",
|
||||
"validTo": null,
|
||||
"resources": { "domainName": "example.org", "targetUnixUser": "fir01-web" }
|
||||
"resources": { "domainName": "example.org" }
|
||||
}
|
||||
"""
|
||||
.replace("{today}", LocalDate.now().toString())
|
||||
@@ -305,8 +416,8 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
|
||||
assertThat(newBookingItem)
|
||||
.extracting(bi -> bi.getDirectValue("domainName", String.class))
|
||||
.isEqualTo("example.org");
|
||||
assertThat(newBookingItem)
|
||||
.extracting(bi -> bi.getDirectValue("status", String.class))
|
||||
final var event = bookingItemCreationEventRepo.findByBookingItem(newBookingItem);
|
||||
assertThat(event.getStatusMessage())
|
||||
.isEqualTo("[[DNS] no TXT record 'Hostsharing-domain-setup-verification-code=just-a-fake-verification-code' found for domain name 'example.org' (nor in its super-domain)]");
|
||||
|
||||
// but the related HostingAsset did not get created
|
||||
@@ -314,6 +425,14 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
|
||||
}
|
||||
}
|
||||
|
||||
private @NotNull HsBookingProjectRealEntity findDefaultProjectOfDebitorNumber(final int debitorNumber) {
|
||||
return debitorRepo.findByDebitorNumber(debitorNumber).stream()
|
||||
.map(d -> realProjectRepo.findAllByDebitorUuid(d.getUuid()))
|
||||
.flatMap(List::stream)
|
||||
.findFirst()
|
||||
.orElseThrow();
|
||||
}
|
||||
|
||||
@Nested
|
||||
@Order(1)
|
||||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||
@@ -534,6 +653,13 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
|
||||
}).assertSuccessful().returnedValue();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void cleanupEventEntities() {
|
||||
jpaAttempt.transacted(() -> {
|
||||
em.createQuery("delete from BookingItemCreatedEventEntity").executeUpdate();
|
||||
}).assertSuccessful();
|
||||
}
|
||||
|
||||
private Map.Entry<String, Object> resource(final String key, final Object value) {
|
||||
return entry(key, value);
|
||||
}
|
||||
|
||||
+5
-31
@@ -36,8 +36,7 @@ class HsDomainSetupBookingItemValidatorUnitTest {
|
||||
.project(project)
|
||||
.caption("Test-Domain")
|
||||
.resources(Map.ofEntries(
|
||||
entry("domainName", "example.org"),
|
||||
entry("targetUnixUser", "xyz00")
|
||||
entry("domainName", "example.org")
|
||||
))
|
||||
.build();
|
||||
|
||||
@@ -59,7 +58,6 @@ class HsDomainSetupBookingItemValidatorUnitTest {
|
||||
.caption("Test-Domain")
|
||||
.resources(Map.ofEntries(
|
||||
entry("domainName", "example.org"),
|
||||
entry("targetUnixUser", "xyz00"),
|
||||
entry("verificationCode", "1234-5678-9100")
|
||||
))
|
||||
.build();
|
||||
@@ -80,8 +78,7 @@ class HsDomainSetupBookingItemValidatorUnitTest {
|
||||
.project(project)
|
||||
.caption("Test-Domain")
|
||||
.resources(Map.ofEntries(
|
||||
entry("domainName", right(TOO_LONG_DOMAIN_NAME, 253)),
|
||||
entry("targetUnixUser", "xyz00")
|
||||
entry("domainName", right(TOO_LONG_DOMAIN_NAME, 253))
|
||||
))
|
||||
.build();
|
||||
|
||||
@@ -99,8 +96,7 @@ class HsDomainSetupBookingItemValidatorUnitTest {
|
||||
.project(project)
|
||||
.caption("Test-Domain")
|
||||
.resources(Map.ofEntries(
|
||||
entry("domainName", right(TOO_LONG_DOMAIN_NAME, 254)),
|
||||
entry("targetUnixUser", "xyz00")
|
||||
entry("domainName", right(TOO_LONG_DOMAIN_NAME, 254))
|
||||
))
|
||||
.build();
|
||||
|
||||
@@ -118,8 +114,7 @@ class HsDomainSetupBookingItemValidatorUnitTest {
|
||||
.project(project)
|
||||
.caption("Test-Domain")
|
||||
.resources(Map.ofEntries(
|
||||
entry("domainName", "example.com"),
|
||||
entry("targetUnixUser", "xyz00-test")
|
||||
entry("domainName", "example.com")
|
||||
))
|
||||
.build();
|
||||
|
||||
@@ -130,25 +125,6 @@ class HsDomainSetupBookingItemValidatorUnitTest {
|
||||
assertThat(result).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsInvalidUnixUser() {
|
||||
final var domainSetupBookingItemEntity = HsBookingItemRealEntity.builder()
|
||||
.type(DOMAIN_SETUP)
|
||||
.project(project)
|
||||
.caption("Test-Domain")
|
||||
.resources(Map.ofEntries(
|
||||
entry("domainName", "example.com"),
|
||||
entry("targetUnixUser", "xyz00test")
|
||||
))
|
||||
.build();
|
||||
|
||||
// when
|
||||
final var result = HsBookingItemEntityValidatorRegistry.doValidate(em, domainSetupBookingItemEntity);
|
||||
|
||||
// then
|
||||
assertThat(result).contains("'D-12345:Test-Project:Test-Domain.resources.targetUnixUser' = 'xyz00test' is not a valid unix-user name");
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = {
|
||||
"de", "com", "net", "org", "actually-any-top-level-domain",
|
||||
@@ -196,8 +172,7 @@ class HsDomainSetupBookingItemValidatorUnitTest {
|
||||
.project(project)
|
||||
.caption("Test-Domain")
|
||||
.resources(Map.ofEntries(
|
||||
entry("domainName", secondLevelRegistrarDomain),
|
||||
entry("targetUnixUser", "xyz00")
|
||||
entry("domainName", secondLevelRegistrarDomain)
|
||||
))
|
||||
.build();
|
||||
|
||||
@@ -219,7 +194,6 @@ class HsDomainSetupBookingItemValidatorUnitTest {
|
||||
// then
|
||||
assertThat(validator.properties()).map(Map::toString).containsExactlyInAnyOrder(
|
||||
"{type=string, propertyName=domainName, matchesRegEx=[^((?!-)[A-Za-z0-9-]{1,63}(?<!-)\\.)+[A-Za-z]{2,12}], matchesRegExDescription=is not a (non-top-level) fully qualified domain name, notMatchesRegEx=[[^.]+, (co|org|gov|ac|sch)\\.uk, (com|net|org|edu|gov|asn|id)\\.au, (co|ne|or|ac|go)\\.jp, (com|net|org|gov|edu|ac)\\.cn, (com|net|org|gov|edu|mil|art)\\.br, (co|net|org|gen|firm|ind)\\.in, (com|net|org|gob|edu)\\.mx, (gov|edu)\\.it, (co|net|org|govt|ac|school|geek|kiwi)\\.nz, (co|ne|or|go|re|pe)\\.kr], notMatchesRegExDescription=is a forbidden registrar-level domain name, maxLength=253, required=true, writeOnce=true}",
|
||||
"{type=string, propertyName=targetUnixUser, matchesRegEx=[^[a-z][a-z0-9]{2}[0-9]{2}$|^[a-z][a-z0-9]{2}[0-9]{2}-[a-z0-9\\._-]+$], matchesRegExDescription=is not a valid unix-user name, maxLength=253, required=true, writeOnce=true}",
|
||||
"{type=string, propertyName=verificationCode, minLength=12, maxLength=64, computed=IN_INIT}");
|
||||
}
|
||||
}
|
||||
|
||||
+16
-4
@@ -157,6 +157,7 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
|
||||
)
|
||||
);
|
||||
final var givenParentAsset = givenParentAsset(MANAGED_SERVER, "vm1011");
|
||||
final var expectedUnixUserId = nextUnixUserId();
|
||||
|
||||
final var location = RestAssured // @formatter:off
|
||||
.given()
|
||||
@@ -184,10 +185,12 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
|
||||
"identifier": "fir10",
|
||||
"caption": "some separate ManagedWebspace HA",
|
||||
"config": {
|
||||
"groupid": 1000000
|
||||
"groupid": {lastUnixUserId}
|
||||
}
|
||||
}
|
||||
"""))
|
||||
"""
|
||||
.replace("{lastUnixUserId}", expectedUnixUserId.toString())
|
||||
))
|
||||
.header("Location", matchesRegex("http://localhost:[1-9][0-9]*/api/hs/hosting/assets/[^/]*"))
|
||||
.extract().header("Location"); // @formatter:on
|
||||
|
||||
@@ -205,9 +208,11 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
|
||||
.isEqualTo("""
|
||||
HsHostingAsset(UNIX_USER, fir10, fir10 webspace user, MANAGED_WEBSPACE:fir10, {
|
||||
"password" : null,
|
||||
"userid" : 1000000
|
||||
"userid" : {lastUnixUserId}
|
||||
})
|
||||
""".trim());
|
||||
"""
|
||||
.replace("{lastUnixUserId}", expectedUnixUserId.toString())
|
||||
.trim());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -777,4 +782,11 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
|
||||
}).returnedValue();
|
||||
}
|
||||
|
||||
|
||||
private Integer nextUnixUserId() {
|
||||
final Object result = em.createNativeQuery("SELECT nextval('hs_hosting.asset_unixuser_system_id_seq')", Integer.class)
|
||||
.getSingleResult();
|
||||
return (Integer) result + 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+259
@@ -0,0 +1,259 @@
|
||||
package net.hostsharing.hsadminng.hs.hosting.asset.factories;
|
||||
|
||||
import net.hostsharing.hsadminng.hs.booking.item.BookingItemCreatedAppEvent;
|
||||
import net.hostsharing.hsadminng.hs.booking.item.BookingItemCreatedEventEntity;
|
||||
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemRealEntity;
|
||||
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetRealEntity;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.validators.Dns;
|
||||
import net.hostsharing.hsadminng.lambda.Reducer;
|
||||
import net.hostsharing.hsadminng.persistence.EntityManagerWrapperFake;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import net.hostsharing.hsadminng.config.JsonObjectMapperConfiguration;
|
||||
import net.hostsharing.hsadminng.mapper.StandardMapper;
|
||||
import net.hostsharing.hsadminng.persistence.EntityManagerWrapper;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Spy;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_WEBSPACE;
|
||||
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.UNIX_USER;
|
||||
import static net.hostsharing.hsadminng.mapper.PatchableMapWrapper.entry;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
// Tests the DomainSetupHostingAssetFactory through a HsBookingItemCreatedListener instance.
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class DomainSetupHostingAssetFactoryUnitTest {
|
||||
|
||||
private final HsHostingAssetRealEntity managedWebspaceHostingAsset = HsHostingAssetRealEntity.builder()
|
||||
.uuid(UUID.randomUUID())
|
||||
.type(MANAGED_WEBSPACE)
|
||||
.identifier("one00")
|
||||
.build();
|
||||
|
||||
private final HsHostingAssetRealEntity unixUserHostingAsset = HsHostingAssetRealEntity.builder()
|
||||
.uuid(UUID.randomUUID())
|
||||
.type(UNIX_USER)
|
||||
.identifier("one00-web")
|
||||
.parentAsset(managedWebspaceHostingAsset)
|
||||
.build();
|
||||
|
||||
private final HsHostingAssetRealEntity anotherManagedWebspaceHostingAsset = HsHostingAssetRealEntity.builder()
|
||||
.uuid(UUID.randomUUID())
|
||||
.type(MANAGED_WEBSPACE)
|
||||
.identifier("two00")
|
||||
.build();
|
||||
|
||||
private EntityManagerWrapperFake emwFake = new EntityManagerWrapperFake();
|
||||
|
||||
@Spy
|
||||
private EntityManagerWrapper emw = emwFake;
|
||||
|
||||
@Spy
|
||||
private ObjectMapper jsonMapper = new JsonObjectMapperConfiguration().customObjectMapper().build();
|
||||
|
||||
@Spy
|
||||
private StandardMapper standardMapper = new StandardMapper(emw);
|
||||
|
||||
@InjectMocks
|
||||
private HsBookingItemCreatedListener listener;
|
||||
|
||||
@BeforeEach
|
||||
void initMocks() {
|
||||
emwFake.persist(managedWebspaceHostingAsset);
|
||||
emwFake.persist(unixUserHostingAsset);
|
||||
}
|
||||
|
||||
@Test
|
||||
void doesNotPersistEventEntityWithoutValidationErrors() {
|
||||
// given
|
||||
final var givenBookingItem = createBookingItemFromResources(
|
||||
entry("domainName", "example.org"),
|
||||
entry("verificationCode", "just-a-fake-verification-code")
|
||||
);
|
||||
final var givenAssetJson = """
|
||||
{
|
||||
"identifier": "example.org", // also as default for all subAssets
|
||||
"subHostingAssets": [
|
||||
{
|
||||
"type": "DOMAIN_HTTP_SETUP",
|
||||
"assignedToAssetUuid": "{unixUserHostingAssetUuid}"
|
||||
},
|
||||
{
|
||||
"type": "DOMAIN_DNS_SETUP"
|
||||
},
|
||||
{
|
||||
"type": "DOMAIN_MBOX_SETUP"
|
||||
},
|
||||
{
|
||||
"type": "DOMAIN_SMTP_SETUP"
|
||||
}
|
||||
]
|
||||
}
|
||||
"""
|
||||
.replace("{unixUserHostingAssetUuid}", unixUserHostingAsset.getUuid().toString());
|
||||
Dns.fakeResultForDomain("example.org",
|
||||
Dns.Result.fromRecords("Hostsharing-domain-setup-verification-code=just-a-fake-verification-code"));
|
||||
|
||||
// when
|
||||
listener.onApplicationEvent(
|
||||
new BookingItemCreatedAppEvent(this, givenBookingItem, givenAssetJson)
|
||||
);
|
||||
|
||||
// then
|
||||
assertThat(emwFake.stream(BookingItemCreatedEventEntity.class))
|
||||
.as("the event should not have been persisted, but got persisted")
|
||||
.isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void persistsEventEntityIfDomainSetupVerificationFails() {
|
||||
// given
|
||||
final var givenBookingItem = createBookingItemFromResources(
|
||||
entry("domainName", "example.org")
|
||||
);
|
||||
final var givenAssetJson = """
|
||||
{
|
||||
"identifier": "example.org", // also as default for all subAssets
|
||||
"subHostingAssets": [
|
||||
{
|
||||
"type": "DOMAIN_HTTP_SETUP",
|
||||
"assignedToAssetUuid": "{unixUserHostingAssetUuid}"
|
||||
},
|
||||
{
|
||||
"type": "DOMAIN_DNS_SETUP"
|
||||
},
|
||||
{
|
||||
"type": "DOMAIN_MBOX_SETUP"
|
||||
},
|
||||
{
|
||||
"type": "DOMAIN_SMTP_SETUP"
|
||||
}
|
||||
]
|
||||
}
|
||||
"""
|
||||
.replace("{unixUserHostingAssetUuid}", unixUserHostingAsset.getUuid().toString());
|
||||
Dns.fakeResultForDomain("example.org", Dns.Result.fromRecords()); // without valid verificationCode
|
||||
|
||||
// when
|
||||
listener.onApplicationEvent(
|
||||
new BookingItemCreatedAppEvent(this, givenBookingItem, givenAssetJson)
|
||||
);
|
||||
|
||||
// then
|
||||
assertEventStatus(givenBookingItem, givenAssetJson,
|
||||
"[[DNS] no TXT record 'Hostsharing-domain-setup-verification-code=null' found for domain name 'example.org' (nor in its super-domain)]");
|
||||
}
|
||||
|
||||
@Test
|
||||
void persistsEventEntityIfDomainDnsSetupIsSupplied() {
|
||||
// given
|
||||
final var givenBookingItem = createBookingItemFromResources(
|
||||
entry("domainName", "example.org"),
|
||||
entry("verificationCode", "just-a-fake-verification-code")
|
||||
);
|
||||
final var givenAssetJson = """
|
||||
{
|
||||
"identifier": "example.org", // also as default for all subAssets
|
||||
"subHostingAssets": [
|
||||
{
|
||||
"type": "DOMAIN_HTTP_SETUP",
|
||||
"assignedToAssetUuid": "{unixUserHostingAssetUuid}"
|
||||
},
|
||||
{
|
||||
"type": "DOMAIN_DNS_SETUP"
|
||||
},
|
||||
{
|
||||
"type": "DOMAIN_MBOX_SETUP"
|
||||
},
|
||||
{
|
||||
"type": "DOMAIN_SMTP_SETUP"
|
||||
}
|
||||
]
|
||||
}
|
||||
"""
|
||||
.replace("{unixUserHostingAssetUuid}", unixUserHostingAsset.getUuid().toString())
|
||||
.replace("{managedWebspaceHostingAssetUuid}", managedWebspaceHostingAsset.getUuid().toString());
|
||||
Dns.fakeResultForDomain("example.org",
|
||||
Dns.Result.fromRecords("Hostsharing-domain-setup-verification-code=just-a-fake-verification-code"));
|
||||
|
||||
// when
|
||||
listener.onApplicationEvent(
|
||||
new BookingItemCreatedAppEvent(this, givenBookingItem, givenAssetJson)
|
||||
);
|
||||
|
||||
// then
|
||||
assertEventStatus(givenBookingItem, givenAssetJson,
|
||||
"domain DNS setup not allowed for legacy compatibility");
|
||||
}
|
||||
|
||||
@Test
|
||||
void persistsEventEntityIfSuppliedDomainUnixUserAndSmtpSetupWebspaceDontMatch() {
|
||||
// given
|
||||
final var givenBookingItem = createBookingItemFromResources(
|
||||
entry("domainName", "example.org"),
|
||||
entry("verificationCode", "just-a-fake-verification-code")
|
||||
);
|
||||
final var givenAssetJson = """
|
||||
{
|
||||
"identifier": "example.org", // also as default for all subAssets
|
||||
"subHostingAssets": [
|
||||
{
|
||||
"type": "DOMAIN_HTTP_SETUP",
|
||||
"assignedToAssetUuid": "{unixUserHostingAssetUuid}"
|
||||
},
|
||||
{
|
||||
"type": "DOMAIN_DNS_SETUP"
|
||||
},
|
||||
{
|
||||
"type": "DOMAIN_MBOX_SETUP"
|
||||
},
|
||||
{
|
||||
"type": "DOMAIN_SMTP_SETUP"
|
||||
}
|
||||
]
|
||||
}
|
||||
"""
|
||||
.replace("{unixUserHostingAssetUuid}", unixUserHostingAsset.getUuid().toString())
|
||||
.replace("{managedWebspaceHostingAssetUuid}", anotherManagedWebspaceHostingAsset.getUuid().toString());
|
||||
Dns.fakeResultForDomain("example.org",
|
||||
Dns.Result.fromRecords("Hostsharing-domain-setup-verification-code=just-a-fake-verification-code"));
|
||||
|
||||
// when
|
||||
listener.onApplicationEvent(
|
||||
new BookingItemCreatedAppEvent(this, givenBookingItem, givenAssetJson)
|
||||
);
|
||||
|
||||
// then
|
||||
assertEventStatus(givenBookingItem, givenAssetJson,
|
||||
"domain DNS setup not allowed for legacy compatibility");
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
private static HsBookingItemRealEntity createBookingItemFromResources(final Map.Entry<String, String>... givenResources) {
|
||||
return HsBookingItemRealEntity.builder()
|
||||
.type(HsBookingItemType.DOMAIN_SETUP)
|
||||
.resources(Map.ofEntries(givenResources))
|
||||
.build();
|
||||
}
|
||||
|
||||
private void assertEventStatus(
|
||||
final HsBookingItemRealEntity givenBookingItem,
|
||||
final String givenAssetJson,
|
||||
final String expectedErrorMessage) {
|
||||
emwFake.stream(BookingItemCreatedEventEntity.class)
|
||||
.reduce(Reducer::toSingleElement)
|
||||
.map(eventEntity -> {
|
||||
assertThat(eventEntity.getBookingItem()).isSameAs(givenBookingItem);
|
||||
assertThat(eventEntity.getAssetJson()).isEqualTo(givenAssetJson);
|
||||
assertThat(eventEntity.getStatusMessage()).isEqualTo(expectedErrorMessage);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
package net.hostsharing.hsadminng.hs.hosting.asset.factories;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import net.hostsharing.hsadminng.config.JsonObjectMapperConfiguration;
|
||||
import net.hostsharing.hsadminng.hs.booking.debitor.HsBookingDebitorEntity;
|
||||
import net.hostsharing.hsadminng.hs.booking.item.BookingItemCreatedAppEvent;
|
||||
import net.hostsharing.hsadminng.hs.booking.item.BookingItemCreatedEventEntity;
|
||||
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemRealEntity;
|
||||
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType;
|
||||
import net.hostsharing.hsadminng.lambda.Reducer;
|
||||
import net.hostsharing.hsadminng.mapper.StandardMapper;
|
||||
import net.hostsharing.hsadminng.persistence.EntityManagerWrapper;
|
||||
import net.hostsharing.hsadminng.persistence.EntityManagerWrapperFake;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Spy;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.DOMAIN_SETUP;
|
||||
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.MANAGED_WEBSPACE;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class HsBookingItemCreatedListenerUnitTest {
|
||||
|
||||
final HsBookingDebitorEntity debitor = HsBookingDebitorEntity.builder()
|
||||
.debitorNumber(12345)
|
||||
.defaultPrefix("xyz")
|
||||
.build();
|
||||
|
||||
private EntityManagerWrapperFake emwFake = new EntityManagerWrapperFake();
|
||||
|
||||
@Spy
|
||||
private EntityManagerWrapper emw = emwFake;
|
||||
|
||||
@Spy
|
||||
private ObjectMapper jsonMapper = new JsonObjectMapperConfiguration().customObjectMapper().build();
|
||||
|
||||
@Spy
|
||||
private StandardMapper standardMapper = new StandardMapper(emw);
|
||||
|
||||
@InjectMocks
|
||||
private HsBookingItemCreatedListener listener;
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("bookingItemTypesWithoutAutomaticAssetCreation")
|
||||
void persistsEventEntityIfBookingItemTypeDoesNotSupportAutomaticHostingAssetCreation(final HsBookingItemType bookingItemType) {
|
||||
// given
|
||||
final var givenBookingItem = createBookingItemFromResources(bookingItemType);
|
||||
final var givenAssetJson = """
|
||||
{
|
||||
// anything should be rejected
|
||||
}
|
||||
""";
|
||||
|
||||
// when
|
||||
listener.onApplicationEvent(
|
||||
new BookingItemCreatedAppEvent(this, givenBookingItem, givenAssetJson)
|
||||
);
|
||||
|
||||
// then
|
||||
assertEventStatus(givenBookingItem, givenAssetJson,
|
||||
"waiting for manual setup of hosting asset for booking item of type " + bookingItemType);
|
||||
}
|
||||
|
||||
static List<HsBookingItemType> bookingItemTypesWithoutAutomaticAssetCreation() {
|
||||
return Arrays.stream(HsBookingItemType.values())
|
||||
.filter(v -> v != MANAGED_WEBSPACE && v != DOMAIN_SETUP)
|
||||
.toList();
|
||||
}
|
||||
|
||||
private static HsBookingItemRealEntity createBookingItemFromResources(
|
||||
final HsBookingItemType bookingItemType
|
||||
) {
|
||||
return HsBookingItemRealEntity.builder()
|
||||
.type(bookingItemType)
|
||||
.build();
|
||||
}
|
||||
|
||||
private void assertEventStatus(
|
||||
final HsBookingItemRealEntity givenBookingItem,
|
||||
final String givenAssetJson,
|
||||
final String expectedErrorMessage) {
|
||||
emwFake.stream(BookingItemCreatedEventEntity.class)
|
||||
.reduce(Reducer::toSingleElement)
|
||||
.map(eventEntity -> {
|
||||
assertThat(eventEntity.getBookingItem()).isSameAs(givenBookingItem);
|
||||
assertThat(eventEntity.getAssetJson()).isEqualTo(givenAssetJson);
|
||||
assertThat(eventEntity.getStatusMessage()).isEqualTo(expectedErrorMessage);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
+137
@@ -0,0 +1,137 @@
|
||||
package net.hostsharing.hsadminng.hs.hosting.asset.factories;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import net.hostsharing.hsadminng.config.JsonObjectMapperConfiguration;
|
||||
import net.hostsharing.hsadminng.hs.booking.debitor.HsBookingDebitorEntity;
|
||||
import net.hostsharing.hsadminng.hs.booking.item.BookingItemCreatedAppEvent;
|
||||
import net.hostsharing.hsadminng.hs.booking.item.BookingItemCreatedEventEntity;
|
||||
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemRealEntity;
|
||||
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType;
|
||||
import net.hostsharing.hsadminng.hs.booking.project.HsBookingProjectRealEntity;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetRealEntity;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.validators.Dns;
|
||||
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContact;
|
||||
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRealEntity;
|
||||
import net.hostsharing.hsadminng.lambda.Reducer;
|
||||
import net.hostsharing.hsadminng.mapper.StandardMapper;
|
||||
import net.hostsharing.hsadminng.persistence.EntityManagerWrapper;
|
||||
import net.hostsharing.hsadminng.persistence.EntityManagerWrapperFake;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Spy;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import static net.hostsharing.hsadminng.mapper.PatchableMapWrapper.entry;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
// Tests the DomainSetupHostingAssetFactory through a HsBookingItemCreatedListener instance.
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class ManagedWebspaceHostingAssetFactoryUnitTest {
|
||||
|
||||
final HsBookingDebitorEntity debitor = HsBookingDebitorEntity.builder()
|
||||
.debitorNumber(12345)
|
||||
.defaultPrefix("xyz")
|
||||
.build();
|
||||
final HsBookingProjectRealEntity project = HsBookingProjectRealEntity.builder()
|
||||
.debitor(debitor)
|
||||
.caption("Test-Project")
|
||||
.build();
|
||||
final HsOfficeContact alarmContact = HsOfficeContactRealEntity.builder()
|
||||
.uuid(UUID.randomUUID())
|
||||
.caption("Alarm Contact xyz")
|
||||
.build();
|
||||
|
||||
private EntityManagerWrapperFake emwFake = new EntityManagerWrapperFake();
|
||||
|
||||
@Spy
|
||||
private EntityManagerWrapper emw = emwFake;
|
||||
|
||||
@Spy
|
||||
private ObjectMapper jsonMapper = new JsonObjectMapperConfiguration().customObjectMapper().build();
|
||||
|
||||
@Spy
|
||||
private StandardMapper standardMapper = new StandardMapper(emw);
|
||||
|
||||
@InjectMocks
|
||||
private HsBookingItemCreatedListener listener;
|
||||
|
||||
@BeforeEach
|
||||
void initMocks() {
|
||||
emwFake.persist(alarmContact);
|
||||
}
|
||||
|
||||
@Test
|
||||
void doesNotPersistAnyEntityWithoutHostingAssetWithoutValidationErrors() {
|
||||
// given
|
||||
final var givenBookingItem = HsBookingItemRealEntity.builder()
|
||||
.type(HsBookingItemType.MANAGED_WEBSPACE)
|
||||
.project(project)
|
||||
.caption("Test Managed-Webspace")
|
||||
.resources(Map.ofEntries(
|
||||
Map.entry("RAM", 25),
|
||||
Map.entry("Traffic", 250)
|
||||
))
|
||||
.build();
|
||||
|
||||
// when
|
||||
listener.onApplicationEvent(
|
||||
new BookingItemCreatedAppEvent(this, givenBookingItem, null)
|
||||
);
|
||||
|
||||
// then
|
||||
assertThat(emwFake.stream(BookingItemCreatedEventEntity.class).findAny().isEmpty())
|
||||
.as("the event should not have been persisted, but got persisted").isTrue();
|
||||
assertThat(emwFake.stream(HsHostingAssetRealEntity.class).findAny().isEmpty())
|
||||
.as("the hosting asset should not have been persisted, but got persisted").isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void persistsEventEntityIfDomainSetupVerificationFails() {
|
||||
// given
|
||||
final var givenBookingItem = createBookingItemFromResources(
|
||||
entry("domainName", "example.org")
|
||||
);
|
||||
final var givenAssetJson = """
|
||||
{
|
||||
"identifier": "xyz00"
|
||||
}
|
||||
""";
|
||||
Dns.fakeResultForDomain("example.org", Dns.Result.fromRecords()); // without valid verificationCode
|
||||
|
||||
// when
|
||||
listener.onApplicationEvent(
|
||||
new BookingItemCreatedAppEvent(this, givenBookingItem, givenAssetJson)
|
||||
);
|
||||
|
||||
// then
|
||||
assertEventStatus(givenBookingItem, givenAssetJson,
|
||||
"requires MANAGED_WEBSPACE hosting asset, but got null");
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
private static HsBookingItemRealEntity createBookingItemFromResources(final Map.Entry<String, String>... givenResources) {
|
||||
return HsBookingItemRealEntity.builder()
|
||||
.type(HsBookingItemType.MANAGED_WEBSPACE)
|
||||
.resources(Map.ofEntries(givenResources))
|
||||
.build();
|
||||
}
|
||||
|
||||
private void assertEventStatus(
|
||||
final HsBookingItemRealEntity givenBookingItem,
|
||||
final String givenAssetJson,
|
||||
final String expectedErrorMessage) {
|
||||
emwFake.stream(BookingItemCreatedEventEntity.class)
|
||||
.reduce(Reducer::toSingleElement)
|
||||
.map(eventEntity -> {
|
||||
assertThat(eventEntity.getBookingItem()).isSameAs(givenBookingItem);
|
||||
assertThat(eventEntity.getAssetJson()).isEqualTo(givenAssetJson);
|
||||
assertThat(eventEntity.getStatusMessage()).isEqualTo(expectedErrorMessage);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
+1
-2
@@ -42,8 +42,7 @@ class HsDomainSetupHostingAssetValidatorUnitTest {
|
||||
.project(project)
|
||||
.type(HsBookingItemType.DOMAIN_SETUP)
|
||||
.resources(new HashMap<>(ofEntries(
|
||||
entry("domainName", domainName),
|
||||
entry("targetUnixUser", "xyz00")
|
||||
entry("domainName", domainName)
|
||||
))));
|
||||
HsBookingItemEntityValidatorRegistry.forType(HsBookingItemType.DOMAIN_SETUP).prepareProperties(null, bookingItem);
|
||||
return HsHostingAssetRbacEntity.builder()
|
||||
|
||||
@@ -6,7 +6,7 @@ import com.opencsv.CSVReaderBuilder;
|
||||
import lombok.SneakyThrows;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAsset;
|
||||
import net.hostsharing.hsadminng.rbac.context.ContextBasedTest;
|
||||
import net.hostsharing.hsadminng.rbac.object.BaseEntity;
|
||||
import net.hostsharing.hsadminng.persistence.BaseEntity;
|
||||
import net.hostsharing.hsadminng.rbac.test.JpaAttempt;
|
||||
import org.junit.jupiter.api.extension.BeforeEachCallback;
|
||||
import org.junit.jupiter.api.extension.ExtensionContext;
|
||||
|
||||
@@ -1680,18 +1680,13 @@ public class ImportHostingAssets extends BaseOfficeDataImport {
|
||||
final var relatedProject = domainSetup.getSubHostingAssets().stream()
|
||||
.map(ha -> ha.getAssignedToAsset() != null ? ha.getAssignedToAsset().getRelatedProject() : null)
|
||||
.findAny().orElseThrow();
|
||||
final var targetUnixUser = domainSetup.getSubHostingAssets().stream()
|
||||
.filter(subAsset -> subAsset.getType() == DOMAIN_HTTP_SETUP)
|
||||
.map(domainHttpSetup -> domainHttpSetup.getAssignedToAsset().getIdentifier())
|
||||
.findAny().orElse(null);
|
||||
final var bookingItem = HsBookingItemRealEntity.builder()
|
||||
.type(HsBookingItemType.DOMAIN_SETUP)
|
||||
.caption("BI " + domainSetup.getIdentifier())
|
||||
.project((HsBookingProjectRealEntity) relatedProject)
|
||||
//.validity(toPostgresDateRange(created, cancelled))
|
||||
.resources(Map.ofEntries(
|
||||
entry("domainName", domainSetup.getIdentifier()),
|
||||
entry("targetUnixUser", targetUnixUser)
|
||||
entry("domainName", domainSetup.getIdentifier())
|
||||
))
|
||||
.build();
|
||||
domainSetup.setBookingItem(bookingItem);
|
||||
|
||||
+2
-19
@@ -4,12 +4,11 @@ import net.hostsharing.hsadminng.context.Context;
|
||||
import net.hostsharing.hsadminng.hs.office.coopassets.HsOfficeCoopAssetsTransactionRepository;
|
||||
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity;
|
||||
import net.hostsharing.hsadminng.mapper.StandardMapper;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import net.hostsharing.hsadminng.persistence.EntityManagerWrapper;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.EnumSource;
|
||||
import org.mockito.Mock;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
@@ -18,15 +17,10 @@ import org.springframework.http.MediaType;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
|
||||
|
||||
import jakarta.persistence.EntityManager;
|
||||
import jakarta.persistence.EntityManagerFactory;
|
||||
import jakarta.persistence.SynchronizationType;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
@@ -47,19 +41,8 @@ public class HsOfficeMembershipControllerRestTest {
|
||||
@MockBean
|
||||
HsOfficeMembershipRepository membershipRepo;
|
||||
|
||||
@Mock
|
||||
EntityManager em;
|
||||
|
||||
@MockBean
|
||||
EntityManagerFactory emf;
|
||||
|
||||
@BeforeEach
|
||||
void init() {
|
||||
when(emf.createEntityManager()).thenReturn(em);
|
||||
when(emf.createEntityManager(any(Map.class))).thenReturn(em);
|
||||
when(emf.createEntityManager(any(SynchronizationType.class))).thenReturn(em);
|
||||
when(emf.createEntityManager(any(SynchronizationType.class), any(Map.class))).thenReturn(em);
|
||||
}
|
||||
EntityManagerWrapper em;
|
||||
|
||||
@Nested
|
||||
class AddMembership {
|
||||
|
||||
+3
-3
@@ -5,6 +5,7 @@ import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeMembershipPatchResource;
|
||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeMembershipStatusResource;
|
||||
import net.hostsharing.hsadminng.mapper.StandardMapper;
|
||||
import net.hostsharing.hsadminng.persistence.EntityManagerWrapper;
|
||||
import net.hostsharing.hsadminng.rbac.test.PatchUnitTestBase;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.TestInstance;
|
||||
@@ -12,7 +13,6 @@ import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import jakarta.persistence.EntityManager;
|
||||
import java.time.LocalDate;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Stream;
|
||||
@@ -38,9 +38,9 @@ class HsOfficeMembershipEntityPatcherUnitTest extends PatchUnitTestBase<
|
||||
private static final Boolean PATCHED_MEMBERSHIP_FEE_BILLABLE = false;
|
||||
|
||||
@Mock
|
||||
private EntityManager em;
|
||||
private EntityManagerWrapper em;
|
||||
|
||||
private StandardMapper mapper = new StandardMapper();
|
||||
private StandardMapper mapper = new StandardMapper(em);
|
||||
|
||||
@BeforeEach
|
||||
void initMocks() {
|
||||
|
||||
+2
-2
@@ -6,6 +6,7 @@ import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealRepository;
|
||||
import net.hostsharing.hsadminng.mapper.StandardMapper;
|
||||
import net.hostsharing.hsadminng.persistence.EntityManagerWrapper;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -18,7 +19,6 @@ import org.springframework.http.MediaType;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
|
||||
|
||||
import jakarta.persistence.EntityManager;
|
||||
import jakarta.persistence.EntityManagerFactory;
|
||||
import jakarta.persistence.EntityNotFoundException;
|
||||
import jakarta.persistence.SynchronizationType;
|
||||
@@ -57,7 +57,7 @@ class HsOfficePartnerControllerRestTest {
|
||||
HsOfficeRelationRealRepository relationRepo;
|
||||
|
||||
@MockBean
|
||||
EntityManager em;
|
||||
EntityManagerWrapper em;
|
||||
|
||||
@MockBean
|
||||
EntityManagerFactory emf;
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
package net.hostsharing.hsadminng.persistence;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
import jakarta.persistence.Id;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
|
||||
public class EntityManagerWrapperFake extends EntityManagerWrapper {
|
||||
|
||||
private Map<Class<?>, Map<Object, Object>> entityClasses = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public boolean contains(final Object entity) {
|
||||
final var id = getEntityId(entity);
|
||||
return find(entity.getClass(), id) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T getReference(final Class<T> entityClass, final Object primaryKey) {
|
||||
return find(entityClass, primaryKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T find(final Class<T> entityClass, final Object primaryKey) {
|
||||
if (entityClasses.containsKey(entityClass)) {
|
||||
final var entities = entityClasses.get(entityClass);
|
||||
//noinspection unchecked
|
||||
return entities.keySet().stream()
|
||||
.filter(key -> key.equals(primaryKey))
|
||||
.map(key -> (T) entities.get(key))
|
||||
.findAny()
|
||||
.orElse(null);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void persist(final Object entity) {
|
||||
if (!entityClasses.containsKey(entity.getClass())) {
|
||||
entityClasses.put(entity.getClass(), new HashMap<>());
|
||||
}
|
||||
final var id = getEntityId(entity).orElseGet(() -> setEntityId(entity, UUID.randomUUID()));
|
||||
entityClasses.get(entity.getClass()).put(id, entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() {
|
||||
}
|
||||
|
||||
public Stream<Object> stream() {
|
||||
return entityClasses.values().stream().flatMap(entitiesPerClass -> entitiesPerClass.values().stream());
|
||||
}
|
||||
|
||||
public <T> Stream<T> stream(final Class<T> entityClass) {
|
||||
if (entityClasses.containsKey(entityClass)) {
|
||||
//noinspection unchecked
|
||||
return (Stream<T>) entityClasses.get(entityClass).values().stream();
|
||||
}
|
||||
return Stream.empty();
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private static Optional<Object> getEntityId(final Object entity) {
|
||||
for (Class<?> currentClass = entity.getClass(); currentClass != null; currentClass = currentClass.getSuperclass()) {
|
||||
for (Field field : currentClass.getDeclaredFields()) {
|
||||
if (field.isAnnotationPresent(Id.class)) {
|
||||
field.setAccessible(true);
|
||||
return Optional.ofNullable(field.get(entity));
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("No @Id field found in entity class: " + entity.getClass().getName());
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private static Object setEntityId(final Object entity, final Object id) {
|
||||
for (Class<?> currentClass = entity.getClass(); currentClass != null; currentClass = currentClass.getSuperclass()) {
|
||||
for (Field field : currentClass.getDeclaredFields()) {
|
||||
if (field.isAnnotationPresent(Id.class)) {
|
||||
field.setAccessible(true);
|
||||
field.set(entity, id);
|
||||
return id;
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("No @Id field found in entity class: " + entity.getClass().getName());
|
||||
}
|
||||
}
|
||||
+2
-3
@@ -1,6 +1,5 @@
|
||||
package net.hostsharing.hsadminng.persistence;
|
||||
|
||||
import net.hostsharing.hsadminng.mapper.Array;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
@@ -53,9 +52,9 @@ class EntityManagerWrapperUnitTest {
|
||||
if (type == double.class) return 0.0;
|
||||
if (type == char.class) return '\0';
|
||||
if (type == String.class) return "dummy";
|
||||
if (type == String[].class) return Array.of("dummy");
|
||||
if (type == String[].class) return new String[]{"dummy"};
|
||||
if (type == Class.class) return String.class;
|
||||
if (type == Class[].class) return Array.of(String.class);
|
||||
if (type == Class[].class) return new Class[0];
|
||||
return mock(type);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package net.hostsharing.hsadminng.rbac.context;
|
||||
import net.hostsharing.hsadminng.context.Context;
|
||||
import net.hostsharing.hsadminng.mapper.StandardMapper;
|
||||
import net.hostsharing.hsadminng.mapper.Array;
|
||||
import net.hostsharing.hsadminng.persistence.EntityManagerWrapper;
|
||||
import net.hostsharing.hsadminng.rbac.test.JpaAttempt;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -17,7 +18,7 @@ import jakarta.servlet.http.HttpServletRequest;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@DataJpaTest
|
||||
@ComponentScan(basePackageClasses = { Context.class, JpaAttempt.class, StandardMapper.class })
|
||||
@ComponentScan(basePackageClasses = { Context.class, JpaAttempt.class, EntityManagerWrapper.class, StandardMapper.class })
|
||||
@DirtiesContext
|
||||
class ContextIntegrationTests {
|
||||
|
||||
|
||||
@@ -2,10 +2,10 @@ package net.hostsharing.hsadminng.rbac.role;
|
||||
|
||||
import net.hostsharing.hsadminng.context.Context;
|
||||
import net.hostsharing.hsadminng.mapper.StandardMapper;
|
||||
import net.hostsharing.hsadminng.persistence.EntityManagerWrapper;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
@@ -15,7 +15,6 @@ import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
|
||||
|
||||
import jakarta.persistence.EntityManager;
|
||||
import jakarta.persistence.EntityManagerFactory;
|
||||
import jakarta.persistence.SynchronizationType;
|
||||
import java.util.Map;
|
||||
@@ -43,8 +42,8 @@ class RbacRoleControllerRestTest {
|
||||
@MockBean
|
||||
RbacRoleRepository rbacRoleRepository;
|
||||
|
||||
@Mock
|
||||
EntityManager em;
|
||||
@MockBean
|
||||
EntityManagerWrapper em;
|
||||
|
||||
@MockBean
|
||||
EntityManagerFactory emf;
|
||||
|
||||
+2
-19
@@ -2,10 +2,9 @@ package net.hostsharing.hsadminng.rbac.subject;
|
||||
|
||||
import net.hostsharing.hsadminng.context.Context;
|
||||
import net.hostsharing.hsadminng.mapper.StandardMapper;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import net.hostsharing.hsadminng.persistence.EntityManagerWrapper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
@@ -15,18 +14,12 @@ import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
|
||||
|
||||
import jakarta.persistence.EntityManager;
|
||||
import jakarta.persistence.EntityManagerFactory;
|
||||
import jakarta.persistence.SynchronizationType;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import static net.hostsharing.hsadminng.rbac.test.IsValidUuidMatcher.isUuidValid;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.argThat;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
@@ -44,19 +37,9 @@ class RbacSubjectControllerRestTest {
|
||||
@MockBean
|
||||
RbacSubjectRepository rbacSubjectRepository;
|
||||
|
||||
@Mock
|
||||
EntityManager em;
|
||||
|
||||
@MockBean
|
||||
EntityManagerFactory emf;
|
||||
EntityManagerWrapper em;
|
||||
|
||||
@BeforeEach
|
||||
void init() {
|
||||
when(emf.createEntityManager()).thenReturn(em);
|
||||
when(emf.createEntityManager(any(Map.class))).thenReturn(em);
|
||||
when(emf.createEntityManager(any(SynchronizationType.class))).thenReturn(em);
|
||||
when(emf.createEntityManager(any(SynchronizationType.class), any(Map.class))).thenReturn(em);
|
||||
}
|
||||
|
||||
@Test
|
||||
void createSubjectUsesGivenUuid() throws Exception {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package net.hostsharing.hsadminng.rbac.test;
|
||||
|
||||
import net.hostsharing.hsadminng.rbac.context.ContextBasedTest;
|
||||
import net.hostsharing.hsadminng.rbac.object.BaseEntity;
|
||||
import net.hostsharing.hsadminng.persistence.BaseEntity;
|
||||
import net.hostsharing.hsadminng.rbac.grant.RbacGrantEntity;
|
||||
import net.hostsharing.hsadminng.rbac.grant.RbacGrantRepository;
|
||||
import net.hostsharing.hsadminng.rbac.grant.RbacGrantsDiagramService;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package net.hostsharing.hsadminng.rbac.test;
|
||||
|
||||
import net.hostsharing.hsadminng.rbac.object.BaseEntity;
|
||||
import net.hostsharing.hsadminng.persistence.BaseEntity;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@@ -3,13 +3,13 @@ package net.hostsharing.hsadminng.rbac.test;
|
||||
import lombok.*;
|
||||
import net.hostsharing.hsadminng.errors.DisplayAs;
|
||||
import net.hostsharing.hsadminng.mapper.StandardMapper;
|
||||
import net.hostsharing.hsadminng.persistence.EntityManagerWrapper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import jakarta.persistence.EntityManager;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.validation.ValidationException;
|
||||
import java.util.List;
|
||||
@@ -24,7 +24,7 @@ import static org.mockito.Mockito.when;
|
||||
class MapperUnitTest {
|
||||
|
||||
@Mock
|
||||
EntityManager em;
|
||||
EntityManagerWrapper em;
|
||||
|
||||
@InjectMocks
|
||||
StandardMapper mapper;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package net.hostsharing.hsadminng.rbac.test;
|
||||
|
||||
import net.hostsharing.hsadminng.rbac.object.BaseEntity;
|
||||
import net.hostsharing.hsadminng.persistence.BaseEntity;
|
||||
import net.hostsharing.hsadminng.mapper.EntityPatcher;
|
||||
import org.junit.jupiter.api.Named;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
Reference in New Issue
Block a user