application-event for booking-item-created with domain-setup-example #110

Merged
hsh-michaelhoennig merged 4 commits from application-event-for-booking-item-created into master 2024-09-27 11:19:01 +02:00
3 changed files with 106 additions and 18 deletions
Showing only changes of commit 4c0b4a2c1e - Show all commits

View File

@ -29,12 +29,17 @@ public class HsBookingItemCreatedListener implements ApplicationListener<Booking
case DOMAIN_SETUP -> createDomainSetupHostingAsset(newBookingItemRealEntity); case DOMAIN_SETUP -> createDomainSetupHostingAsset(newBookingItemRealEntity);
}; };
if (newHostingAsset != null) { if (newHostingAsset != null) {
try {
new HostingAssetEntitySaveProcessor(emw, newHostingAsset) new HostingAssetEntitySaveProcessor(emw, newHostingAsset)
.preprocessEntity() .preprocessEntity()
.validateEntity() .validateEntity()
.prepareForSave() .prepareForSave()
.save() .save()
.validateContext(); .validateContext();
} catch (final Exception e) {
// TODO.impl: store status in a separate field, maybe enum+message
newBookingItemRealEntity.getResources().put("status", e.getMessage());
}
} }
} }

View File

@ -4,9 +4,11 @@ import io.hypersistence.utils.hibernate.type.range.Range;
import io.restassured.RestAssured; import io.restassured.RestAssured;
import io.restassured.http.ContentType; import io.restassured.http.ContentType;
import net.hostsharing.hsadminng.HsadminNgApplication; import net.hostsharing.hsadminng.HsadminNgApplication;
import net.hostsharing.hsadminng.hs.booking.debitor.HsBookingDebitorRepository;
import net.hostsharing.hsadminng.hs.booking.project.HsBookingProjectRealRepository; 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.hs.hosting.asset.validators.Dns;
import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorRepository;
import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup; import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup;
import net.hostsharing.hsadminng.rbac.test.JpaAttempt; import net.hostsharing.hsadminng.rbac.test.JpaAttempt;
import org.junit.jupiter.api.ClassOrderer; import org.junit.jupiter.api.ClassOrderer;
@ -51,7 +53,10 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
HsBookingProjectRealRepository realProjectRepo; HsBookingProjectRealRepository realProjectRepo;
@Autowired @Autowired
HsOfficeDebitorRepository debitorRepo; HsBookingDebitorRepository debitorRepo;
@Autowired
HsHostingAssetRealRepository realHostingAssetRepo;
@Autowired @Autowired
JpaAttempt jpaAttempt; JpaAttempt jpaAttempt;
@ -65,7 +70,7 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
// given // given
context("superuser-alex@hostsharing.net"); context("superuser-alex@hostsharing.net");
final var givenProject = debitorRepo.findDebitorByDebitorNumber(1000111).stream() final var givenProject = debitorRepo.findByDebitorNumber(1000111).stream()
.map(d -> realProjectRepo.findAllByDebitorUuid(d.getUuid())) .map(d -> realProjectRepo.findAllByDebitorUuid(d.getUuid()))
.flatMap(List::stream) .flatMap(List::stream)
.findFirst() .findFirst()
@ -133,7 +138,7 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
void globalAdmin_canAddBookingItem() { void globalAdmin_canAddBookingItem() {
context.define("superuser-alex@hostsharing.net"); context.define("superuser-alex@hostsharing.net");
final var givenProject = debitorRepo.findDebitorByDebitorNumber(1000111).stream() final var givenProject = debitorRepo.findByDebitorNumber(1000111).stream()
.map(d -> realProjectRepo.findAllByDebitorUuid(d.getUuid())) .map(d -> realProjectRepo.findAllByDebitorUuid(d.getUuid()))
.flatMap(List::stream) .flatMap(List::stream)
.findFirst() .findFirst()
@ -183,10 +188,10 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
} }
@Test @Test
void globalAdmin_canAddBookingItemWithHostingAsset() { void projectAgent_canAddBookingItemWithHostingAsset() {
context.define("superuser-alex@hostsharing.net"); context.define("superuser-alex@hostsharing.net", "hs_booking.project#D-1000111-D-1000111defaultproject:AGENT");
final var givenProject = debitorRepo.findDebitorByDebitorNumber(1000111).stream() final var givenProject = debitorRepo.findByDebitorNumber(1000111).stream()
.map(d -> realProjectRepo.findAllByDebitorUuid(d.getUuid())) .map(d -> realProjectRepo.findAllByDebitorUuid(d.getUuid()))
.flatMap(List::stream) .flatMap(List::stream)
.findFirst() .findFirst()
@ -234,10 +239,80 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
.header("Location", matchesRegex("http://localhost:[1-9][0-9]*/api/hs/booking/items/[^/]*")) .header("Location", matchesRegex("http://localhost:[1-9][0-9]*/api/hs/booking/items/[^/]*"))
.extract().header("Location"); // @formatter:on .extract().header("Location"); // @formatter:on
// finally, the new bookingItem can be accessed under the generated UUID // then, the new BookingItem can be accessed under the generated UUID
final var newSubjectUuid = UUID.fromString( final var newBookingItem = fetchRealBookingItemFromURI(location);
location.substring(location.lastIndexOf('/') + 1)); assertThat(newBookingItem)
assertThat(newSubjectUuid).isNotNull(); .extracting(bi -> bi.getDirectValue("domainName", String.class))
.isEqualTo("example.org");
// and the related HostingAsset also got created
assertThat(realHostingAssetRepo.findByIdentifier("example.org")).isNotEmpty()
.map(HsHostingAsset::getBookingItem)
.contains(newBookingItem);
}
@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();
Dns.fakeResultForDomain("example.org", Dns.Result.fromRecords()); // without valid verificationCode
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"
}
}
"""
.replace("{projectUuid}", givenProject.getUuid().toString())
)
.port(port)
.when()
.post("http://localhost/api/hs/booking/items")
.then().log().all().assertThat()
.statusCode(201)
.contentType(ContentType.JSON)
.body("", lenientlyEquals("""
{
"type": "DOMAIN_SETUP",
"caption": "some new domain-setup booking",
"validFrom": "{today}",
"validTo": null,
"resources": { "domainName": "example.org", "targetUnixUser": "fir01-web" }
}
"""
.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(bi -> bi.getDirectValue("domainName", String.class))
.isEqualTo("example.org");
assertThat(newBookingItem)
.extracting(bi -> bi.getDirectValue("status", String.class))
.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
assertThat(realHostingAssetRepo.findByIdentifier("example.org")).isEmpty();
} }
} }
@ -464,4 +539,13 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
private Map.Entry<String, Object> resource(final String key, final Object value) { private Map.Entry<String, Object> resource(final String key, final Object value) {
return entry(key, value); return entry(key, value);
} }
private HsBookingItemRealEntity fetchRealBookingItemFromURI(final String location) {
final var newBookingItemUuid = UUID.fromString(
location.substring(location.lastIndexOf('/') + 1));
assertThat(newBookingItemUuid).isNotNull();
final var optional = realBookingItemRepo.findByUuid(newBookingItemUuid);
assertThat(optional).isNotEmpty();
return optional.get();
}
} }

View File

@ -3,7 +3,6 @@ package net.hostsharing.hsadminng.hs.booking.item.validators;
import net.hostsharing.hsadminng.hs.booking.debitor.HsBookingDebitorEntity; import net.hostsharing.hsadminng.hs.booking.debitor.HsBookingDebitorEntity;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemRealEntity; import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemRealEntity;
import net.hostsharing.hsadminng.hs.booking.project.HsBookingProjectRealEntity; import net.hostsharing.hsadminng.hs.booking.project.HsBookingProjectRealEntity;
import net.hostsharing.hsadminng.hs.hosting.asset.validators.HostingAssetEntityValidatorRegistry;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource; import org.junit.jupiter.params.provider.ValueSource;