add DomainSetup-HostingAssets for new BookingItem via created-event #111

Merged
hsh-michaelhoennig merged 31 commits from add-hsoting-asset-to-booking-item-resource-and-created-event into master 2024-10-08 11:48:35 +02:00
6 changed files with 238 additions and 6 deletions
Showing only changes of commit 7e177adff3 - Show all commits

View File

@ -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);

View File

@ -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)

View File

@ -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();

View File

@ -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) {

View File

@ -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;
});
}
}

View File

@ -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());
}
}