working API with patching partner person directly into partner

This commit is contained in:
Michael Hoennig 2025-03-06 10:06:45 +01:00
parent f645b97533
commit 3030b91e2c
11 changed files with 356 additions and 129 deletions

View File

@ -159,7 +159,7 @@ public class HsOfficePartnerController implements HsOfficePartnersApi {
final var current = rbacPartnerRepo.findByUuid(partnerUuid).orElseThrow(); final var current = rbacPartnerRepo.findByUuid(partnerUuid).orElseThrow();
final var previousPartnerPerson = current.getPartnerRel().getHolder(); final var previousPartnerPerson = current.getPartnerRel().getHolder();
new HsOfficePartnerEntityPatcher(em, current).apply(body); new HsOfficePartnerEntityPatcher(mapper, em, current).apply(body);
final var saved = rbacPartnerRepo.save(current); final var saved = rbacPartnerRepo.save(current);
optionallyCreateExPartnerRelation(saved, previousPartnerPerson); optionallyCreateExPartnerRelation(saved, previousPartnerPerson);

View File

@ -3,15 +3,21 @@ package net.hostsharing.hsadminng.hs.office.partner;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficePartnerPatchResource; import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficePartnerPatchResource;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationEntityPatcher; import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationEntityPatcher;
import net.hostsharing.hsadminng.mapper.EntityPatcher; import net.hostsharing.hsadminng.mapper.EntityPatcher;
import net.hostsharing.hsadminng.mapper.StrictMapper;
import jakarta.persistence.EntityManager; import jakarta.persistence.EntityManager;
class HsOfficePartnerEntityPatcher implements EntityPatcher<HsOfficePartnerPatchResource> { class HsOfficePartnerEntityPatcher implements EntityPatcher<HsOfficePartnerPatchResource> {
private final StrictMapper mapper;
private final EntityManager em; private final EntityManager em;
private final HsOfficePartnerRbacEntity entity; private final HsOfficePartnerRbacEntity entity;
HsOfficePartnerEntityPatcher( HsOfficePartnerEntityPatcher(
final StrictMapper mapper,
final EntityManager em, final EntityManager em,
final HsOfficePartnerRbacEntity entity) { final HsOfficePartnerRbacEntity entity) {
this.mapper = mapper;
this.em = em; this.em = em;
this.entity = entity; this.entity = entity;
} }
@ -20,7 +26,7 @@ class HsOfficePartnerEntityPatcher implements EntityPatcher<HsOfficePartnerPatch
public void apply(final HsOfficePartnerPatchResource resource) { public void apply(final HsOfficePartnerPatchResource resource) {
if (resource.getPartnerRel() != null) { if (resource.getPartnerRel() != null) {
new HsOfficeRelationEntityPatcher(em, entity.getPartnerRel()).apply(resource.getPartnerRel()); new HsOfficeRelationEntityPatcher(mapper, em, entity.getPartnerRel()).apply(resource.getPartnerRel());
} }
if (resource.getDetails() != null) { if (resource.getDetails() != null) {

View File

@ -4,40 +4,47 @@ import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRealEntity;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeRelationPatchResource; import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeRelationPatchResource;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRealEntity; import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRealEntity;
import net.hostsharing.hsadminng.mapper.EntityPatcher; import net.hostsharing.hsadminng.mapper.EntityPatcher;
import net.hostsharing.hsadminng.mapper.OptionalFromJson; import net.hostsharing.hsadminng.mapper.StrictMapper;
import jakarta.persistence.EntityManager; import jakarta.persistence.EntityManager;
import java.util.UUID; import jakarta.validation.ValidationException;
public class HsOfficeRelationEntityPatcher implements EntityPatcher<HsOfficeRelationPatchResource> { public class HsOfficeRelationEntityPatcher implements EntityPatcher<HsOfficeRelationPatchResource> {
private final StrictMapper mapper;
private final EntityManager em; private final EntityManager em;
private final HsOfficeRelation entity; private final HsOfficeRelation entity;
public HsOfficeRelationEntityPatcher(final EntityManager em, final HsOfficeRelation entity) { public HsOfficeRelationEntityPatcher(final StrictMapper mapper, final EntityManager em, final HsOfficeRelation entity) {
this.mapper = mapper;
this.em = em; this.em = em;
this.entity = entity; this.entity = entity;
} }
@Override @Override
public void apply(final HsOfficeRelationPatchResource resource) { public void apply(final HsOfficeRelationPatchResource resource) {
OptionalFromJson.of(resource.getAnchorUuid()).ifPresent(newValue -> { if (resource.getHolder() != null && resource.getHolderUuid() != null) {
verifyNotNull(newValue, "contact"); throw new ValidationException("either \"holder\" or \"holder.uuid\" can be given, not both");
entity.setAnchor(em.getReference(HsOfficePersonRealEntity.class, newValue)); } else {
}); if (resource.getHolder() != null) {
OptionalFromJson.of(resource.getHolderUuid()).ifPresent(newValue -> { final var newHolder = mapper.map(resource.getHolder(), HsOfficePersonRealEntity.class);
verifyNotNull(newValue, "contact"); em.persist(newHolder);
entity.setHolder(em.getReference(HsOfficePersonRealEntity.class, newValue)); entity.setHolder(newHolder);
}); } else if (resource.getHolderUuid() != null) {
OptionalFromJson.of(resource.getContactUuid()).ifPresent(newValue -> { entity.setHolder(em.getReference(HsOfficePersonRealEntity.class, resource.getHolderUuid().get()));
verifyNotNull(newValue, "contact"); }
entity.setContact(em.getReference(HsOfficeContactRealEntity.class, newValue)); }
});
}
private void verifyNotNull(final UUID newValue, final String propertyName) { if (resource.getContact() != null && resource.getContactUuid() != null) {
if (newValue == null) { throw new ValidationException("either \"contact\" or \"contact.uuid\" can be given, not both");
throw new IllegalArgumentException("property '" + propertyName + "' must not be null"); } else {
if (resource.getContact() != null) {
final var newContact = mapper.map(resource.getContact(), HsOfficeContactRealEntity.class);
em.persist(newContact);
entity.setContact(newContact);
} else if (resource.getContactUuid() != null) {
entity.setContact(em.getReference(HsOfficeContactRealEntity.class, resource.getContactUuid().get()));
}
} }
} }
} }

View File

@ -52,6 +52,7 @@ components:
$ref: 'hs-office-relation-schemas.yaml#/components/schemas/HsOfficeRelationPatch' $ref: 'hs-office-relation-schemas.yaml#/components/schemas/HsOfficeRelationPatch'
details: details:
$ref: '#/components/schemas/HsOfficePartnerDetailsPatch' $ref: '#/components/schemas/HsOfficePartnerDetailsPatch'
additionalProperties: false
HsOfficePartnerDetailsPatch: HsOfficePartnerDetailsPatch:
type: object type: object

View File

@ -53,10 +53,15 @@ components:
type: string type: string
format: uuid format: uuid
nullable: true nullable: true
holder:
$ref: 'hs-office-person-schemas.yaml#/components/schemas/HsOfficePersonInsert'
contact.uuid: contact.uuid:
type: string type: string
format: uuid format: uuid
nullable: true nullable: true
contact:
$ref: 'hs-office-contact-schemas.yaml#/components/schemas/HsOfficeContactInsert'
additionalProperties: false
# arbitrary relation with explicit type # arbitrary relation with explicit type
HsOfficeRelationInsert: HsOfficeRelationInsert:

View File

@ -108,8 +108,6 @@ class HsOfficePartnerControllerRestTest {
"holder.uuid": "%s", "holder.uuid": "%s",
"contact.uuid": "%s" "contact.uuid": "%s"
}, },
"person.uuid": "%s",
"contact.uuid": "%s",
"details": { "details": {
"registrationOffice": "Temp Registergericht Aurich", "registrationOffice": "Temp Registergericht Aurich",
"registrationNumber": "111111" "registrationNumber": "111111"
@ -118,8 +116,6 @@ class HsOfficePartnerControllerRestTest {
""".formatted( """.formatted(
GIVEN_MANDANTE_UUID, GIVEN_MANDANTE_UUID,
GIVEN_INVALID_UUID, GIVEN_INVALID_UUID,
GIVEN_CONTACT_UUID,
GIVEN_INVALID_UUID,
GIVEN_CONTACT_UUID)) GIVEN_CONTACT_UUID))
.accept(MediaType.APPLICATION_JSON)) .accept(MediaType.APPLICATION_JSON))
@ -145,8 +141,6 @@ class HsOfficePartnerControllerRestTest {
"holder.uuid": "%s", "holder.uuid": "%s",
"contact.uuid": "%s" "contact.uuid": "%s"
}, },
"person.uuid": "%s",
"contact.uuid": "%s",
"details": { "details": {
"registrationOffice": "Temp Registergericht Aurich", "registrationOffice": "Temp Registergericht Aurich",
"registrationNumber": "111111" "registrationNumber": "111111"
@ -155,8 +149,6 @@ class HsOfficePartnerControllerRestTest {
""".formatted( """.formatted(
GIVEN_MANDANTE_UUID, GIVEN_MANDANTE_UUID,
GIVEN_PERSON_UUID, GIVEN_PERSON_UUID,
GIVEN_INVALID_UUID,
GIVEN_PERSON_UUID,
GIVEN_INVALID_UUID)) GIVEN_INVALID_UUID))
.accept(MediaType.APPLICATION_JSON)) .accept(MediaType.APPLICATION_JSON))

View File

@ -6,6 +6,8 @@ import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficePartne
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeRelationPatchResource; import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeRelationPatchResource;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRealEntity; import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRealEntity;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealEntity; import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealEntity;
import net.hostsharing.hsadminng.mapper.StrictMapper;
import net.hostsharing.hsadminng.persistence.EntityManagerWrapper;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.TestInstance;
@ -14,7 +16,6 @@ import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import org.openapitools.jackson.nullable.JsonNullable; import org.openapitools.jackson.nullable.JsonNullable;
import jakarta.persistence.EntityManager;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.UUID; import java.util.UUID;
@ -26,7 +27,7 @@ import static org.mockito.Mockito.lenient;
@TestInstance(PER_CLASS) @TestInstance(PER_CLASS)
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
// This test class does not rerive from PatchUnitTestBase because it has no directly patchable properties. // This test class does not subclass PatchUnitTestBase because it has no directly patchable properties.
// But the factory-structure is kept, so PatchUnitTestBase could easily be plugged back in if needed. // But the factory-structure is kept, so PatchUnitTestBase could easily be plugged back in if needed.
class HsOfficePartnerEntityPatcherUnitTest { class HsOfficePartnerEntityPatcherUnitTest {
@ -47,13 +48,15 @@ class HsOfficePartnerEntityPatcherUnitTest {
.build(); .build();
@Mock @Mock
private EntityManager em; private EntityManagerWrapper emw;
private StrictMapper mapper = new StrictMapper(emw);
@BeforeEach @BeforeEach
void initMocks() { void initMocks() {
lenient().when(em.getReference(eq(HsOfficePersonRealEntity.class), any())).thenAnswer(invocation -> lenient().when(emw.getReference(eq(HsOfficePersonRealEntity.class), any())).thenAnswer(invocation ->
HsOfficePersonRealEntity.builder().uuid(invocation.getArgument(1)).build()); HsOfficePersonRealEntity.builder().uuid(invocation.getArgument(1)).build());
lenient().when(em.getReference(eq(HsOfficeContactRealEntity.class), any())).thenAnswer(invocation -> lenient().when(emw.getReference(eq(HsOfficeContactRealEntity.class), any())).thenAnswer(invocation ->
HsOfficeContactRealEntity.builder().uuid(invocation.getArgument(1)).build()); HsOfficeContactRealEntity.builder().uuid(invocation.getArgument(1)).build());
} }
@ -123,6 +126,6 @@ class HsOfficePartnerEntityPatcherUnitTest {
} }
protected HsOfficePartnerEntityPatcher createPatcher(final HsOfficePartnerRbacEntity partner) { protected HsOfficePartnerEntityPatcher createPatcher(final HsOfficePartnerRbacEntity partner) {
return new HsOfficePartnerEntityPatcher(em, partner); return new HsOfficePartnerEntityPatcher(mapper, emw, partner);
} }
} }

View File

@ -0,0 +1,227 @@
package net.hostsharing.hsadminng.hs.office.relation;
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRealEntity;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeContactInsertResource;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficePersonInsertResource;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficePersonTypeResource;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeRelationPatchResource;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRealEntity;
import net.hostsharing.hsadminng.mapper.StrictMapper;
import net.hostsharing.hsadminng.persistence.EntityManagerWrapper;
import net.hostsharing.hsadminng.rbac.test.PatchUnitTestBase;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.openapitools.jackson.nullable.JsonNullable;
import jakarta.validation.ValidationException;
import java.util.UUID;
import java.util.stream.Stream;
import static net.hostsharing.hsadminng.hs.office.person.HsOfficePersonType.LEGAL_PERSON;
import static net.hostsharing.hsadminng.hs.office.person.HsOfficePersonType.NATURAL_PERSON;
import static net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationType.PARTNER;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.catchThrowable;
import static org.assertj.core.api.Assumptions.assumeThat;
import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@TestInstance(PER_CLASS)
@ExtendWith(MockitoExtension.class)
class HsOfficeRelationEntityPatcherUnitTest extends PatchUnitTestBase<
HsOfficeRelationPatchResource,
HsOfficeRelation
> {
private static final UUID INITIAL_RELATION_UUID = UUID.randomUUID();
private static final UUID INITIAL_ANCHOR_UUID = UUID.randomUUID();
private static final UUID INITIAL_HOLDER_UUID = UUID.randomUUID();
private static final UUID INITIAL_CONTACT_UUID = UUID.randomUUID();
private static final UUID PATCHED_HOLDER_UUID = UUID.randomUUID();
private static HsOfficePersonInsertResource HOLDER_PATCH_RESOURCE = new HsOfficePersonInsertResource() {
{
setPersonType(HsOfficePersonTypeResource.NATURAL_PERSON);
setFamilyName("Patched-Holder-Family-Name");
setGivenName("Patched-Holder-Given-Name");
}
};
private static HsOfficePersonRealEntity PATCHED_HOLDER = HsOfficePersonRealEntity.builder()
.uuid(PATCHED_HOLDER_UUID)
.personType(NATURAL_PERSON)
.familyName("Patched-Holder-Family-Name")
.givenName("Patched-Holder-Given-Name")
.build();
private static final UUID PATCHED_CONTACT_UUID = UUID.randomUUID();
private static HsOfficeContactInsertResource CONTACT_PATCH_RESOURCE = new HsOfficeContactInsertResource() {
{
setCaption("Patched-Contact-Caption");
}
};
private static HsOfficeContactRealEntity PATCHED_CONTACT = HsOfficeContactRealEntity.builder()
.uuid(PATCHED_CONTACT_UUID)
.caption("Patched-Contact-Caption")
.build();
@Mock
private EntityManagerWrapper emw;
private StrictMapper mapper = new StrictMapper(emw);
@BeforeEach
void initMocks() {
lenient().when(emw.getReference(HsOfficePersonRealEntity.class, PATCHED_HOLDER_UUID)).thenAnswer(
p -> PATCHED_HOLDER);
lenient().when(emw.getReference(HsOfficeContactRealEntity.class, PATCHED_CONTACT_UUID)).thenAnswer(
p -> PATCHED_CONTACT);
}
@Override
protected HsOfficeRelation newInitialEntity() {
final var entity = new HsOfficeRelationRealEntity();
entity.setUuid(INITIAL_RELATION_UUID);
entity.setType(PARTNER);
entity.setAnchor(HsOfficePersonRealEntity.builder()
.uuid(INITIAL_ANCHOR_UUID)
.personType(LEGAL_PERSON)
.tradeName("Initial-Anchor-Tradename")
.build());
entity.setHolder(HsOfficePersonRealEntity.builder()
.uuid(INITIAL_HOLDER_UUID)
.personType(NATURAL_PERSON)
.familyName("Initial-Holder-Family-Name")
.givenName("Initial-Holder-Given-Name")
.build());
entity.setContact(HsOfficeContactRealEntity.builder()
.uuid(INITIAL_CONTACT_UUID)
.caption("Initial-Contact-Caption")
.build());
return entity;
}
@Override
protected HsOfficeRelationPatchResource newPatchResource() {
return new HsOfficeRelationPatchResource();
}
@Override
protected HsOfficeRelationEntityPatcher createPatcher(final HsOfficeRelation relation) {
return new HsOfficeRelationEntityPatcher(mapper, emw, relation);
}
@Override
protected Stream<Property> propertyTestDescriptors() {
return Stream.of(
new JsonNullableProperty<>(
"holderUuid",
HsOfficeRelationPatchResource::setHolderUuid,
PATCHED_HOLDER_UUID,
HsOfficeRelation::setHolder,
PATCHED_HOLDER),
new SimpleProperty<>(
"holder",
HsOfficeRelationPatchResource::setHolder,
HOLDER_PATCH_RESOURCE,
HsOfficeRelation::setHolder,
withoutUuid(PATCHED_HOLDER))
.notNullable(),
new JsonNullableProperty<>(
"contactUuid",
HsOfficeRelationPatchResource::setContactUuid,
PATCHED_CONTACT_UUID,
HsOfficeRelation::setContact,
PATCHED_CONTACT),
new SimpleProperty<>(
"contact",
HsOfficeRelationPatchResource::setContact,
CONTACT_PATCH_RESOURCE,
HsOfficeRelation::setContact,
withoutUuid(PATCHED_CONTACT))
.notNullable()
);
}
@Override
protected void willPatchAllProperties() {
// this generic test does not work because either holder or holder.uuid can be set
assumeThat(true).isFalse();
}
@Test
void willThrowExceptionIfHolderAndHolderUuidAreGiven() {
// given
final var givenEntity = newInitialEntity();
final var patchResource = newPatchResource();
patchResource.setHolderUuid(JsonNullable.of(PATCHED_HOLDER_UUID));
patchResource.setHolder(HOLDER_PATCH_RESOURCE);
// when
final var exception = catchThrowable(() -> createPatcher(givenEntity).apply(patchResource));
// then
assertThat(exception).isInstanceOf(ValidationException.class)
.hasMessage("either \"holder\" or \"holder.uuid\" can be given, not both");
}
@Test
void willThrowExceptionIfContactAndContactUuidAreGiven() {
// given
final var givenEntity = newInitialEntity();
final var patchResource = newPatchResource();
patchResource.setContactUuid(JsonNullable.of(PATCHED_CONTACT_UUID));
patchResource.setContact(CONTACT_PATCH_RESOURCE);
// when
final var exception = catchThrowable(() -> createPatcher(givenEntity).apply(patchResource));
// then
assertThat(exception).isInstanceOf(ValidationException.class)
.hasMessage("either \"contact\" or \"contact.uuid\" can be given, not both");
}
@Test
void willPersistNewHolder() {
// given
final var givenEntity = newInitialEntity();
final var patchResource = newPatchResource();
patchResource.setHolder(HOLDER_PATCH_RESOURCE);
// when
createPatcher(givenEntity).apply(patchResource);
// then
verify(emw, times(1)).persist(givenEntity.getHolder());
}
@Test
void willPersistNewContact() {
// given
final var givenEntity = newInitialEntity();
final var patchResource = newPatchResource();
patchResource.setContact(CONTACT_PATCH_RESOURCE);
// when
createPatcher(givenEntity).apply(patchResource);
// then
verify(emw, times(1)).persist(givenEntity.getContact());
}
private HsOfficePersonRealEntity withoutUuid(final HsOfficePersonRealEntity givenWithUuid) {
return givenWithUuid.toBuilder().uuid(null).build();
}
private HsOfficeContactRealEntity withoutUuid(final HsOfficeContactRealEntity givenWithUuid) {
return givenWithUuid.toBuilder().uuid(null).build();
}
}

View File

@ -3,6 +3,8 @@ package net.hostsharing.hsadminng.hs.office.relation;
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRealEntity; import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRealEntity;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeRelationPatchResource; import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeRelationPatchResource;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRealEntity; import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRealEntity;
import net.hostsharing.hsadminng.mapper.StrictMapper;
import net.hostsharing.hsadminng.persistence.EntityManagerWrapper;
import net.hostsharing.hsadminng.rbac.test.PatchUnitTestBase; import net.hostsharing.hsadminng.rbac.test.PatchUnitTestBase;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.TestInstance;
@ -10,7 +12,6 @@ import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import jakarta.persistence.EntityManager;
import java.util.UUID; import java.util.UUID;
import java.util.stream.Stream; import java.util.stream.Stream;
@ -30,11 +31,13 @@ class HsOfficeRelationPatcherUnitTest extends PatchUnitTestBase<
static final UUID PATCHED_CONTACT_UUID = UUID.randomUUID(); static final UUID PATCHED_CONTACT_UUID = UUID.randomUUID();
@Mock @Mock
EntityManager em; EntityManagerWrapper emw;
StrictMapper mapper = new StrictMapper(emw);
@BeforeEach @BeforeEach
void initMocks() { void initMocks() {
lenient().when(em.getReference(eq(HsOfficeContactRealEntity.class), any())).thenAnswer(invocation -> lenient().when(emw.getReference(eq(HsOfficeContactRealEntity.class), any())).thenAnswer(invocation ->
HsOfficeContactRealEntity.builder().uuid(invocation.getArgument(1)).build()); HsOfficeContactRealEntity.builder().uuid(invocation.getArgument(1)).build());
} }
@ -66,7 +69,7 @@ class HsOfficeRelationPatcherUnitTest extends PatchUnitTestBase<
@Override @Override
protected HsOfficeRelationEntityPatcher createPatcher(final HsOfficeRelation relation) { protected HsOfficeRelationEntityPatcher createPatcher(final HsOfficeRelation relation) {
return new HsOfficeRelationEntityPatcher(em, relation); return new HsOfficeRelationEntityPatcher(mapper, emw, relation);
} }
@Override @Override

View File

@ -12,101 +12,81 @@ public class ReplaceDeceasedPartnerWithCommunityOfHeirs extends UseCase<ReplaceD
public ReplaceDeceasedPartnerWithCommunityOfHeirs(final ScenarioTest testSuite) { public ReplaceDeceasedPartnerWithCommunityOfHeirs(final ScenarioTest testSuite) {
super(testSuite); super(testSuite);
} }
@Override @Override
protected HttpResponse run() { protected HttpResponse run() {
obtain("Person: Hostsharing eG", () -> obtain("Partner: %{partnerNumber}",
httpGet("/api/hs/office/persons?name=Hostsharing+eG") () -> httpGet("/api/hs/office/partners/%{partnerNumber}")
.expecting(OK).expecting(JSON), .reportWithResponse().expecting(OK).expecting(JSON),
response -> response.expectArrayElements(1).getFromBody("[0].uuid"),
"Even in production data we expect this query to return just a single result." // TODO.impl: add constraint?
);
obtain("Partner: %{partnerNumber}", () ->
httpGet("/api/hs/office/partners/%{partnerNumber}")
.reportWithResponse().expecting(OK).expecting(JSON),
response -> response.getFromBody("uuid"), response -> response.getFromBody("uuid"),
"Even in production data we expect this query to return just a single result." // TODO.impl: add constraint? "Even in production data we expect this query to return just a single result."
// TODO.impl: add constraint?
) )
.extractValue("partnerRel.holder.familyName", "familyNameOfDeceasedPerson") .extractValue("partnerRel.holder.familyName", "familyNameOfDeceasedPerson")
.extractValue("partnerRel.holder.givenName", "givenNameOfDeceasedPerson") .extractValue("partnerRel.holder.givenName", "givenNameOfDeceasedPerson")
.extractUuidAlias("partnerRel.holder.uuid", "Person: %{givenNameOfDeceasedPerson} %{familyNameOfDeceasedPerson}"); .extractUuidAlias(
"partnerRel.holder.uuid",
"Person: %{givenNameOfDeceasedPerson} %{familyNameOfDeceasedPerson}");
obtain("Partner-Relation: Erbengemeinschaft %{givenNameOfDeceasedPerson} %{familyNameOfDeceasedPerson}", () -> withTitle("New Partner-Person+Contact: Erbengemeinschaft %{givenNameOfDeceasedPerson} %{familyNameOfDeceasedPerson}",
httpPost("/api/hs/office/relations", usingJsonBody(""" () -> httpPatch("/api/hs/office/partners/%{Partner: %{partnerNumber}}",
{ usingJsonBody("""
"type": "PARTNER", {
"anchor.uuid": ${Person: Hostsharing eG}, "wrong1": false,
"holder": { "partnerRel": {
"personType": "UNINCORPORATED_FIRM", "wrong2": false,
"tradeName": "Erbengemeinschaft %{givenNameOfDeceasedPerson} %{familyNameOfDeceasedPerson}", "holder": {
}, "personType": "UNINCORPORATED_FIRM",
"contact": { "tradeName": "Erbengemeinschaft %{givenNameOfDeceasedPerson} %{familyNameOfDeceasedPerson}",
"caption": "Erbengemeinschaft %{givenNameOfDeceasedPerson} %{familyNameOfDeceasedPerson}", },
"postalAddress": { "contact": {
"name": "Erbengemeinschaft %{givenNameOfDeceasedPerson} %{familyNameOfDeceasedPerson}", "wrong3": false,
"co": "%{representativeGivenName} %{representativeFamilyName}", "caption": "Erbengemeinschaft %{givenNameOfDeceasedPerson} %{familyNameOfDeceasedPerson}",
%{communityOfHeirsPostalAddress} "postalAddress": {
}, "wrong4": false,
"phoneNumbers": { "name": "Erbengemeinschaft %{givenNameOfDeceasedPerson} %{familyNameOfDeceasedPerson}",
"office": ${communityOfHeirsOfficePhoneNumber} "co": "%{representativeGivenName} %{representativeFamilyName}",
}, %{communityOfHeirsPostalAddress}
"emailAddresses": { },
"main": ${communityOfHeirsEmailAddress} "phoneNumbers": {
} "office": ${communityOfHeirsOfficePhoneNumber}
} },
} "emailAddresses": {
""")) "main": ${communityOfHeirsEmailAddress}
.reportWithResponse().expecting(CREATED).expecting(JSON) }
) }
.extractUuidAlias("contact.uuid", "Contact: Erbengemeinschaft %{givenNameOfDeceasedPerson} %{familyNameOfDeceasedPerson}") }
.extractUuidAlias("holder.uuid", "Person: Erbengemeinschaft %{givenNameOfDeceasedPerson} %{familyNameOfDeceasedPerson}"); }
"""))
obtain("Representative-Relation: %{representativeGivenName} %{representativeFamilyName} for Erbengemeinschaft %{givenNameOfDeceasedPerson} %{familyNameOfDeceasedPerson}", () -> .reportWithResponse().expecting(HttpStatus.OK).expecting(JSON)
httpPost("/api/hs/office/relations", usingJsonBody(""" .extractUuidAlias(
{ "partnerRel.holder.uuid",
"type": "REPRESENTATIVE", "Person: Erbengemeinschaft %{givenNameOfDeceasedPerson} %{familyNameOfDeceasedPerson}")
"anchor.uuid": ${Person: Erbengemeinschaft %{givenNameOfDeceasedPerson} %{familyNameOfDeceasedPerson}}, .extractUuidAlias(
"holder": { "partnerRel.contact.uuid",
"personType": "NATURAL_PERSON", "Contact: Erbengemeinschaft %{givenNameOfDeceasedPerson} %{familyNameOfDeceasedPerson}")
"givenName": ${representativeGivenName},
"familyName": ${representativeFamilyName}
},
"contact.uuid": ${Contact: Erbengemeinschaft %{givenNameOfDeceasedPerson} %{familyNameOfDeceasedPerson}}
}
"""))
.reportWithResponse().expecting(CREATED).expecting(JSON)
).extractUuidAlias("holder.uuid", "Person: %{representativeGivenName} %{representativeFamilyName}");
obtain("Partner: Erbengemeinschaft %{givenNameOfDeceasedPerson} %{familyNameOfDeceasedPerson}", () ->
httpPatch("/api/hs/office/partners/%{Partner: %{partnerNumber}}", usingJsonBody("""
{
"partnerRel.uuid": ${Partner-Relation: Erbengemeinschaft %{givenNameOfDeceasedPerson} %{familyNameOfDeceasedPerson}}
}
"""))
.expecting(HttpStatus.OK)
); );
// TODO.test: missing steps Debitor, Membership, Coop-Shares+Assets obtain(
"Representative-Relation: %{representativeGivenName} %{representativeFamilyName} for Erbengemeinschaft %{givenNameOfDeceasedPerson} %{familyNameOfDeceasedPerson}",
// Debitors () -> httpPost("/api/hs/office/relations",
usingJsonBody("""
// die Erbengemeinschaft wird als Anchor-Person (Partner) in die Debitor-Relations eingetragen {
// der neue Rechnungsempfänger (z.B. auch ggf. Rechtsanwalt) wird als Holder-Person (Debitor-Person) in die Debitor-Relations eingetragen -- oder neu? "type": "REPRESENTATIVE",
"anchor.uuid": ${Person: Erbengemeinschaft %{givenNameOfDeceasedPerson} %{familyNameOfDeceasedPerson}},
// Membership "holder": {
"personType": "NATURAL_PERSON",
// intro: die Mitgliedschaft geht juristisch gesehen auf die Erbengemeinschaft über "givenName": ${representativeGivenName},
"familyName": ${representativeFamilyName}
// die bisherige Mitgliedschaft als DECEASED mit Ende-Datum=Todesdatum markieren },
"contact.uuid": ${Contact: Erbengemeinschaft %{givenNameOfDeceasedPerson} %{familyNameOfDeceasedPerson}}
// eine neue Mitgliedschaft (-00) mit dem Start-Datum=Todesdatum+1 anlegen }
"""))
// die Geschäftsanteile per share-tx: TRANSFERADOPT an die Erbengemeinschaft übertragen .reportWithResponse().expecting(CREATED).expecting(JSON)
// die Geschäftsguthaben per asset-tx: TRANSFERADOPT an die Erbengemeinschaft übertragen )
.extractUuidAlias("holder.uuid", "Person: %{representativeGivenName} %{representativeFamilyName}");
// outro: die Erbengemeinschaft hat eine Frist von 6 Monaten, um die Mitgliedschaft einer Person zu übertragen // outro: die Erbengemeinschaft hat eine Frist von 6 Monaten, um die Mitgliedschaft einer Person zu übertragen
// nächster "Drecksfall" // nächster "Drecksfall"
@ -120,7 +100,8 @@ public class ReplaceDeceasedPartnerWithCommunityOfHeirs extends UseCase<ReplaceD
"Verify the Updated Partner", "Verify the Updated Partner",
() -> httpGet("/api/hs/office/partners/%{partnerNumber}") () -> httpGet("/api/hs/office/partners/%{partnerNumber}")
.expecting(OK).expecting(JSON).expectObject(), .expecting(OK).expecting(JSON).expectObject(),
path("partnerRel.holder.tradeName").contains("Erbengemeinschaft %{givenNameOfDeceasedPerson} %{familyNameOfDeceasedPerson}") path("partnerRel.holder.tradeName").contains(
"Erbengemeinschaft %{givenNameOfDeceasedPerson} %{familyNameOfDeceasedPerson}")
); );
// TODO.test: Verify the EX_PARTNER-Relation, once we fixed the anchor problem, see HsOfficePartnerController // TODO.test: Verify the EX_PARTNER-Relation, once we fixed the anchor problem, see HsOfficePartnerController
@ -128,9 +109,11 @@ public class ReplaceDeceasedPartnerWithCommunityOfHeirs extends UseCase<ReplaceD
verify( verify(
"Verify the Representative-Relation", "Verify the Representative-Relation",
() -> httpGet("/api/hs/office/relations?relationType=REPRESENTATIVE&personUuid=%{Person: %{representativeGivenName} %{representativeFamilyName}}") () -> httpGet(
"/api/hs/office/relations?relationType=REPRESENTATIVE&personUuid=%{Person: %{representativeGivenName} %{representativeFamilyName}}")
.expecting(OK).expecting(JSON).expectArrayElements(1), .expecting(OK).expecting(JSON).expectArrayElements(1),
path("[0].anchor.tradeName").contains("Erbengemeinschaft %{givenNameOfDeceasedPerson} %{familyNameOfDeceasedPerson}"), path("[0].anchor.tradeName").contains(
"Erbengemeinschaft %{givenNameOfDeceasedPerson} %{familyNameOfDeceasedPerson}"),
path("[0].holder.familyName").contains("%{representativeFamilyName}") path("[0].holder.familyName").contains("%{representativeFamilyName}")
); );

View File

@ -19,7 +19,7 @@ import static org.assertj.core.api.Assumptions.assumeThat;
public abstract class PatchUnitTestBase<R, E> { public abstract class PatchUnitTestBase<R, E> {
@Test @Test
void willPatchNoProperty() { protected void willPatchNoProperty() {
// given // given
final var givenEntity = newInitialEntity(); final var givenEntity = newInitialEntity();
final var patchResource = newPatchResource(); final var patchResource = newPatchResource();
@ -73,7 +73,7 @@ public abstract class PatchUnitTestBase<R, E> {
@ParameterizedTest @ParameterizedTest
@MethodSource("propertyTestCases") @MethodSource("propertyTestCases")
void willThrowExceptionIfNotNullableValueIsNull(final Property<R, Object, E, Object> testCase) { protected void willThrowExceptionIfNotNullableValueIsNull(final Property<R, Object, E, Object> testCase) {
assumeThat(testCase instanceof JsonNullableProperty).isTrue(); assumeThat(testCase instanceof JsonNullableProperty).isTrue();
assumeThat(testCase.nullable).isFalse(); assumeThat(testCase.nullable).isFalse();
@ -94,7 +94,7 @@ public abstract class PatchUnitTestBase<R, E> {
@ParameterizedTest @ParameterizedTest
@MethodSource("propertyTestCases") @MethodSource("propertyTestCases")
void willPatchOnlyGivenPropertyToNull(final Property<R, Object, E, Object> testCase) { protected void willPatchOnlyGivenPropertyToNull(final Property<R, Object, E, Object> testCase) {
assumeThat(testCase.nullable).isTrue(); assumeThat(testCase.nullable).isTrue();
// given // given
@ -113,7 +113,7 @@ public abstract class PatchUnitTestBase<R, E> {
@ParameterizedTest @ParameterizedTest
@MethodSource("propertyTestCases") @MethodSource("propertyTestCases")
void willNotPatchIfGivenPropertyNotGiven(final Property<R, Object, E, Object> testCase) { protected void willNotPatchIfGivenPropertyNotGiven(final Property<R, Object, E, Object> testCase) {
// given // given
final var givenEntity = newInitialEntity(); final var givenEntity = newInitialEntity();