implement holder and contact data in HTTP POST to relations
This commit is contained in:
parent
15900d83e4
commit
03f208a27a
@ -13,8 +13,16 @@ public class Validate {
|
||||
return new Validate(variableNames);
|
||||
}
|
||||
|
||||
public final void atMaxOneNonNull(final Object var1, final Object var2) {
|
||||
public final void atMaxOne(final Object var1, final Object var2) {
|
||||
if (var1 != null && var2 != null) {
|
||||
throw new ValidationException(
|
||||
"At maximum one of (" + variableNames + ") must be non-null, " +
|
||||
"but are (" + var1 + ", " + var2 + ")");
|
||||
}
|
||||
}
|
||||
|
||||
public final void exactlyOne(final Object var1, final Object var2) {
|
||||
if ((var1 != null) == (var2 != null)) {
|
||||
throw new ValidationException(
|
||||
"Exactly one of (" + variableNames + ") must be non-null, " +
|
||||
"but are (" + var1 + ", " + var2 + ")");
|
||||
|
@ -43,7 +43,7 @@ public class HsOfficeMembershipController implements HsOfficeMembershipsApi {
|
||||
final String partnerNumber) {
|
||||
context.define(currentSubject, assumedRoles);
|
||||
|
||||
validate("partnerUuid, partnerNumber").atMaxOneNonNull(partnerUuid, partnerNumber);
|
||||
validate("partnerUuid, partnerNumber").atMaxOne(partnerUuid, partnerNumber);
|
||||
|
||||
final var entities = partnerNumber != null
|
||||
? membershipRepo.findMembershipsByPartnerNumber(
|
||||
|
@ -2,9 +2,12 @@ package net.hostsharing.hsadminng.hs.office.relation;
|
||||
|
||||
import io.micrometer.core.annotation.Timed;
|
||||
import net.hostsharing.hsadminng.context.Context;
|
||||
import net.hostsharing.hsadminng.errors.Validate;
|
||||
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRealEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRealRepository;
|
||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficeRelationsApi;
|
||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.*;
|
||||
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRealEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRealRepository;
|
||||
import net.hostsharing.hsadminng.mapper.StandardMapper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@ -20,6 +23,7 @@ import java.util.NoSuchElementException;
|
||||
import java.util.UUID;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import static net.hostsharing.hsadminng.mapper.KeyValueMap.from;
|
||||
|
||||
@RestController
|
||||
public class HsOfficeRelationController implements HsOfficeRelationsApi {
|
||||
@ -31,10 +35,10 @@ public class HsOfficeRelationController implements HsOfficeRelationsApi {
|
||||
private StandardMapper mapper;
|
||||
|
||||
@Autowired
|
||||
private HsOfficeRelationRbacRepository relationRbacRepo;
|
||||
private HsOfficeRelationRbacRepository rbacRelationRepo;
|
||||
|
||||
@Autowired
|
||||
private HsOfficePersonRealRepository personRepo;
|
||||
private HsOfficePersonRealRepository realPersonRepo;
|
||||
|
||||
@Autowired
|
||||
private HsOfficeContactRealRepository realContactRepo;
|
||||
@ -55,7 +59,7 @@ public class HsOfficeRelationController implements HsOfficeRelationsApi {
|
||||
context.define(currentSubject, assumedRoles);
|
||||
|
||||
final List<HsOfficeRelationRbacEntity> entities =
|
||||
relationRbacRepo.findRelationRelatedToPersonUuidRelationTypePersonAndContactData(
|
||||
rbacRelationRepo.findRelationRelatedToPersonUuidRelationTypePersonAndContactData(
|
||||
personUuid,
|
||||
relationType == null ? null : HsOfficeRelationType.valueOf(relationType.name()),
|
||||
personData, contactData);
|
||||
@ -78,17 +82,34 @@ public class HsOfficeRelationController implements HsOfficeRelationsApi {
|
||||
final var entityToSave = new HsOfficeRelationRbacEntity();
|
||||
entityToSave.setType(HsOfficeRelationType.valueOf(body.getType()));
|
||||
entityToSave.setMark(body.getMark());
|
||||
entityToSave.setAnchor(personRepo.findByUuid(body.getAnchorUuid()).orElseThrow(
|
||||
|
||||
entityToSave.setAnchor(realPersonRepo.findByUuid(body.getAnchorUuid()).orElseThrow(
|
||||
() -> new NoSuchElementException("cannot find Person by anchorUuid: " + body.getAnchorUuid())
|
||||
));
|
||||
entityToSave.setHolder(personRepo.findByUuid(body.getHolderUuid()).orElseThrow(
|
||||
|
||||
Validate.validate("anchor, anchor.uuid").exactlyOne(body.getHolder(), body.getHolderUuid());
|
||||
if ( body.getHolderUuid() != null) {
|
||||
entityToSave.setHolder(realPersonRepo.findByUuid(body.getHolderUuid()).orElseThrow(
|
||||
() -> new NoSuchElementException("cannot find Person by holderUuid: " + body.getHolderUuid())
|
||||
));
|
||||
} else {
|
||||
entityToSave.setHolder(realPersonRepo.save(
|
||||
mapper.map(body.getHolder(), HsOfficePersonRealEntity.class)
|
||||
) );
|
||||
}
|
||||
|
||||
Validate.validate("contact, contact.uuid").exactlyOne(body.getContact(), body.getContactUuid());
|
||||
if ( body.getContactUuid() != null) {
|
||||
entityToSave.setContact(realContactRepo.findByUuid(body.getContactUuid()).orElseThrow(
|
||||
() -> new NoSuchElementException("cannot find Contact by contactUuid: " + body.getContactUuid())
|
||||
));
|
||||
} else {
|
||||
entityToSave.setContact(realContactRepo.save(
|
||||
mapper.map(body.getContact(), HsOfficeContactRealEntity.class, CONTACT_RESOURCE_TO_ENTITY_POSTMAPPER)
|
||||
) );
|
||||
}
|
||||
|
||||
final var saved = relationRbacRepo.save(entityToSave);
|
||||
final var saved = rbacRelationRepo.save(entityToSave);
|
||||
|
||||
final var uri =
|
||||
MvcUriComponentsBuilder.fromController(getClass())
|
||||
@ -110,7 +131,7 @@ public class HsOfficeRelationController implements HsOfficeRelationsApi {
|
||||
|
||||
context.define(currentSubject, assumedRoles);
|
||||
|
||||
final var result = relationRbacRepo.findByUuid(relationUuid);
|
||||
final var result = rbacRelationRepo.findByUuid(relationUuid);
|
||||
if (result.isEmpty()) {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
@ -126,7 +147,7 @@ public class HsOfficeRelationController implements HsOfficeRelationsApi {
|
||||
final UUID relationUuid) {
|
||||
context.define(currentSubject, assumedRoles);
|
||||
|
||||
final var result = relationRbacRepo.deleteByUuid(relationUuid);
|
||||
final var result = rbacRelationRepo.deleteByUuid(relationUuid);
|
||||
if (result == 0) {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
@ -145,11 +166,11 @@ public class HsOfficeRelationController implements HsOfficeRelationsApi {
|
||||
|
||||
context.define(currentSubject, assumedRoles);
|
||||
|
||||
final var current = relationRbacRepo.findByUuid(relationUuid).orElseThrow();
|
||||
final var current = rbacRelationRepo.findByUuid(relationUuid).orElseThrow();
|
||||
|
||||
new HsOfficeRelationEntityPatcher(em, current).apply(body);
|
||||
|
||||
final var saved = relationRbacRepo.save(current);
|
||||
final var saved = rbacRelationRepo.save(current);
|
||||
final var mapped = mapper.map(saved, HsOfficeRelationResource.class);
|
||||
return ResponseEntity.ok(mapped);
|
||||
}
|
||||
@ -159,4 +180,11 @@ public class HsOfficeRelationController implements HsOfficeRelationsApi {
|
||||
resource.setHolder(mapper.map(entity.getHolder(), HsOfficePersonResource.class));
|
||||
resource.setContact(mapper.map(entity.getContact(), HsOfficeContactResource.class));
|
||||
};
|
||||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
final BiConsumer<HsOfficeContactInsertResource, HsOfficeContactRealEntity> CONTACT_RESOURCE_TO_ENTITY_POSTMAPPER = (resource, entity) -> {
|
||||
entity.putEmailAddresses(from(resource.getEmailAddresses()));
|
||||
entity.putPhoneNumbers(from(resource.getPhoneNumbers()));
|
||||
};
|
||||
}
|
||||
|
@ -52,6 +52,8 @@ components:
|
||||
holder.uuid:
|
||||
type: string
|
||||
format: uuid
|
||||
holder:
|
||||
$ref: 'hs-office-person-schemas.yaml#/components/schemas/HsOfficePersonInsert'
|
||||
type:
|
||||
type: string
|
||||
nullable: true
|
||||
@ -61,11 +63,17 @@ components:
|
||||
contact.uuid:
|
||||
type: string
|
||||
format: uuid
|
||||
contact:
|
||||
$ref: 'hs-office-contact-schemas.yaml#/components/schemas/HsOfficeContactInsert'
|
||||
required:
|
||||
- anchor.uuid
|
||||
- holder.uuid
|
||||
- type
|
||||
- contact.uuid
|
||||
# soon we might need to be able to use this:
|
||||
# https://community.smartbear.com/discussions/swaggerostools/defining-conditional-attributes-in-openapi/222410
|
||||
# For now we just describe the conditionally required properties:
|
||||
description:
|
||||
Additionally to `type` and `anchor.uuid`, either `anchor.uuid` or `anchor`
|
||||
and either `contact` or `contact.uuid` need to be given.
|
||||
|
||||
# relation created as a sub-element with implicitly known type
|
||||
HsOfficeRelationSubInsert:
|
||||
|
@ -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 {
|
||||
|
||||
@Nested
|
||||
class AtMaxOne {
|
||||
@Test
|
||||
void shouldFailValidationIfBothParametersAreNotNull() {
|
||||
final var throwable = catchThrowable(() ->
|
||||
Validate.validate("var1, var2").atMaxOneNonNull("val1", "val2")
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
@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 shouldNotFailValidationIfBothParametersAreull() {
|
||||
Validate.validate("var1, var2").atMaxOneNonNull(null, null);
|
||||
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").atMaxOneNonNull("val1", null);
|
||||
Validate.validate("var1, var2").atMaxOneNonNull(null, "val2");
|
||||
Validate.validate("var1, var2").exactlyOne("val1", null);
|
||||
Validate.validate("var1, var2").exactlyOne(null, "val2");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
@ -269,6 +269,61 @@ class HsOfficeRelationControllerAcceptanceTest extends ContextBasedTestWithClean
|
||||
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(
|
||||
location.substring(location.lastIndexOf('/') + 1)));
|
||||
assertThat(newSubjectUuid).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void globalAdmin_canNotAddRelation_ifAnchorPersonDoesNotExist() {
|
||||
|
||||
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user