create partner now taking existing contact+person uuids instead of complete (new) objects

This commit is contained in:
Michael Hoennig 2022-09-14 12:16:44 +02:00
parent 3fa02d4a10
commit 9cd4525e2b
7 changed files with 143 additions and 101 deletions

View File

@ -13,6 +13,7 @@ import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.NoSuchElementException;
import java.util.Optional; import java.util.Optional;
@ControllerAdvice @ControllerAdvice
@ -34,6 +35,13 @@ public class RestResponseEntityExceptionHandler
return errorResponse(request, httpStatus(message).orElse(HttpStatus.INTERNAL_SERVER_ERROR), message); return errorResponse(request, httpStatus(message).orElse(HttpStatus.INTERNAL_SERVER_ERROR), message);
} }
@ExceptionHandler(NoSuchElementException.class)
protected ResponseEntity<CustomErrorResponse> handleNoSuchElementException(
final RuntimeException exc, final WebRequest request) {
final var message = firstLine(NestedExceptionUtils.getMostSpecificCause(exc).getMessage());
return errorResponse(request, HttpStatus.NOT_FOUND, message);
}
@ExceptionHandler(Throwable.class) @ExceptionHandler(Throwable.class)
protected ResponseEntity<CustomErrorResponse> handleOtherExceptions( protected ResponseEntity<CustomErrorResponse> handleOtherExceptions(
final Throwable exc, final WebRequest request) { final Throwable exc, final WebRequest request) {

View File

@ -2,14 +2,9 @@ package net.hostsharing.hsadminng.hs.office.partner;
import net.hostsharing.hsadminng.Mapper; import net.hostsharing.hsadminng.Mapper;
import net.hostsharing.hsadminng.context.Context; import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity;
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRepository; import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRepository;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficePartnersApi; import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficePartnersApi;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeContactResource; import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.*;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficePartnerResource;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficePartnerUpdateResource;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficePersonResource;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRepository; import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRepository;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
@ -18,6 +13,7 @@ import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder; import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder;
import java.util.List; import java.util.List;
import java.util.NoSuchElementException;
import java.util.UUID; import java.util.UUID;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
@ -59,34 +55,25 @@ public class HsOfficePartnerController implements HsOfficePartnersApi {
public ResponseEntity<HsOfficePartnerResource> addPartner( public ResponseEntity<HsOfficePartnerResource> addPartner(
final String currentUser, final String currentUser,
final String assumedRoles, final String assumedRoles,
final HsOfficePartnerResource body) { final HsOfficePartnerInsertResource body) {
context.define(currentUser, assumedRoles); context.define(currentUser, assumedRoles);
if (body.getUuid() == null) { final var entityToSave = mapToHsOfficePartnerEntity(body);
body.setUuid(UUID.randomUUID()); entityToSave.setUuid(UUID.randomUUID());
} entityToSave.setContact(contactRepo.findByUuid(body.getContactUuid()).orElseThrow(
() -> new NoSuchElementException("cannot find contact uuid " + body.getContactUuid())
));
entityToSave.setPerson(personRepo.findByUuid(body.getPersonUuid()).orElseThrow(
() -> new NoSuchElementException("cannot find person uuid " + body.getPersonUuid())
));
final var entityToSave = map(body, HsOfficePartnerEntity.class);
if (entityToSave.getContact().getUuid() != null) {
contactRepo.findByUuid(entityToSave.getContact().getUuid()).ifPresent(entityToSave::setContact);
} else {
entityToSave.getContact().setUuid(UUID.randomUUID());
entityToSave.setContact(contactRepo.save(entityToSave.getContact()));
}
if (entityToSave.getPerson().getUuid() != null) {
personRepo.findByUuid(entityToSave.getPerson().getUuid()).ifPresent(entityToSave::setPerson);
} else {
entityToSave.getPerson().setUuid(UUID.randomUUID());
entityToSave.setPerson(personRepo.save(entityToSave.getPerson()));
}
final var saved = partnerRepo.save(entityToSave); final var saved = partnerRepo.save(entityToSave);
final var uri = final var uri =
MvcUriComponentsBuilder.fromController(getClass()) MvcUriComponentsBuilder.fromController(getClass())
.path("/api/hs/office/partners/{id}") .path("/api/hs/office/partners/{id}")
.buildAndExpand(body.getUuid()) .buildAndExpand(entityToSave.getUuid())
.toUri(); .toUri();
final var mapped = map(saved, HsOfficePartnerResource.class, final var mapped = map(saved, HsOfficePartnerResource.class,
PARTNER_ENTITY_TO_RESOURCE_POSTMAPPER); PARTNER_ENTITY_TO_RESOURCE_POSTMAPPER);
@ -121,18 +108,22 @@ public class HsOfficePartnerController implements HsOfficePartnersApi {
final String currentUser, final String currentUser,
final String assumedRoles, final String assumedRoles,
final UUID partnerUuid, final UUID partnerUuid,
final HsOfficePartnerUpdateResource body) { final HsOfficePartnerPatchResource body) {
return null; return null;
} }
private final BiConsumer<HsOfficePartnerResource, HsOfficePartnerEntity> PARTNER_RESOURCE_TO_ENTITY_POSTMAPPER = (resource, entity) -> { final BiConsumer<HsOfficePartnerEntity, HsOfficePartnerResource> PARTNER_ENTITY_TO_RESOURCE_POSTMAPPER = (entity, resource) -> {
entity.setPerson(map(resource.getPerson(), HsOfficePersonEntity.class));
entity.setContact(map(resource.getContact(), HsOfficeContactEntity.class));
};
private final BiConsumer<HsOfficePartnerEntity, HsOfficePartnerResource> PARTNER_ENTITY_TO_RESOURCE_POSTMAPPER = (entity, resource) -> {
resource.setPerson(map(entity.getPerson(), HsOfficePersonResource.class)); resource.setPerson(map(entity.getPerson(), HsOfficePersonResource.class));
resource.setContact(map(entity.getContact(), HsOfficeContactResource.class)); resource.setContact(map(entity.getContact(), HsOfficeContactResource.class));
}; };
private HsOfficePartnerEntity mapToHsOfficePartnerEntity(final HsOfficePartnerInsertResource resource) {
final var entity = new HsOfficePartnerEntity();
entity.setBirthday(resource.getBirthday());
entity.setBirthName(resource.getBirthName());
entity.setDateOfDeath(resource.getDateOfDeath());
entity.setRegistrationNumber(resource.getRegistrationNumber());
entity.setRegistrationOffice(resource.getRegistrationOffice());
return entity;
}
} }

View File

@ -32,7 +32,7 @@ components:
$ref: './hs-office-contact-schemas.yaml#/components/schemas/HsOfficeContact' $ref: './hs-office-contact-schemas.yaml#/components/schemas/HsOfficeContact'
- $ref: '#/components/schemas/HsOfficePartnerBase' - $ref: '#/components/schemas/HsOfficePartnerBase'
HsOfficePartnerUpdate: HsOfficePartnerPatch:
allOf: allOf:
- type: object - type: object
properties: properties:
@ -43,3 +43,18 @@ components:
type: string type: string
format: uuid format: uuid
- $ref: '#/components/schemas/HsOfficePartnerBase' - $ref: '#/components/schemas/HsOfficePartnerBase'
HsOfficePartnerInsert:
allOf:
- type: object
properties:
personUuid:
type: string
format: uuid
contactUuid:
type: string
format: uuid
- required:
- personUuid
- contactUuid
- $ref: '#/components/schemas/HsOfficePartnerBase'

View File

@ -42,7 +42,7 @@ patch:
content: content:
'application/json': 'application/json':
schema: schema:
$ref: './hs-office-partner-schemas.yaml#/components/schemas/HsOfficePartnerUpdate' $ref: './hs-office-partner-schemas.yaml#/components/schemas/HsOfficePartnerPatch'
responses: responses:
"200": "200":
description: OK description: OK

View File

@ -39,7 +39,7 @@ post:
content: content:
'application/json': 'application/json':
schema: schema:
$ref: './hs-office-partner-schemas.yaml#/components/schemas/HsOfficePartner' $ref: './hs-office-partner-schemas.yaml#/components/schemas/HsOfficePartnerInsert'
required: true required: true
responses: responses:
"201": "201":

View File

@ -156,7 +156,7 @@ grant all privileges on hs_office_contact_rv to restricted;
/** /**
Instead of insert trigger function for hs_office_contact_rv. Instead of insert trigger function for hs_office_contact_rv.
*/ */
create or replace function inserthsOfficeContact() create or replace function insertHsOfficeContact()
returns trigger returns trigger
language plpgsql as $$ language plpgsql as $$
declare declare
@ -173,11 +173,11 @@ $$;
/* /*
Creates an instead of insert trigger for the hs_office_contact_rv view. Creates an instead of insert trigger for the hs_office_contact_rv view.
*/ */
create trigger inserthsOfficeContact_Trigger create trigger insertHsOfficeContact_Trigger
instead of insert instead of insert
on hs_office_contact_rv on hs_office_contact_rv
for each row for each row
execute function inserthsOfficeContact(); execute function insertHsOfficeContact();
--// --//
-- ============================================================================ -- ============================================================================
@ -189,7 +189,7 @@ execute function inserthsOfficeContact();
Checks if the current subject (user / assumed role) has the permission to delete the row. Checks if the current subject (user / assumed role) has the permission to delete the row.
*/ */
create or replace function deletehsOfficeContact() create or replace function deleteHsOfficeContact()
returns trigger returns trigger
language plpgsql as $$ language plpgsql as $$
begin begin
@ -204,11 +204,11 @@ end; $$;
/* /*
Creates an instead of delete trigger for the hs_office_contact_rv view. Creates an instead of delete trigger for the hs_office_contact_rv view.
*/ */
create trigger deletehsOfficeContact_Trigger create trigger deleteHsOfficeContact_Trigger
instead of delete instead of delete
on hs_office_contact_rv on hs_office_contact_rv
for each row for each row
execute function deletehsOfficeContact(); execute function deleteHsOfficeContact();
--/ --/
-- ============================================================================ -- ============================================================================

View File

@ -5,6 +5,8 @@ import io.restassured.http.ContentType;
import net.hostsharing.hsadminng.Accepts; import net.hostsharing.hsadminng.Accepts;
import net.hostsharing.hsadminng.HsadminNgApplication; import net.hostsharing.hsadminng.HsadminNgApplication;
import net.hostsharing.hsadminng.context.Context; import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRepository;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRepository;
import net.hostsharing.test.JpaAttempt; import net.hostsharing.test.JpaAttempt;
import org.json.JSONException; import org.json.JSONException;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
@ -20,7 +22,6 @@ import java.util.Set;
import java.util.UUID; import java.util.UUID;
import static net.hostsharing.test.IsValidUuidMatcher.isUuidValid; import static net.hostsharing.test.IsValidUuidMatcher.isUuidValid;
import static net.hostsharing.test.JsonBuilder.jsonObject;
import static net.hostsharing.test.JsonMatcher.lenientlyEquals; import static net.hostsharing.test.JsonMatcher.lenientlyEquals;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
@ -45,6 +46,12 @@ class HsOfficePartnerControllerAcceptanceTest {
@Autowired @Autowired
HsOfficePartnerRepository partnerRepo; HsOfficePartnerRepository partnerRepo;
@Autowired
HsOfficePersonRepository personRepo;
@Autowired
HsOfficeContactRepository contactRepo;
@Autowired @Autowired
JpaAttempt jpaAttempt; JpaAttempt jpaAttempt;
@ -90,70 +97,28 @@ class HsOfficePartnerControllerAcceptanceTest {
@Accepts({ "Partner:C(Create)" }) @Accepts({ "Partner:C(Create)" })
class AddPartner { class AddPartner {
private final static String NEW_PARTNER_JSON_WITHOUT_UUID = @Test
""" void globalAdmin_withoutAssumedRole_canAddPartner_withGeneratedUuid() {
{
"person": { context.define("superuser-alex@hostsharing.net");
"personType": "LEGAL", final var givenPerson = personRepo.findPersonByOptionalNameLike("Ostfriesische").get(0);
"tradeName": "Test Corp.", final var givenContact = contactRepo.findContactByOptionalLabelLike("forth").get(0);
"givenName": null,
"familyName": null final var location = RestAssured // @formatter:off
}, .given()
"contact": { .header("current-user", "superuser-alex@hostsharing.net")
"label": "Test Corp.", .contentType(ContentType.JSON)
"postalAddress": "Test Corp.\\nTestweg 50\\n20001 Hamburg", .body("""
"emailAddresses": "office@example.com", {
"phoneNumbers": "040 12345" "contactUuid": "%s",
}, "personUuid": "%s",
"registrationOffice": "Registergericht Hamburg", "registrationOffice": "Registergericht Hamburg",
"registrationNumber": "123456", "registrationNumber": "123456",
"birthName": null, "birthName": null,
"birthday": null, "birthday": null,
"dateOfDeath": null "dateOfDeath": null
} }
"""; """.formatted(givenContact.getUuid(), givenPerson.getUuid()))
@Test
void globalAdmin_withoutAssumedRole_canAddPartner_withExplicitUuid() {
final var givenUUID = toCleanup(UUID.fromString("3fa85f64-5717-4562-b3fc-2c963f66afa6"));
final var location = RestAssured // @formatter:off
.given()
.header("current-user", "superuser-alex@hostsharing.net")
.contentType(ContentType.JSON)
.body(jsonObject(NEW_PARTNER_JSON_WITHOUT_UUID)
.with("uuid", givenUUID.toString()).toString())
.port(port)
.when()
.post("http://localhost/api/hs/office/partners")
.then().assertThat()
.statusCode(201)
.contentType(ContentType.JSON)
.body("uuid", is("3fa85f64-5717-4562-b3fc-2c963f66afa6"))
.body("registrationNumber", is("123456"))
.body("person.tradeName", is("Test Corp."))
.body("contact.label", is("Test Corp."))
.header("Location", startsWith("http://localhost"))
.extract().header("Location"); // @formatter:on
// finally, the new partner can be accessed under the given UUID
final var newUserUuid = UUID.fromString(
location.substring(location.lastIndexOf('/') + 1));
assertThat(newUserUuid).isEqualTo(givenUUID);
context.define("superuser-alex@hostsharing.net");
assertThat(partnerRepo.findByUuid(newUserUuid))
.hasValueSatisfying(c -> assertThat(c.getPerson().getTradeName()).isEqualTo("Test Corp."));
}
@Test
void globalAdmin_withoutAssumedRole_canAddPartner_withGeneratedUuid() {
final var location = RestAssured // @formatter:off
.given()
.header("current-user", "superuser-alex@hostsharing.net")
.contentType(ContentType.JSON)
.body(NEW_PARTNER_JSON_WITHOUT_UUID)
.port(port) .port(port)
.when() .when()
.post("http://localhost/api/hs/office/partners") .post("http://localhost/api/hs/office/partners")
@ -162,7 +127,8 @@ class HsOfficePartnerControllerAcceptanceTest {
.contentType(ContentType.JSON) .contentType(ContentType.JSON)
.body("uuid", isUuidValid()) .body("uuid", isUuidValid())
.body("registrationNumber", is("123456")) .body("registrationNumber", is("123456"))
.body("person.tradeName", is("Test Corp.")) .body("contact.label", is(givenContact.getLabel()))
.body("person.tradeName", is(givenPerson.getTradeName()))
.header("Location", startsWith("http://localhost")) .header("Location", startsWith("http://localhost"))
.extract().header("Location"); // @formatter:on .extract().header("Location"); // @formatter:on
@ -171,6 +137,68 @@ class HsOfficePartnerControllerAcceptanceTest {
location.substring(location.lastIndexOf('/') + 1))); location.substring(location.lastIndexOf('/') + 1)));
assertThat(newUserUuid).isNotNull(); assertThat(newUserUuid).isNotNull();
} }
@Test
void globalAdmin_canNotAddPartner_ifContactDoesNotExist() {
context.define("superuser-alex@hostsharing.net");
final var givenPerson = personRepo.findPersonByOptionalNameLike("Ostfriesische").get(0);
final var givenContactUuid = UUID.fromString("3fa85f64-5717-4562-b3fc-2c963f66afa6");
final var location = RestAssured // @formatter:off
.given()
.header("current-user", "superuser-alex@hostsharing.net")
.contentType(ContentType.JSON)
.body("""
{
"contactUuid": "%s",
"personUuid": "%s",
"registrationOffice": "Registergericht Hamburg",
"registrationNumber": "123456",
"birthName": null,
"birthday": null,
"dateOfDeath": null
}
""".formatted(givenContactUuid, givenPerson.getUuid()))
.port(port)
.when()
.post("http://localhost/api/hs/office/partners")
.then().log().all().assertThat()
.statusCode(404)
.body("message", is("cannot find contact uuid 3fa85f64-5717-4562-b3fc-2c963f66afa6"));
// @formatter:on
}
@Test
void globalAdmin_canNotAddPartner_ifPersonDoesNotExist() {
context.define("superuser-alex@hostsharing.net");
final var givenPersonUuid = UUID.fromString("3fa85f64-5717-4562-b3fc-2c963f66afa6");
final var givenContact = contactRepo.findContactByOptionalLabelLike("forth").get(0);
final var location = RestAssured // @formatter:off
.given()
.header("current-user", "superuser-alex@hostsharing.net")
.contentType(ContentType.JSON)
.body("""
{
"contactUuid": "%s",
"personUuid": "%s",
"registrationOffice": "Registergericht Hamburg",
"registrationNumber": "123456",
"birthName": null,
"birthday": null,
"dateOfDeath": null
}
""".formatted(givenContact.getUuid(), givenPersonUuid))
.port(port)
.when()
.post("http://localhost/api/hs/office/partners")
.then().log().all().assertThat()
.statusCode(404)
.body("message", is("cannot find person uuid 3fa85f64-5717-4562-b3fc-2c963f66afa6"));
// @formatter:on
}
} }
@Nested @Nested