1
0

create relation with holder- and contact-data, and search for contact emailAddress + relation mark (#136)

Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: https://dev.hostsharing.net/hostsharing/hs.hsadmin.ng/pulls/136
Reviewed-by: Marc Sandlus <marc.sandlus@hostsharing.net>
This commit is contained in:
Michael Hoennig
2024-12-13 14:09:01 +01:00
parent 19fac6b5e1
commit 20fa27194b
18 changed files with 354 additions and 119 deletions

View File

@@ -1,5 +1,6 @@
package net.hostsharing.hsadminng.errors;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import jakarta.validation.ValidationException;
@@ -9,23 +10,53 @@ import static org.assertj.core.api.Assertions.catchThrowable;
class ValidateUnitTest {
@Test
void shouldFailValidationIfBothParametersAreNotNull() {
final var throwable = catchThrowable(() ->
Validate.validate("var1, var2").atMaxOneNonNull("val1", "val2")
);
assertThat(throwable).isInstanceOf(ValidationException.class)
.hasMessage("Exactly one of (var1, var2) must be non-null, but are (val1, val2)");
@Nested
class AtMaxOne {
@Test
void shouldFailValidationIfBothParametersAreNotNull() {
final var throwable = catchThrowable(() ->
Validate.validate("var1, var2").atMaxOne("val1", "val2")
);
assertThat(throwable).isInstanceOf(ValidationException.class)
.hasMessage("At maximum one of (var1, var2) must be non-null, but are (val1, val2)");
}
@Test
void shouldNotFailValidationIfBothParametersAreNull() {
Validate.validate("var1, var2").atMaxOne(null, null);
}
@Test
void shouldNotFailValidationIfExactlyOneParameterIsNonNull() {
Validate.validate("var1, var2").atMaxOne("val1", null);
Validate.validate("var1, var2").atMaxOne(null, "val2");
}
}
@Test
void shouldNotFailValidationIfBothParametersAreull() {
Validate.validate("var1, var2").atMaxOneNonNull(null, null);
}
@Nested
class ExactlyOne {
@Test
void shouldFailValidationIfBothParametersAreNotNull() {
final var throwable = catchThrowable(() ->
Validate.validate("var1, var2").exactlyOne("val1", "val2")
);
assertThat(throwable).isInstanceOf(ValidationException.class)
.hasMessage("Exactly one of (var1, var2) must be non-null, but are (val1, val2)");
}
@Test
void shouldNotFailValidationIfExactlyOneParameterIsNonNull() {
Validate.validate("var1, var2").atMaxOneNonNull("val1", null);
Validate.validate("var1, var2").atMaxOneNonNull(null, "val2");
@Test
void shouldFailValidationIfBothParametersAreNull() {
final var throwable = catchThrowable(() ->
Validate.validate("var1, var2").exactlyOne(null, null)
);
assertThat(throwable).isInstanceOf(ValidationException.class)
.hasMessage("Exactly one of (var1, var2) must be non-null, but are (null, null)");
}
@Test
void shouldNotFailValidationIfExactlyOneParameterIsNonNull() {
Validate.validate("var1, var2").exactlyOne("val1", null);
Validate.validate("var1, var2").exactlyOne(null, "val2");
}
}
}

View File

@@ -127,7 +127,7 @@ class HsOfficeContactRbacRepositoryIntegrationTest extends ContextBasedTestWithC
}
@Nested
class FindAllContacts {
class FindContacts {
@Test
public void globalAdmin_withoutAssumedRole_canViewAllContacts() {
@@ -184,6 +184,22 @@ class HsOfficeContactRbacRepositoryIntegrationTest extends ContextBasedTestWithC
}
}
@Nested
class FindByEmailAddress {
@Test
public void globalAdmin_withoutAssumedRole_canFindContactsByEmailAddress() {
// given
context("superuser-alex@hostsharing.net", null);
// when
final var result = contactRepo.findContactByEmailAddress("%@secondcontact.example.com");
// then
exactlyTheseContactsAreReturned(result, "second contact");
}
}
@Nested
class DeleteByUuid {

View File

@@ -223,7 +223,7 @@ class HsOfficeRelationControllerAcceptanceTest extends ContextBasedTestWithClean
class AddRelation {
@Test
void globalAdmin_withoutAssumedRole_canAddRelation() {
void globalAdmin_withoutAssumedRole_canAddRelationWithHolderUuidAndContactUuid() {
context.define("superuser-alex@hostsharing.net");
final var givenAnchorPerson = personRepo.findPersonByOptionalNameLike("Third").get(0);
@@ -261,7 +261,62 @@ class HsOfficeRelationControllerAcceptanceTest extends ContextBasedTestWithClean
.body("holder.givenName", is("Paul"))
.body("contact.caption", is("second contact"))
.header("Location", startsWith("http://localhost"))
.extract().header("Location"); // @formatter:on
.extract().header("Location"); // @formatter:on
// finally, the new relation can be accessed under the generated UUID
final var newSubjectUuid = toCleanup(HsOfficeRelationRealEntity.class, UUID.fromString(
location.substring(location.lastIndexOf('/') + 1)));
assertThat(newSubjectUuid).isNotNull();
}
@Test
void globalAdmin_withoutAssumedRole_canAddRelationWithHolderAndContactData() {
context.define("superuser-alex@hostsharing.net");
final var givenAnchorPerson = personRepo.findPersonByOptionalNameLike("Third").get(0);
final var location = RestAssured // @formatter:off
.given()
.header("current-subject", "superuser-alex@hostsharing.net")
.contentType(ContentType.JSON)
.body("""
{
"type": "%s",
"mark": "%s",
"anchor.uuid": "%s",
"holder": {
"personType": "NATURAL_PERSON",
"familyName": "Person",
"givenName": "Temp"
},
"contact": {
"caption": "Temp Contact",
"emailAddresses": {
"main": "test@example.org"
}
}
}
""".formatted(
HsOfficeRelationTypeResource.SUBSCRIBER,
"operations-discuss",
givenAnchorPerson.getUuid()
)
)
.port(port)
.when()
.post("http://localhost/api/hs/office/relations")
.then().log().all().assertThat()
.statusCode(201)
.contentType(ContentType.JSON)
.body("uuid", isUuidValid())
.body("type", is("SUBSCRIBER"))
.body("mark", is("operations-discuss"))
.body("anchor.tradeName", is("Third OHG"))
.body("holder.givenName", is("Temp"))
.body("holder.familyName", is("Person"))
.body("contact.caption", is("Temp Contact"))
.header("Location", startsWith("http://localhost"))
.extract().header("Location"); // @formatter:on
// finally, the new relation can be accessed under the generated UUID
final var newSubjectUuid = toCleanup(HsOfficeRelationRealEntity.class, UUID.fromString(
@@ -277,7 +332,7 @@ class HsOfficeRelationControllerAcceptanceTest extends ContextBasedTestWithClean
final var givenHolderPerson = personRepo.findPersonByOptionalNameLike("Smith").get(0);
final var givenContact = contactrealRepo.findContactByOptionalCaptionLike("fourth").get(0);
final var location = RestAssured // @formatter:off
RestAssured // @formatter:off
.given()
.header("current-subject", "superuser-alex@hostsharing.net")
.contentType(ContentType.JSON)

View File

@@ -193,7 +193,7 @@ class HsOfficeRelationRepositoryIntegrationTest extends ContextBasedTestWithClea
.findFirst().orElseThrow();
// when:
final var result = relationRbacRepo.findRelationRelatedToPersonUuidRelationTypePersonAndContactData(person.getUuid(), null, null, null);
final var result = relationRbacRepo.findRelationRelatedToPersonUuidRelationTypeMarkPersonAndContactData(person.getUuid(), null, null, null, null);
// then:
exactlyTheseRelationsAreReturned(

View File

@@ -31,7 +31,8 @@ import net.hostsharing.hsadminng.hs.office.scenarios.partner.CreatePartner;
import net.hostsharing.hsadminng.hs.office.scenarios.partner.DeletePartner;
import net.hostsharing.hsadminng.hs.office.scenarios.person.ShouldUpdatePersonData;
import net.hostsharing.hsadminng.hs.office.scenarios.subscription.RemoveOperationsContactFromPartner;
import net.hostsharing.hsadminng.hs.office.scenarios.subscription.SubscribeToMailinglist;
import net.hostsharing.hsadminng.hs.office.scenarios.subscription.SubscribeExistingPersonAndContactToMailinglist;
import net.hostsharing.hsadminng.hs.office.scenarios.subscription.SubscribeNewPersonAndContactToMailinglist;
import net.hostsharing.hsadminng.hs.office.scenarios.subscription.UnsubscribeFromMailinglist;
import net.hostsharing.hsadminng.hs.scenarios.Produces;
import net.hostsharing.hsadminng.hs.scenarios.Requires;
@@ -564,7 +565,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
@Requires("Person: Test AG")
@Produces("Subscription: Michael Miller to operations-announce")
void shouldSubscribeNewPersonAndContactToMailinglist() {
new SubscribeToMailinglist(scenarioTest)
new SubscribeNewPersonAndContactToMailinglist(scenarioTest)
// TODO.spec: do we need the personType? or is an operational contact always a natural person? what about distribution lists?
.given("partnerPersonTradeName", "Test AG")
.given("subscriberFamilyName", "Miller")
@@ -578,7 +579,22 @@ class HsOfficeScenarioTests extends ScenarioTest {
@Test
@Order(5001)
@Requires("Subscription: Michael Miller to operations-announce")
void shouldUnsubscribeNewPersonAndContactToMailinglist() {
@Produces("Subscription: Michael Miller to operations-discussion")
void shouldSubscribeExistingPersonAndContactToMailinglist() {
new SubscribeExistingPersonAndContactToMailinglist(scenarioTest)
.given("partnerPersonTradeName", "Test AG")
.given("subscriberFamilyName", "Miller")
.given("subscriberGivenName", "Michael")
.given("subscriberEMailAddress", "michael.miller@example.org")
.given("mailingList", "operations-discussion")
.doRun()
.keep();
}
@Test
@Order(5002)
@Requires("Subscription: Michael Miller to operations-announce")
void shouldUnsubscribePersonAndContactFromMailinglist() {
new UnsubscribeFromMailinglist(scenarioTest)
.given("mailingList", "operations-announce")
.given("subscriberEMailAddress", "michael.miller@example.org")

View File

@@ -0,0 +1,54 @@
package net.hostsharing.hsadminng.hs.office.scenarios.subscription;
import io.restassured.http.ContentType;
import net.hostsharing.hsadminng.hs.scenarios.ScenarioTest;
import net.hostsharing.hsadminng.hs.scenarios.UseCase;
import org.springframework.http.HttpStatus;
import static io.restassured.http.ContentType.JSON;
import static org.springframework.http.HttpStatus.CREATED;
import static org.springframework.http.HttpStatus.OK;
public class SubscribeExistingPersonAndContactToMailinglist extends UseCase<SubscribeExistingPersonAndContactToMailinglist> {
public SubscribeExistingPersonAndContactToMailinglist(final ScenarioTest testSuite) {
super(testSuite);
}
@Override
protected HttpResponse run() {
obtain("Person: %{partnerPersonTradeName}", () ->
httpGet("/api/hs/office/persons?name=" + uriEncoded("%{partnerPersonTradeName}"))
.expecting(OK).expecting(JSON),
response -> response.expectArrayElements(1).getFromBody("[0].uuid"),
"In production, data this query could result in multiple outputs. In that case, you have to find out which is the right one."
);
obtain(
"Person: %{subscriberGivenName} %{subscriberFamilyName}", () ->
httpGet("/api/hs/office/persons?name=%{subscriberFamilyName}")
.expecting(HttpStatus.OK).expecting(ContentType.JSON),
response -> response.expectArrayElements(1).getFromBody("[0].uuid"),
"In real scenarios there are most likely multiple results and you have to choose the right one."
);
obtain("Contact: %{subscriberEMailAddress}", () ->
httpGet("/api/hs/office/contacts?emailAddress=%{subscriberEMailAddress}")
.expecting(HttpStatus.OK).expecting(ContentType.JSON),
response -> response.expectArrayElements(1).getFromBody("[0].uuid"),
"In real scenarios there are most likely multiple results and you have to choose the right one."
);
return httpPost("/api/hs/office/relations", usingJsonBody("""
{
"type": "SUBSCRIBER",
"mark": ${mailingList},
"anchor.uuid": ${Person: %{partnerPersonTradeName}},
"holder.uuid": ${Person: %{subscriberGivenName} %{subscriberFamilyName}},
"contact.uuid": ${Contact: %{subscriberEMailAddress}}
}
"""))
.expecting(CREATED).expecting(JSON);
}
}

View File

@@ -0,0 +1,46 @@
package net.hostsharing.hsadminng.hs.office.scenarios.subscription;
import net.hostsharing.hsadminng.hs.scenarios.UseCase;
import net.hostsharing.hsadminng.hs.scenarios.ScenarioTest;
import static io.restassured.http.ContentType.JSON;
import static org.springframework.http.HttpStatus.CREATED;
import static org.springframework.http.HttpStatus.OK;
public class SubscribeNewPersonAndContactToMailinglist extends UseCase<SubscribeNewPersonAndContactToMailinglist> {
public SubscribeNewPersonAndContactToMailinglist(final ScenarioTest testSuite) {
super(testSuite);
}
@Override
protected HttpResponse run() {
obtain("Person: %{partnerPersonTradeName}", () ->
httpGet("/api/hs/office/persons?name=" + uriEncoded("%{partnerPersonTradeName}"))
.expecting(OK).expecting(JSON),
response -> response.expectArrayElements(1).getFromBody("[0].uuid"),
"In production, data this query could result in multiple outputs. In that case, you have to find out which is the right one."
);
return httpPost("/api/hs/office/relations", usingJsonBody("""
{
"type": "SUBSCRIBER",
"mark": ${mailingList},
"anchor.uuid": ${Person: %{partnerPersonTradeName}},
"holder": {
"personType": "NATURAL_PERSON",
"familyName": ${subscriberFamilyName},
"givenName": ${subscriberGivenName}
},
"contact": {
"caption": "%{subscriberGivenName} %{subscriberFamilyName}",
"emailAddresses": {
"main": ${subscriberEMailAddress}
}
}
}
"""))
.expecting(CREATED).expecting(JSON);
}
}

View File

@@ -1,62 +0,0 @@
package net.hostsharing.hsadminng.hs.office.scenarios.subscription;
import io.restassured.http.ContentType;
import net.hostsharing.hsadminng.hs.scenarios.UseCase;
import net.hostsharing.hsadminng.hs.scenarios.ScenarioTest;
import org.springframework.http.HttpStatus;
import static io.restassured.http.ContentType.JSON;
import static org.springframework.http.HttpStatus.CREATED;
import static org.springframework.http.HttpStatus.OK;
public class SubscribeToMailinglist extends UseCase<SubscribeToMailinglist> {
public SubscribeToMailinglist(final ScenarioTest testSuite) {
super(testSuite);
}
@Override
protected HttpResponse run() {
obtain("Person: %{partnerPersonTradeName}", () ->
httpGet("/api/hs/office/persons?name=" + uriEncoded("%{partnerPersonTradeName}"))
.expecting(OK).expecting(JSON),
response -> response.expectArrayElements(1).getFromBody("[0].uuid"),
"In production, data this query could result in multiple outputs. In that case, you have to find out which is the right one."
);
obtain("Person: %{subscriberGivenName} %{subscriberFamilyName}", () ->
httpPost("/api/hs/office/persons", usingJsonBody("""
{
"personType": "NATURAL_PERSON",
"familyName": ${subscriberFamilyName},
"givenName": ${subscriberGivenName}
}
"""))
.expecting(HttpStatus.CREATED).expecting(ContentType.JSON)
);
obtain("Contact: %{subscriberGivenName} %{subscriberFamilyName}", () ->
httpPost("/api/hs/office/contacts", usingJsonBody("""
{
"caption": "%{subscriberGivenName} %{subscriberFamilyName}",
"emailAddresses": {
"main": ${subscriberEMailAddress}
}
}
"""))
.expecting(CREATED).expecting(JSON)
);
return httpPost("/api/hs/office/relations", usingJsonBody("""
{
"type": "SUBSCRIBER",
"mark": ${mailingList},
"anchor.uuid": ${Person: %{partnerPersonTradeName}},
"holder.uuid": ${Person: %{subscriberGivenName} %{subscriberFamilyName}},
"contact.uuid": ${Contact: %{subscriberGivenName} %{subscriberFamilyName}}
}
"""))
.expecting(CREATED).expecting(JSON);
}
}