add DomainSetup-HostingAssets for new BookingItem via created-event #111
@ -11,7 +11,7 @@ public class BookingItemCreatedAppEvent extends ApplicationEvent {
|
|||||||
private BookingItemCreatedEventEntity entity;
|
private BookingItemCreatedEventEntity entity;
|
||||||
|
|
||||||
public BookingItemCreatedAppEvent(
|
public BookingItemCreatedAppEvent(
|
||||||
@NotNull final HsBookingItemController source,
|
@NotNull final Object source,
|
||||||
@NotNull final HsBookingItemRealEntity newBookingItem,
|
@NotNull final HsBookingItemRealEntity newBookingItem,
|
||||||
final String assetJson) {
|
final String assetJson) {
|
||||||
super(source);
|
super(source);
|
||||||
|
@ -4,6 +4,7 @@ import lombok.Getter;
|
|||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
import lombok.experimental.SuperBuilder;
|
import lombok.experimental.SuperBuilder;
|
||||||
|
import net.hostsharing.hsadminng.rbac.object.BaseEntity;
|
||||||
|
|
||||||
import jakarta.persistence.Column;
|
import jakarta.persistence.Column;
|
||||||
import jakarta.persistence.Entity;
|
import jakarta.persistence.Entity;
|
||||||
@ -23,10 +24,10 @@ import java.util.UUID;
|
|||||||
@SuperBuilder(toBuilder = true)
|
@SuperBuilder(toBuilder = true)
|
||||||
@Getter
|
@Getter
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
public class BookingItemCreatedEventEntity {
|
public class BookingItemCreatedEventEntity implements BaseEntity {
|
||||||
@Id
|
@Id
|
||||||
@Column(name="bookingitemuuid")
|
@Column(name="bookingitemuuid")
|
||||||
private UUID id;
|
private UUID uuid;
|
||||||
|
|
||||||
@MapsId
|
@MapsId
|
||||||
@ManyToOne(optional = false)
|
@ManyToOne(optional = false)
|
||||||
|
@ -69,9 +69,8 @@ public class DomainSetupHostingAssetFactory extends HostingAssetFactory {
|
|||||||
.type(HsHostingAssetType.DOMAIN_SETUP)
|
.type(HsHostingAssetType.DOMAIN_SETUP)
|
||||||
.identifier(domainName)
|
.identifier(domainName)
|
||||||
.caption(asset.getCaption() != null ? asset.getCaption() : domainName)
|
.caption(asset.getCaption() != null ? asset.getCaption() : domainName)
|
||||||
.parentAsset(ref(HsHostingAssetRealEntity.class, asset.getParentAssetUuid()))
|
|
||||||
.alarmContact(ref(HsOfficeContactRealEntity.class, asset.getAlarmContactUuid()))
|
.alarmContact(ref(HsOfficeContactRealEntity.class, asset.getAlarmContactUuid()))
|
||||||
.subHostingAssets(
|
.subHostingAssets( // FIXME: is this even used?
|
||||||
standardMapper.mapList(getSubHostingAssetResources(), HsHostingAssetRealEntity.class)
|
standardMapper.mapList(getSubHostingAssetResources(), HsHostingAssetRealEntity.class)
|
||||||
)
|
)
|
||||||
.build();
|
.build();
|
||||||
|
@ -29,7 +29,7 @@ public class HsBookingItemCreatedListener implements ApplicationListener<Booking
|
|||||||
final var asset = jsonMapper.readValue(event.getEntity().getAssetJson(), HsHostingAssetAutoInsertResource.class);
|
final var asset = jsonMapper.readValue(event.getEntity().getAssetJson(), HsHostingAssetAutoInsertResource.class);
|
||||||
final var factory = switch (newBookingItemRealEntity.getType()) {
|
final var factory = switch (newBookingItemRealEntity.getType()) {
|
||||||
case PRIVATE_CLOUD, CLOUD_SERVER, MANAGED_SERVER -> null; // for now, no automatic HostingAsset possible
|
case PRIVATE_CLOUD, CLOUD_SERVER, MANAGED_SERVER -> null; // for now, no automatic HostingAsset possible
|
||||||
case MANAGED_WEBSPACE -> null; // TODO.impl: implement ManagedWebspace HostingAsset creation, where possible
|
case MANAGED_WEBSPACE -> null; // FIXME: implement ManagedWebspace HostingAsset creation, where possible
|
||||||
case DOMAIN_SETUP -> new DomainSetupHostingAssetFactory(emw, newBookingItemRealEntity, asset, standardMapper);
|
case DOMAIN_SETUP -> new DomainSetupHostingAssetFactory(emw, newBookingItemRealEntity, asset, standardMapper);
|
||||||
};
|
};
|
||||||
if (factory != null) {
|
if (factory != null) {
|
||||||
|
@ -0,0 +1,137 @@
|
|||||||
|
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.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;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class HsBookingItemCreatedListenerUnitTest {
|
||||||
|
|
||||||
|
private final HsHostingAssetRealEntity managedWebspaceHostingAsset = HsHostingAssetRealEntity.builder()
|
||||||
|
.uuid(UUID.randomUUID())
|
||||||
|
.type(MANAGED_WEBSPACE)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
private final HsHostingAssetRealEntity unixUserHostingAsset = HsHostingAssetRealEntity.builder()
|
||||||
|
.uuid(UUID.randomUUID())
|
||||||
|
.type(UNIX_USER)
|
||||||
|
.parentAsset(managedWebspaceHostingAsset)
|
||||||
|
.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();
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
private HsBookingItemCreatedListener listener;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void initMocks() {
|
||||||
|
emwFake.persist(managedWebspaceHostingAsset);
|
||||||
|
emwFake.persist(unixUserHostingAsset);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void doesNotPersistEventEntityWithoutValidationErrors() {
|
||||||
|
// given
|
||||||
|
final var givenBookingItem = HsBookingItemRealEntity.builder()
|
||||||
|
.type(HsBookingItemType.DOMAIN_SETUP)
|
||||||
|
.resources(Map.ofEntries(
|
||||||
|
entry("domainName", "example.org"),
|
||||||
|
entry("verificationCode", "just-a-fake-verification-code")
|
||||||
|
))
|
||||||
|
.build();
|
||||||
|
final var givenAssetJson = """
|
||||||
|
{
|
||||||
|
"identifier": "example.org", // also as default for all subAssets
|
||||||
|
"subHostingAssets": [
|
||||||
|
{
|
||||||
|
"type": "DOMAIN_HTTP_SETUP",
|
||||||
|
"assignedToAssetUuid": "{unixUserHostingAssetUuid}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
.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).findAny().isEmpty())
|
||||||
|
.as("the event should not have been persisted, but got persisted").isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void persistsEventEntityIfHostingAssetVerificationFails() {
|
||||||
|
// given
|
||||||
|
final var givenBookingItem = HsBookingItemRealEntity.builder()
|
||||||
|
.type(HsBookingItemType.DOMAIN_SETUP)
|
||||||
|
.resources(Map.ofEntries(
|
||||||
|
entry("domainName", "example.org")
|
||||||
|
))
|
||||||
|
.build();
|
||||||
|
final var givenAssetJson = """
|
||||||
|
{
|
||||||
|
"identifier": "example.org", // also as default for all subAssets
|
||||||
|
"subHostingAssets": [
|
||||||
|
{
|
||||||
|
"type": "DOMAIN_HTTP_SETUP",
|
||||||
|
"assignedToAssetUuid": "{unixUserHostingAssetUuid}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
.replace("{unixUserHostingAssetUuid}", unixUserHostingAsset.getUuid().toString());
|
||||||
|
Dns.fakeResultForDomain("example.org", Dns.Result.fromRecords()); // without valid verificationCode
|
||||||
|
|
||||||
|
// when
|
||||||
|
listener.onApplicationEvent(
|
||||||
|
new BookingItemCreatedAppEvent(this, givenBookingItem, givenAssetJson)
|
||||||
|
);
|
||||||
|
|
||||||
|
// then
|
||||||
|
emwFake.stream(BookingItemCreatedEventEntity.class)
|
||||||
|
.reduce(EntityManagerWrapperFake::toSingleElement)
|
||||||
|
.map(eventEntity -> {
|
||||||
|
assertThat(eventEntity.getBookingItem()).isSameAs(givenBookingItem);
|
||||||
|
assertThat(eventEntity.getAssetJson()).isEqualTo(givenAssetJson);
|
||||||
|
assertThat(eventEntity.getStatusMessage()).isEqualTo(
|
||||||
|
"[[DNS] no TXT record 'Hostsharing-domain-setup-verification-code=null' found for domain name 'example.org' (nor in its super-domain)]");
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,95 @@
|
|||||||
|
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<>();
|
||||||
|
|
||||||
|
public static <T> T toSingleElement(T last, T next) {
|
||||||
|
throw new AssertionError("only a single entity expected");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean contains(final Object entity) {
|
||||||
|
final var id = getEntityId(entity);
|
||||||
|
return find(entity.getClass(), id) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> T find(final Class<T> entityClass, final Object primaryKey) {
|
||||||
|
final var self = this;
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user