add DomainSetup-HostingAssets for new BookingItem via created-event #111
@ -11,7 +11,7 @@ public class BookingItemCreatedAppEvent extends ApplicationEvent {
|
||||
private BookingItemCreatedEventEntity entity;
|
||||
|
||||
public BookingItemCreatedAppEvent(
|
||||
@NotNull final HsBookingItemController source,
|
||||
@NotNull final Object source,
|
||||
@NotNull final HsBookingItemRealEntity newBookingItem,
|
||||
final String assetJson) {
|
||||
super(source);
|
||||
|
@ -4,6 +4,7 @@ import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.experimental.SuperBuilder;
|
||||
import net.hostsharing.hsadminng.rbac.object.BaseEntity;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
@ -23,10 +24,10 @@ import java.util.UUID;
|
||||
@SuperBuilder(toBuilder = true)
|
||||
@Getter
|
||||
@NoArgsConstructor
|
||||
public class BookingItemCreatedEventEntity {
|
||||
public class BookingItemCreatedEventEntity implements BaseEntity {
|
||||
@Id
|
||||
@Column(name="bookingitemuuid")
|
||||
private UUID id;
|
||||
private UUID uuid;
|
||||
|
||||
@MapsId
|
||||
@ManyToOne(optional = false)
|
||||
|
@ -69,9 +69,8 @@ public class DomainSetupHostingAssetFactory extends HostingAssetFactory {
|
||||
.type(HsHostingAssetType.DOMAIN_SETUP)
|
||||
.identifier(domainName)
|
||||
.caption(asset.getCaption() != null ? asset.getCaption() : domainName)
|
||||
.parentAsset(ref(HsHostingAssetRealEntity.class, asset.getParentAssetUuid()))
|
||||
.alarmContact(ref(HsOfficeContactRealEntity.class, asset.getAlarmContactUuid()))
|
||||
.subHostingAssets(
|
||||
.subHostingAssets( // FIXME: is this even used?
|
||||
standardMapper.mapList(getSubHostingAssetResources(), HsHostingAssetRealEntity.class)
|
||||
)
|
||||
.build();
|
||||
|
@ -29,7 +29,7 @@ public class HsBookingItemCreatedListener implements ApplicationListener<Booking
|
||||
final var asset = jsonMapper.readValue(event.getEntity().getAssetJson(), HsHostingAssetAutoInsertResource.class);
|
||||
final var factory = switch (newBookingItemRealEntity.getType()) {
|
||||
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);
|
||||
};
|
||||
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