Michael Hoennig
2022-10-26 09c19649e448d8a317fd3adfd04540aa4b6b5e79
avoid select before insert and map sub-entities in mapper
57 files modified
1058 ■■■■■ changed files
src/main/java/net/hostsharing/hsadminng/errors/CustomErrorResponse.java 6 ●●●● patch | view | raw | blame | history
src/main/java/net/hostsharing/hsadminng/errors/RestResponseEntityExceptionHandler.java 14 ●●●●● patch | view | raw | blame | history
src/main/java/net/hostsharing/hsadminng/hs/office/bankaccount/HsOfficeBankAccountController.java 5 ●●●●● patch | view | raw | blame | history
src/main/java/net/hostsharing/hsadminng/hs/office/bankaccount/HsOfficeBankAccountEntity.java 9 ●●●● patch | view | raw | blame | history
src/main/java/net/hostsharing/hsadminng/hs/office/contact/HsOfficeContactController.java 3 ●●●● patch | view | raw | blame | history
src/main/java/net/hostsharing/hsadminng/hs/office/contact/HsOfficeContactEntity.java 12 ●●●●● patch | view | raw | blame | history
src/main/java/net/hostsharing/hsadminng/hs/office/coopassets/HsOfficeCoopAssetsTransactionController.java 3 ●●●● patch | view | raw | blame | history
src/main/java/net/hostsharing/hsadminng/hs/office/coopassets/HsOfficeCoopAssetsTransactionEntity.java 6 ●●●● patch | view | raw | blame | history
src/main/java/net/hostsharing/hsadminng/hs/office/coopshares/HsOfficeCoopSharesTransactionController.java 3 ●●●● patch | view | raw | blame | history
src/main/java/net/hostsharing/hsadminng/hs/office/coopshares/HsOfficeCoopSharesTransactionEntity.java 6 ●●●● patch | view | raw | blame | history
src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorController.java 3 ●●●● patch | view | raw | blame | history
src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorEntity.java 20 ●●●● patch | view | raw | blame | history
src/main/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipController.java 5 ●●●●● patch | view | raw | blame | history
src/main/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipEntity.java 21 ●●●● patch | view | raw | blame | history
src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerController.java 6 ●●●●● patch | view | raw | blame | history
src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerDetailsEntity.java 11 ●●●● patch | view | raw | blame | history
src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerEntity.java 6 ●●●● patch | view | raw | blame | history
src/main/java/net/hostsharing/hsadminng/hs/office/person/HsOfficePersonController.java 3 ●●●● patch | view | raw | blame | history
src/main/java/net/hostsharing/hsadminng/hs/office/person/HsOfficePersonEntity.java 6 ●●●● patch | view | raw | blame | history
src/main/java/net/hostsharing/hsadminng/hs/office/relationship/HsOfficeRelationshipController.java 3 ●●●● patch | view | raw | blame | history
src/main/java/net/hostsharing/hsadminng/hs/office/relationship/HsOfficeRelationshipEntity.java 6 ●●●● patch | view | raw | blame | history
src/main/java/net/hostsharing/hsadminng/hs/office/sepamandate/HsOfficeSepaMandateController.java 8 ●●●●● patch | view | raw | blame | history
src/main/java/net/hostsharing/hsadminng/hs/office/sepamandate/HsOfficeSepaMandateEntity.java 6 ●●●● patch | view | raw | blame | history
src/main/java/net/hostsharing/hsadminng/mapper/Mapper.java 49 ●●●●● patch | view | raw | blame | history
src/main/java/net/hostsharing/hsadminng/rbac/rbacrole/RbacRoleEntity.java 3 ●●●●● patch | view | raw | blame | history
src/main/java/net/hostsharing/hsadminng/rbac/rbacuser/RbacUserEntity.java 4 ●●●● patch | view | raw | blame | history
src/main/java/net/hostsharing/hsadminng/test/cust/TestCustomerController.java 6 ●●●● patch | view | raw | blame | history
src/main/java/net/hostsharing/hsadminng/test/cust/TestCustomerEntity.java 11 ●●●● patch | view | raw | blame | history
src/main/java/net/hostsharing/hsadminng/test/pac/TestPackageEntity.java 6 ●●●● patch | view | raw | blame | history
src/test/java/net/hostsharing/hsadminng/errors/RestResponseEntityExceptionHandlerUnitTest.java 20 ●●●●● patch | view | raw | blame | history
src/test/java/net/hostsharing/hsadminng/hs/office/bankaccount/TestHsOfficeBankAccount.java 1 ●●●● patch | view | raw | blame | history
src/test/java/net/hostsharing/hsadminng/hs/office/contact/HsOfficeContactControllerAcceptanceTest.java 40 ●●●●● patch | view | raw | blame | history
src/test/java/net/hostsharing/hsadminng/hs/office/contact/HsOfficeContactRepositoryIntegrationTest.java 3 ●●●●● patch | view | raw | blame | history
src/test/java/net/hostsharing/hsadminng/hs/office/contact/TestHsOfficeContact.java 1 ●●●● patch | view | raw | blame | history
src/test/java/net/hostsharing/hsadminng/hs/office/coopassets/HsOfficeCoopAssetsTransactionRepositoryIntegrationTest.java 2 ●●●●● patch | view | raw | blame | history
src/test/java/net/hostsharing/hsadminng/hs/office/coopshares/HsOfficeCoopSharesTransactionRepositoryIntegrationTest.java 2 ●●●●● patch | view | raw | blame | history
src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorControllerAcceptanceTest.java 53 ●●●●● patch | view | raw | blame | history
src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorRepositoryIntegrationTest.java 3 ●●●●● patch | view | raw | blame | history
src/test/java/net/hostsharing/hsadminng/hs/office/debitor/TestHsOfficeDebitor.java 1 ●●●● patch | view | raw | blame | history
src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipControllerRestTest.java 53 ●●●● patch | view | raw | blame | history
src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipRepositoryIntegrationTest.java 3 ●●●●● patch | view | raw | blame | history
src/test/java/net/hostsharing/hsadminng/hs/office/membership/TestHsMembership.java 1 ●●●● patch | view | raw | blame | history
src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerControllerAcceptanceTest.java 72 ●●●●● patch | view | raw | blame | history
src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerRepositoryIntegrationTest.java 10 ●●●● patch | view | raw | blame | history
src/test/java/net/hostsharing/hsadminng/hs/office/partner/TestHsOfficePartner.java 1 ●●●● patch | view | raw | blame | history
src/test/java/net/hostsharing/hsadminng/hs/office/person/HsOfficePersonControllerAcceptanceTest.java 110 ●●●● patch | view | raw | blame | history
src/test/java/net/hostsharing/hsadminng/hs/office/person/TestHsOfficePerson.java 1 ●●●● patch | view | raw | blame | history
src/test/java/net/hostsharing/hsadminng/hs/office/relationship/HsOfficeRelationshipRepositoryIntegrationTest.java 6 ●●●●● patch | view | raw | blame | history
src/test/java/net/hostsharing/hsadminng/hs/office/sepamandate/HsOfficeSepaMandateControllerAcceptanceTest.java 63 ●●●●● patch | view | raw | blame | history
src/test/java/net/hostsharing/hsadminng/hs/office/sepamandate/HsOfficeSepaMandateRepositoryIntegrationTest.java 48 ●●●● patch | view | raw | blame | history
src/test/java/net/hostsharing/hsadminng/rbac/rbacrole/RbacRoleControllerRestTest.java 5 ●●●●● patch | view | raw | blame | history
src/test/java/net/hostsharing/hsadminng/rbac/rbacuser/RbacUserControllerRestTest.java 4 ●●●● patch | view | raw | blame | history
src/test/java/net/hostsharing/hsadminng/test/cust/TestCustomer.java 2 ●●● patch | view | raw | blame | history
src/test/java/net/hostsharing/hsadminng/test/cust/TestCustomerControllerAcceptanceTest.java 71 ●●●● patch | view | raw | blame | history
src/test/java/net/hostsharing/hsadminng/test/cust/TestCustomerRepositoryIntegrationTest.java 2 ●●●●● patch | view | raw | blame | history
src/test/java/net/hostsharing/test/JpaAttempt.java 6 ●●●●● patch | view | raw | blame | history
src/test/java/net/hostsharing/test/MapperUnitTest.java 224 ●●●● patch | view | raw | blame | history
src/main/java/net/hostsharing/hsadminng/errors/CustomErrorResponse.java
@@ -21,13 +21,13 @@
    static String firstMessageLine(final Throwable exception) {
        if (exception.getMessage() != null) {
            return firstLine(exception.getMessage());
            return line(exception.getMessage(), 0);
        }
        return "ERROR: [500] " + exception.getClass().getName();
    }
    static String firstLine(final String message) {
        return message.split("\\r|\\n|\\r\\n", 0)[0];
    static String line(final String message, final int lineNo) {
        return message.split("\\r|\\n|\\r\\n", 0)[lineNo];
    }
    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd hh:mm:ss")
src/main/java/net/hostsharing/hsadminng/errors/RestResponseEntityExceptionHandler.java
@@ -31,21 +31,25 @@
    protected ResponseEntity<CustomErrorResponse> handleConflict(
            final RuntimeException exc, final WebRequest request) {
        final var message = firstLine(NestedExceptionUtils.getMostSpecificCause(exc).getMessage());
        final var rawMessage = NestedExceptionUtils.getMostSpecificCause(exc).getMessage();
        var message = line(rawMessage, 0);
        if (message.contains("violates foreign key constraint")) {
            return errorResponse(request, HttpStatus.BAD_REQUEST, line(rawMessage, 1).replaceAll(" *Detail: *", ""));
        }
        return errorResponse(request, HttpStatus.CONFLICT, message);
    }
    @ExceptionHandler(JpaSystemException.class)
    protected ResponseEntity<CustomErrorResponse> handleJpaExceptions(
            final RuntimeException exc, final WebRequest request) {
        final var message = firstLine(NestedExceptionUtils.getMostSpecificCause(exc).getMessage());
        final var message = line(NestedExceptionUtils.getMostSpecificCause(exc).getMessage(), 0);
        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());
        final var message = line(NestedExceptionUtils.getMostSpecificCause(exc).getMessage(), 0);
        return errorResponse(request, HttpStatus.NOT_FOUND, message);
    }
@@ -54,14 +58,14 @@
            final RuntimeException exc, final WebRequest request) {
        final var message =
                userReadableEntityClassName(
                        firstLine(NestedExceptionUtils.getMostSpecificCause(exc).getMessage()));
                        line(NestedExceptionUtils.getMostSpecificCause(exc).getMessage(), 0));
        return errorResponse(request, HttpStatus.BAD_REQUEST, message);
    }
    @ExceptionHandler({ Iban4jException.class, ValidationException.class })
    protected ResponseEntity<CustomErrorResponse> handleIbanAndBicExceptions(
            final Throwable exc, final WebRequest request) {
        final var message = firstLine(NestedExceptionUtils.getMostSpecificCause(exc).getMessage());
        final var message = line(NestedExceptionUtils.getMostSpecificCause(exc).getMessage(), 0);
        return errorResponse(request, HttpStatus.BAD_REQUEST, message);
    }
src/main/java/net/hostsharing/hsadminng/hs/office/bankaccount/HsOfficeBankAccountController.java
@@ -56,14 +56,13 @@
        BicUtil.validate(body.getBic());
        final var entityToSave = mapper.map(body, HsOfficeBankAccountEntity.class);
        entityToSave.setUuid(UUID.randomUUID());
        final var saved = bankAccountRepo.save(entityToSave);
        final var uri =
                MvcUriComponentsBuilder.fromController(getClass())
                        .path("/api/hs/office/BankAccounts/{id}")
                        .buildAndExpand(entityToSave.getUuid())
                        .path("/api/hs/office/bankaccounts/{id}")
                        .buildAndExpand(saved.getUuid())
                        .toUri();
        final var mapped = mapper.map(saved, HsOfficeBankAccountResource.class);
        return ResponseEntity.created(uri).body(mapped);
src/main/java/net/hostsharing/hsadminng/hs/office/bankaccount/HsOfficeBankAccountEntity.java
@@ -2,11 +2,13 @@
import lombok.*;
import lombok.experimental.FieldNameConstants;
import net.hostsharing.hsadminng.errors.DisplayName;
import net.hostsharing.hsadminng.stringify.Stringify;
import net.hostsharing.hsadminng.stringify.Stringifyable;
import net.hostsharing.hsadminng.errors.DisplayName;
import org.hibernate.annotations.GenericGenerator;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import java.util.UUID;
@@ -29,7 +31,10 @@
            .withProp(Fields.iban, HsOfficeBankAccountEntity::getIban)
            .withProp(Fields.bic, HsOfficeBankAccountEntity::getBic);
    private @Id UUID uuid;
    @Id
    @GeneratedValue(generator = "UUID")
    @GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator")
    private UUID uuid;
    private String holder;
    private String iban;
src/main/java/net/hostsharing/hsadminng/hs/office/contact/HsOfficeContactController.java
@@ -52,14 +52,13 @@
        context.define(currentUser, assumedRoles);
        final var entityToSave = mapper.map(body, HsOfficeContactEntity.class);
        entityToSave.setUuid(UUID.randomUUID());
        final var saved = contactRepo.save(entityToSave);
        final var uri =
                MvcUriComponentsBuilder.fromController(getClass())
                        .path("/api/hs/office/contacts/{id}")
                        .buildAndExpand(entityToSave.getUuid())
                        .buildAndExpand(saved.getUuid())
                        .toUri();
        final var mapped = mapper.map(saved, HsOfficeContactResource.class);
        return ResponseEntity.created(uri).body(mapped);
src/main/java/net/hostsharing/hsadminng/hs/office/contact/HsOfficeContactEntity.java
@@ -5,11 +5,9 @@
import net.hostsharing.hsadminng.errors.DisplayName;
import net.hostsharing.hsadminng.stringify.Stringify;
import net.hostsharing.hsadminng.stringify.Stringifyable;
import org.hibernate.annotations.GenericGenerator;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.*;
import java.util.UUID;
import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
@@ -29,7 +27,11 @@
            .withProp(Fields.label, HsOfficeContactEntity::getLabel)
            .withProp(Fields.emailAddresses, HsOfficeContactEntity::getEmailAddresses);
    private @Id UUID uuid;
    @Id
    @GeneratedValue(generator = "UUID")
    @GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator")
    private UUID uuid;
    private String label;
    @Column(name = "postaladdress")
src/main/java/net/hostsharing/hsadminng/hs/office/coopassets/HsOfficeCoopAssetsTransactionController.java
@@ -66,14 +66,13 @@
        validate(requestBody);
        final var entityToSave = mapper.map(requestBody, HsOfficeCoopAssetsTransactionEntity.class);
        entityToSave.setUuid(UUID.randomUUID());
        final var saved = coopAssetsTransactionRepo.save(entityToSave);
        final var uri =
                MvcUriComponentsBuilder.fromController(getClass())
                        .path("/api/hs/office/coopassetstransactions/{id}")
                        .buildAndExpand(entityToSave.getUuid())
                        .buildAndExpand(saved.getUuid())
                        .toUri();
        final var mapped = mapper.map(saved, HsOfficeCoopAssetsTransactionResource.class);
        return ResponseEntity.created(uri).body(mapped);
src/main/java/net/hostsharing/hsadminng/hs/office/coopassets/HsOfficeCoopAssetsTransactionEntity.java
@@ -6,6 +6,7 @@
import net.hostsharing.hsadminng.hs.office.membership.HsOfficeMembershipEntity;
import net.hostsharing.hsadminng.stringify.Stringify;
import net.hostsharing.hsadminng.stringify.Stringifyable;
import org.hibernate.annotations.GenericGenerator;
import org.hibernate.annotations.Type;
import org.hibernate.annotations.TypeDef;
@@ -40,7 +41,10 @@
            .withSeparator(", ")
            .quotedValues(false);
    private @Id UUID uuid;
    @Id
    @GeneratedValue(generator = "UUID")
    @GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator")
    private UUID uuid;
    @ManyToOne
    @JoinColumn(name = "membershipuuid")
src/main/java/net/hostsharing/hsadminng/hs/office/coopshares/HsOfficeCoopSharesTransactionController.java
@@ -67,14 +67,13 @@
        validate(requestBody);
        final var entityToSave = mapper.map(requestBody, HsOfficeCoopSharesTransactionEntity.class);
        entityToSave.setUuid(UUID.randomUUID());
        final var saved = coopSharesTransactionRepo.save(entityToSave);
        final var uri =
                MvcUriComponentsBuilder.fromController(getClass())
                        .path("/api/hs/office/coopsharestransactions/{id}")
                        .buildAndExpand(entityToSave.getUuid())
                        .buildAndExpand(saved.getUuid())
                        .toUri();
        final var mapped = mapper.map(saved, HsOfficeCoopSharesTransactionResource.class);
        return ResponseEntity.created(uri).body(mapped);
src/main/java/net/hostsharing/hsadminng/hs/office/coopshares/HsOfficeCoopSharesTransactionEntity.java
@@ -6,6 +6,7 @@
import net.hostsharing.hsadminng.stringify.Stringifyable;
import net.hostsharing.hsadminng.errors.DisplayName;
import net.hostsharing.hsadminng.hs.office.membership.HsOfficeMembershipEntity;
import org.hibernate.annotations.GenericGenerator;
import org.hibernate.annotations.Type;
import org.hibernate.annotations.TypeDef;
@@ -38,7 +39,10 @@
            .withSeparator(", ")
            .quotedValues(false);
    private @Id UUID uuid;
    @Id
    @GeneratedValue(generator = "UUID")
    @GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator")
    private UUID uuid;
    @ManyToOne
    @JoinColumn(name = "membershipuuid")
src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorController.java
@@ -59,14 +59,13 @@
        context.define(currentUser, assumedRoles);
        final var entityToSave = mapper.map(body, HsOfficeDebitorEntity.class);
        entityToSave.setUuid(UUID.randomUUID());
        final var saved = debitorRepo.save(entityToSave);
        final var uri =
                MvcUriComponentsBuilder.fromController(getClass())
                        .path("/api/hs/office/debitors/{id}")
                        .buildAndExpand(entityToSave.getUuid())
                        .buildAndExpand(saved.getUuid())
                        .toUri();
        final var mapped = mapper.map(saved, HsOfficeDebitorResource.class);
        return ResponseEntity.created(uri).body(mapped);
src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorEntity.java
@@ -7,6 +7,7 @@
import net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountEntity;
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity;
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity;
import org.hibernate.annotations.GenericGenerator;
import javax.persistence.*;
import java.util.UUID;
@@ -30,27 +31,34 @@
                    .withSeparator(": ")
                    .quotedValues(false);
    private @Id UUID uuid;
    @Id
    @GeneratedValue(generator = "UUID")
    @GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator")
    private UUID uuid;
    @ManyToOne
    @JoinColumn(name = "partneruuid")
    private HsOfficePartnerEntity partner;
    private @Column(name = "debitornumber") Integer debitorNumber;
    @Column(name = "debitornumber")
    private Integer debitorNumber;
    @ManyToOne
    @JoinColumn(name = "billingcontactuuid")
    private HsOfficeContactEntity billingContact;
    private @Column(name = "vatid") String vatId;
    private @Column(name = "vatcountrycode") String vatCountryCode;
    private @Column(name = "vatbusiness") boolean vatBusiness;
    @Column(name = "vatid")
    private String vatId;
    @Column(name = "vatcountrycode")
    private String vatCountryCode;
    @Column(name = "vatbusiness")
    private boolean vatBusiness;
    @ManyToOne
    @JoinColumn(name = "refundbankaccountuuid")
    private HsOfficeBankAccountEntity refundBankAccount;
    @Override
    public String toString() {
src/main/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipController.java
@@ -61,14 +61,13 @@
        context.define(currentUser, assumedRoles);
        final var entityToSave = mapper.map(body, HsOfficeMembershipEntity.class);
        entityToSave.setUuid(UUID.randomUUID());
        final var saved = membershipRepo.save(entityToSave);
        final var uri =
                MvcUriComponentsBuilder.fromController(getClass())
                        .path("/api/hs/office/Memberships/{id}")
                        .buildAndExpand(entityToSave.getUuid())
                        .path("/api/hs/office/memberships/{id}")
                        .buildAndExpand(saved.getUuid())
                        .toUri();
        final var mapped = mapper.map(saved, HsOfficeMembershipResource.class,
                SEPA_MANDATE_ENTITY_TO_RESOURCE_POSTMAPPER);
src/main/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipEntity.java
@@ -9,12 +9,11 @@
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity;
import net.hostsharing.hsadminng.stringify.Stringify;
import net.hostsharing.hsadminng.stringify.Stringifyable;
import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchMode;
import org.hibernate.annotations.Type;
import org.hibernate.annotations.TypeDef;
import org.hibernate.annotations.*;
import javax.persistence.*;
import javax.persistence.Entity;
import javax.persistence.Table;
import java.time.LocalDate;
import java.util.UUID;
@@ -48,7 +47,10 @@
            .withSeparator(", ")
            .quotedValues(false);
    private @Id UUID uuid;
    @Id
    @GeneratedValue(generator = "UUID")
    @GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator")
    private UUID uuid;
    @ManyToOne
    @JoinColumn(name = "partneruuid")
@@ -63,7 +65,7 @@
    private int memberNumber;
    @Column(name = "validity", columnDefinition = "daterange")
    private Range<LocalDate> validity = Range.infinite(LocalDate.class);
    private Range<LocalDate> validity;
    @Column(name = "reasonfortermination")
    @Enumerated(EnumType.STRING)
@@ -78,6 +80,13 @@
        validity = toPostgresDateRange(getValidity().lower(), validTo);
    }
    public Range<LocalDate> getValidity() {
        if ( validity == null ) {
            validity  = Range.infinite(LocalDate.class);
        };
        return validity;
    }
    @Override
    public String toString() {
        return stringify.apply(this);
src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerController.java
@@ -1,6 +1,7 @@
package net.hostsharing.hsadminng.hs.office.partner;
import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficePartnersApi;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficePartnerInsertResource;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficePartnerPatchResource;
@@ -56,16 +57,13 @@
        context.define(currentUser, assumedRoles);
        final var entityToSave = mapper.map(body, HsOfficePartnerEntity.class);
        entityToSave.setUuid(UUID.randomUUID());
        entityToSave.setDetails(mapper.map(body.getDetails(), HsOfficePartnerDetailsEntity.class));
        entityToSave.getDetails().setUuid(UUID.randomUUID());
        final var saved = partnerRepo.save(entityToSave);
        final var uri =
                MvcUriComponentsBuilder.fromController(getClass())
                        .path("/api/hs/office/partners/{id}")
                        .buildAndExpand(entityToSave.getUuid())
                        .buildAndExpand(saved.getUuid())
                        .toUri();
        final var mapped = mapper.map(saved, HsOfficePartnerResource.class);
        return ResponseEntity.created(uri).body(mapped);
src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerDetailsEntity.java
@@ -4,11 +4,9 @@
import net.hostsharing.hsadminng.stringify.Stringify;
import net.hostsharing.hsadminng.stringify.Stringifyable;
import net.hostsharing.hsadminng.errors.DisplayName;
import org.hibernate.annotations.GenericGenerator;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.*;
import java.time.LocalDate;
import java.util.UUID;
@@ -35,7 +33,10 @@
            .withSeparator(", ")
            .quotedValues(false);
    private @Id UUID uuid;
    @Id
    @GeneratedValue(generator = "UUID")
    @GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator")
    private UUID uuid;
    private @Column(name = "registrationoffice") String registrationOffice;
    private @Column(name = "registrationnumber") String registrationNumber;
src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerEntity.java
@@ -6,6 +6,7 @@
import net.hostsharing.hsadminng.stringify.Stringifyable;
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity;
import org.hibernate.annotations.GenericGenerator;
import org.hibernate.annotations.NotFound;
import org.hibernate.annotations.NotFoundAction;
@@ -30,7 +31,10 @@
            .withSeparator(": ")
            .quotedValues(false);
    private @Id UUID uuid;
    @Id
    @GeneratedValue(generator = "UUID")
    @GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator")
    private UUID uuid;
    @ManyToOne
    @JoinColumn(name = "personuuid", nullable = false)
src/main/java/net/hostsharing/hsadminng/hs/office/person/HsOfficePersonController.java
@@ -52,14 +52,13 @@
        context.define(currentUser, assumedRoles);
        final var entityToSave = mapper.map(body, HsOfficePersonEntity.class);
        entityToSave.setUuid(UUID.randomUUID());
        final var saved = personRepo.save(entityToSave);
        final var uri =
                MvcUriComponentsBuilder.fromController(getClass())
                        .path("/api/hs/office/persons/{id}")
                        .buildAndExpand(entityToSave.getUuid())
                        .buildAndExpand(saved.getUuid())
                        .toUri();
        final var mapped = mapper.map(saved, HsOfficePersonResource.class);
        return ResponseEntity.created(uri).body(mapped);
src/main/java/net/hostsharing/hsadminng/hs/office/person/HsOfficePersonEntity.java
@@ -7,6 +7,7 @@
import net.hostsharing.hsadminng.stringify.Stringify;
import net.hostsharing.hsadminng.stringify.Stringifyable;
import org.apache.commons.lang3.StringUtils;
import org.hibernate.annotations.GenericGenerator;
import org.hibernate.annotations.Type;
import org.hibernate.annotations.TypeDef;
@@ -36,7 +37,10 @@
            .withProp(Fields.familyName, HsOfficePersonEntity::getFamilyName)
            .withProp(Fields.givenName, HsOfficePersonEntity::getGivenName);
    private @Id UUID uuid;
    @Id
    @GeneratedValue(generator = "UUID")
    @GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator")
    private UUID uuid;
    @Column(name = "persontype")
    @Enumerated(EnumType.STRING)
src/main/java/net/hostsharing/hsadminng/hs/office/relationship/HsOfficeRelationshipController.java
@@ -69,7 +69,6 @@
        final var entityToSave = new HsOfficeRelationshipEntity();
        entityToSave.setRelType(HsOfficeRelationshipType.valueOf(body.getRelType()));
        entityToSave.setUuid(UUID.randomUUID());
        entityToSave.setRelAnchor(relHolderRepo.findByUuid(body.getRelAnchorUuid()).orElseThrow(
                () -> new NoSuchElementException("cannot find relAnchorUuid " + body.getRelAnchorUuid())
        ));
@@ -85,7 +84,7 @@
        final var uri =
                MvcUriComponentsBuilder.fromController(getClass())
                        .path("/api/hs/office/relationships/{id}")
                        .buildAndExpand(entityToSave.getUuid())
                        .buildAndExpand(saved.getUuid())
                        .toUri();
        final var mapped = mapper.map(saved, HsOfficeRelationshipResource.class,
                RELATIONSHIP_ENTITY_TO_RESOURCE_POSTMAPPER);
src/main/java/net/hostsharing/hsadminng/hs/office/relationship/HsOfficeRelationshipEntity.java
@@ -6,6 +6,7 @@
import net.hostsharing.hsadminng.stringify.Stringify;
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity;
import org.hibernate.annotations.GenericGenerator;
import org.hibernate.annotations.Type;
import org.hibernate.annotations.TypeDef;
@@ -34,7 +35,10 @@
            .withProp(Fields.relHolder, HsOfficeRelationshipEntity::getRelHolder)
            .withProp(Fields.contact, HsOfficeRelationshipEntity::getContact);
    private @Id UUID uuid;
    @Id
    @GeneratedValue(generator = "UUID")
    @GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator")
    private UUID uuid;
    @ManyToOne
    @JoinColumn(name = "relanchoruuid")
src/main/java/net/hostsharing/hsadminng/hs/office/sepamandate/HsOfficeSepaMandateController.java
@@ -1,12 +1,11 @@
package net.hostsharing.hsadminng.hs.office.sepamandate;
import com.vladmihalcea.hibernate.type.range.Range;
import net.hostsharing.hsadminng.mapper.Mapper;
import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficeSepaMandatesApi;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeSepaMandateInsertResource;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeSepaMandatePatchResource;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeSepaMandateResource;
import net.hostsharing.hsadminng.mapper.Mapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.transaction.annotation.Transactional;
@@ -62,14 +61,13 @@
        context.define(currentUser, assumedRoles);
        final var entityToSave = mapper.map(body, HsOfficeSepaMandateEntity.class, SEPA_MANDATE_RESOURCE_TO_ENTITY_POSTMAPPER);
        entityToSave.setUuid(UUID.randomUUID());
        final var saved = SepaMandateRepo.save(entityToSave);
        final var uri =
                MvcUriComponentsBuilder.fromController(getClass())
                        .path("/api/hs/office/SepaMandates/{id}")
                        .buildAndExpand(entityToSave.getUuid())
                        .path("/api/hs/office/sepamandates/{id}")
                        .buildAndExpand(saved.getUuid())
                        .toUri();
        final var mapped = mapper.map(saved, HsOfficeSepaMandateResource.class,
                SEPA_MANDATE_ENTITY_TO_RESOURCE_POSTMAPPER);
src/main/java/net/hostsharing/hsadminng/hs/office/sepamandate/HsOfficeSepaMandateEntity.java
@@ -8,6 +8,7 @@
import net.hostsharing.hsadminng.stringify.Stringifyable;
import net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountEntity;
import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorEntity;
import org.hibernate.annotations.GenericGenerator;
import org.hibernate.annotations.TypeDef;
import javax.persistence.*;
@@ -37,7 +38,10 @@
            .withSeparator(", ")
            .quotedValues(false);
    private @Id UUID uuid;
    @Id
    @GeneratedValue(generator = "UUID")
    @GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator")
    private UUID uuid;
    @ManyToOne
    @JoinColumn(name = "debitoruuid")
src/main/java/net/hostsharing/hsadminng/mapper/Mapper.java
@@ -1,7 +1,14 @@
package net.hostsharing.hsadminng.mapper;
import net.hostsharing.hsadminng.errors.DisplayName;
import org.modelmapper.ModelMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.ReflectionUtils;
import javax.persistence.EntityManager;
import javax.persistence.ManyToOne;
import javax.validation.ValidationException;
import java.lang.reflect.Field;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.stream.Collectors;
@@ -10,6 +17,9 @@
 * A nicer API for ModelMapper.
 */
public class Mapper extends ModelMapper {
    @Autowired
    EntityManager em;
    public Mapper() {
        getConfiguration().setAmbiguityIgnored(true);
@@ -32,6 +42,45 @@
                .collect(Collectors.toList());
    }
    @Override
    public <D> D map(final Object source, final Class<D> destinationType) {
        final var target = super.map(source, destinationType);
        for (Field f : destinationType.getDeclaredFields()) {
            if (f.getAnnotation(ManyToOne.class) == null) {
                continue;
            }
            ReflectionUtils.makeAccessible(f);
            final var subEntity = ReflectionUtils.getField(f, target);
            if (subEntity == null) {
                continue;
            }
            final var subEntityUuidField = ReflectionUtils.findField(f.getType(), "uuid");
            if (subEntityUuidField == null) {
                continue;
            }
            ReflectionUtils.makeAccessible(subEntityUuidField);
            final var subEntityUuid = ReflectionUtils.getField(subEntityUuidField, subEntity);
            if (subEntityUuid == null) {
                continue;
            }
            ReflectionUtils.setField(f, target, findEntityById(f.getType(), subEntityUuid));
        }
        return target;
    }
    private Object findEntityById(final Class<?> entityClass, final Object subEntityUuid) {
        // using getReference would be more efficent, but results in very technical error messages
        final var entity = em.find(entityClass, subEntityUuid);
        if (entity != null) {
            return entity;
        }
        final var displayNameAnnot = entityClass.getAnnotation(DisplayName.class);
        final var displayName = displayNameAnnot != null ? displayNameAnnot.value() : entityClass.getSimpleName();
        throw new ValidationException("Unable to find %s with uuid %s".formatted(
                displayName, subEntityUuid
        ));
    }
    public <S, T> T map(final S source, final Class<T> targetClass, final BiConsumer<S, T> postMapper) {
        if (source == null) {
            return null;
src/main/java/net/hostsharing/hsadminng/rbac/rbacrole/RbacRoleEntity.java
@@ -2,6 +2,7 @@
import lombok.*;
import org.hibernate.annotations.Formula;
import org.hibernate.annotations.GenericGenerator;
import org.springframework.data.annotation.Immutable;
import javax.persistence.*;
@@ -18,6 +19,8 @@
public class RbacRoleEntity {
    @Id
    @GeneratedValue(generator = "UUID")
    @GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator")
    private UUID uuid;
    @Column(name = "objectuuid")
src/main/java/net/hostsharing/hsadminng/rbac/rbacuser/RbacUserEntity.java
@@ -1,9 +1,11 @@
package net.hostsharing.hsadminng.rbac.rbacuser;
import lombok.*;
import org.hibernate.annotations.GenericGenerator;
import org.springframework.data.annotation.Immutable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import java.time.LocalDateTime;
@@ -25,6 +27,8 @@
    private static DateTimeFormatter DATE_FORMAT_WITH_FULLHOUR = DateTimeFormatter.ofPattern("MM-dd-yyyy HH");
    @Id
    @GeneratedValue(generator = "UUID")
    @GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator")
    private UUID uuid;
    private String name;
src/main/java/net/hostsharing/hsadminng/test/cust/TestCustomerController.java
@@ -48,16 +48,12 @@
        context.define(currentUser, assumedRoles);
        if (customer.getUuid() == null) {
            customer.setUuid(UUID.randomUUID());
        }
        final var saved = testCustomerRepository.save(mapper.map(customer, TestCustomerEntity.class));
        final var uri =
                MvcUriComponentsBuilder.fromController(getClass())
                        .path("/api/test/customers/{id}")
                        .buildAndExpand(customer.getUuid())
                        .buildAndExpand(saved.getUuid())
                        .toUri();
        return ResponseEntity.created(uri).body(mapper.map(saved, TestCustomerResource.class));
    }
src/main/java/net/hostsharing/hsadminng/test/cust/TestCustomerEntity.java
@@ -4,6 +4,7 @@
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.hibernate.annotations.GenericGenerator;
import javax.persistence.*;
import java.util.UUID;
@@ -15,8 +16,14 @@
@NoArgsConstructor
@AllArgsConstructor
public class TestCustomerEntity {
    private @Id UUID uuid;
    @Id
    @GeneratedValue(generator = "UUID")
    @GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator")
    private UUID uuid;
    private String prefix;
    private int reference;
    private @Column(name="adminusername")String adminUserName;
    @Column(name="adminusername")
    private String adminUserName;
}
src/main/java/net/hostsharing/hsadminng/test/pac/TestPackageEntity.java
@@ -5,6 +5,7 @@
import lombok.NoArgsConstructor;
import lombok.Setter;
import net.hostsharing.hsadminng.test.cust.TestCustomerEntity;
import org.hibernate.annotations.GenericGenerator;
import javax.persistence.*;
import java.util.UUID;
@@ -17,7 +18,10 @@
@AllArgsConstructor
public class TestPackageEntity {
    private @Id UUID uuid;
    @Id
    @GeneratedValue(generator = "UUID")
    @GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator")
    private UUID uuid;
    @Version
    private int version;
src/test/java/net/hostsharing/hsadminng/errors/RestResponseEntityExceptionHandlerUnitTest.java
@@ -9,6 +9,7 @@
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.orm.jpa.JpaObjectRetrievalFailureException;
import org.springframework.orm.jpa.JpaSystemException;
import org.springframework.validation.BindingResult;
@@ -44,6 +45,25 @@
    }
    @Test
    void handleForeignKeyViolation() {
        // given
        final var givenException = new DataIntegrityViolationException("""
            ... violates foreign key constraint ...
               Detail:   Second Line
            Third Line
            """);
        final var givenWebRequest = mock(WebRequest.class);
        // when
        final var errorResponse = exceptionHandler.handleConflict(givenException, givenWebRequest);
        // then
        assertThat(errorResponse.getStatusCodeValue()).isEqualTo(400);
        assertThat(errorResponse.getBody()).isNotNull()
                .extracting(CustomErrorResponse::getMessage).isEqualTo("Second Line");
    }
    @Test
    void jpaExceptionWithKnownErrorCode() {
        // given
        final var givenException = new JpaSystemException(new RuntimeException(
src/test/java/net/hostsharing/hsadminng/hs/office/bankaccount/TestHsOfficeBankAccount.java
@@ -9,7 +9,6 @@
    static public HsOfficeBankAccountEntity hsOfficeBankAccount(final String holder, final String iban, final String bic) {
        return HsOfficeBankAccountEntity.builder()
                .uuid(UUID.randomUUID())
                .holder(holder)
                .iban(iban)
                .bic(bic)
src/test/java/net/hostsharing/hsadminng/hs/office/contact/HsOfficeContactControllerAcceptanceTest.java
@@ -17,8 +17,7 @@
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.transaction.annotation.Transactional;
import java.util.HashSet;
import java.util.Set;
import javax.persistence.EntityManager;
import java.util.UUID;
import static net.hostsharing.test.IsValidUuidMatcher.isUuidValid;
@@ -49,7 +48,8 @@
    @Autowired
    JpaAttempt jpaAttempt;
    Set<UUID> tempContactUuids = new HashSet<>();
    @Autowired
    EntityManager em;
    @Nested
    @Accepts({ "Contact:F(Find)" })
@@ -103,7 +103,7 @@
                        .contentType(ContentType.JSON)
                        .body("""
                               {
                                   "label": "Test Contact",
                                   "label": "Temp Contact",
                                   "emailAddresses": "test@example.org"
                                 }
                            """)
@@ -114,14 +114,14 @@
                        .statusCode(201)
                        .contentType(ContentType.JSON)
                        .body("uuid", isUuidValid())
                        .body("label", is("Test Contact"))
                        .body("label", is("Temp Contact"))
                        .body("emailAddresses", is("test@example.org"))
                        .header("Location", startsWith("http://localhost"))
                    .extract().header("Location");  // @formatter:on
            // finally, the new contact can be accessed under the generated UUID
            final var newUserUuid = toCleanup(UUID.fromString(
                    location.substring(location.lastIndexOf('/') + 1)));
            final var newUserUuid = UUID.fromString(
                    location.substring(location.lastIndexOf('/') + 1));
            assertThat(newUserUuid).isNotNull();
        }
    }
@@ -208,7 +208,7 @@
                    .contentType(ContentType.JSON)
                    .body("""
                       {
                           "label": "patched contact",
                           "label": "Temp patched contact",
                           "emailAddresses": "patched@example.org",
                           "postalAddress": "Patched Address",
                           "phoneNumbers": "+01 100 123456"
@@ -221,7 +221,7 @@
                    .statusCode(200)
                    .contentType(ContentType.JSON)
                    .body("uuid", isUuidValid())
                    .body("label", is("patched contact"))
                    .body("label", is("Temp patched contact"))
                    .body("emailAddresses", is("patched@example.org"))
                    .body("postalAddress", is("Patched Address"))
                    .body("phoneNumbers", is("+01 100 123456"));
@@ -231,7 +231,7 @@
            context.define("superuser-alex@hostsharing.net");
            assertThat(contactRepo.findByUuid(givenContact.getUuid())).isPresent().get()
                    .matches(person -> {
                        assertThat(person.getLabel()).isEqualTo("patched contact");
                        assertThat(person.getLabel()).isEqualTo("Temp patched contact");
                        assertThat(person.getEmailAddresses()).isEqualTo("patched@example.org");
                        assertThat(person.getPostalAddress()).isEqualTo("Patched Address");
                        assertThat(person.getPhoneNumbers()).isEqualTo("+01 100 123456");
@@ -353,28 +353,16 @@
                    .phoneNumbers("+01 200 " + RandomStringUtils.randomNumeric(8))
                    .build();
            toCleanup(newContact.getUuid());
            return contactRepo.save(newContact);
        }).assertSuccessful().returnedValue();
    }
    private UUID toCleanup(final UUID tempContactUuid) {
        tempContactUuids.add(tempContactUuid);
        return tempContactUuid;
    }
    @BeforeEach
    @AfterEach
    void cleanup() {
        tempContactUuids.forEach(uuid -> {
            jpaAttempt.transacted(() -> {
                context.define("superuser-alex@hostsharing.net", null);
                System.out.println("DELETING temporary contact: " + uuid);
                final var count = contactRepo.deleteByUuid(uuid);
                System.out.println("DELETED temporary contact: " + uuid + (count > 0 ? " successful" : " failed"));
            }).assertSuccessful();
        });
        jpaAttempt.transacted(() -> {
            context.define("superuser-alex@hostsharing.net", null);
            em.createQuery("DELETE FROM HsOfficeContactEntity c WHERE c.label LIKE 'Temp %'").executeUpdate();
        }).assertSuccessful();
    }
}
src/test/java/net/hostsharing/hsadminng/hs/office/contact/HsOfficeContactRepositoryIntegrationTest.java
@@ -54,9 +54,6 @@
    @MockBean
    HttpServletRequest request;
    @Container
    Container postgres;
    @Nested
    class CreateContact {
src/test/java/net/hostsharing/hsadminng/hs/office/contact/TestHsOfficeContact.java
@@ -8,7 +8,6 @@
    static public HsOfficeContactEntity hsOfficeContact(final String label, final String emailAddr) {
        return HsOfficeContactEntity.builder()
                .uuid(UUID.randomUUID())
                .label(label)
                .postalAddress("address of " + label)
                .emailAddresses(emailAddr)
src/test/java/net/hostsharing/hsadminng/hs/office/coopassets/HsOfficeCoopAssetsTransactionRepositoryIntegrationTest.java
@@ -71,7 +71,6 @@
            // when
            final var result = attempt(em, () -> {
                final var newCoopAssetsTransaction = HsOfficeCoopAssetsTransactionEntity.builder()
                        .uuid(UUID.randomUUID())
                        .membership(givenMembership)
                        .transactionType(HsOfficeCoopAssetsTransactionType.DEPOSIT)
                        .assetValue(new BigDecimal("128.00"))
@@ -104,7 +103,6 @@
                        null,
                        10001).get(0);
                final var newCoopAssetsTransaction = HsOfficeCoopAssetsTransactionEntity.builder()
                        .uuid(UUID.randomUUID())
                        .membership(givenMembership)
                        .transactionType(HsOfficeCoopAssetsTransactionType.DEPOSIT)
                        .assetValue(new BigDecimal("128.00"))
src/test/java/net/hostsharing/hsadminng/hs/office/coopshares/HsOfficeCoopSharesTransactionRepositoryIntegrationTest.java
@@ -69,7 +69,6 @@
            // when
            final var result = attempt(em, () -> {
                final var newCoopSharesTransaction = HsOfficeCoopSharesTransactionEntity.builder()
                        .uuid(UUID.randomUUID())
                        .membership(givenMembership)
                        .transactionType(HsOfficeCoopSharesTransactionType.SUBSCRIPTION)
                        .shareCount(4)
@@ -102,7 +101,6 @@
                        null,
                        10001).get(0);
                final var newCoopSharesTransaction = HsOfficeCoopSharesTransactionEntity.builder()
                        .uuid(UUID.randomUUID())
                        .membership(givenMembership)
                        .transactionType(HsOfficeCoopSharesTransactionType.SUBSCRIPTION)
                        .shareCount(4)
src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorControllerAcceptanceTest.java
@@ -2,15 +2,16 @@
import io.restassured.RestAssured;
import io.restassured.http.ContentType;
import net.hostsharing.test.Accepts;
import net.hostsharing.hsadminng.HsadminNgApplication;
import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountRepository;
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRepository;
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerRepository;
import net.hostsharing.test.Accepts;
import net.hostsharing.test.JpaAttempt;
import org.json.JSONException;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
@@ -18,8 +19,7 @@
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.transaction.annotation.Transactional;
import java.util.HashSet;
import java.util.Set;
import javax.persistence.EntityManager;
import java.util.UUID;
import static net.hostsharing.test.IsValidUuidMatcher.isUuidValid;
@@ -35,7 +35,8 @@
@Transactional
class HsOfficeDebitorControllerAcceptanceTest {
    private static int nextDebitorNumber = 20001;
    private static final int LOWEST_TEMP_DEBITOR_NUMBER = 20000;
    private static int nextDebitorNumber = LOWEST_TEMP_DEBITOR_NUMBER;
    @LocalServerPort
    private Integer port;
@@ -58,7 +59,8 @@
    @Autowired
    JpaAttempt jpaAttempt;
    Set<UUID> tempDebitorUuids = new HashSet<>();
    @Autowired
    EntityManager em;
    @Nested
    @Accepts({ "Debitor:F(Find)" })
@@ -164,7 +166,7 @@
                                   "vatBusiness": true,
                                   "refundBankAccountUuid": "%s"
                                 }
                            """.formatted( givenPartner.getUuid(), givenContact.getUuid(), nextDebitorNumber++, givenBankAccount.getUuid()))
                            """.formatted( givenPartner.getUuid(), givenContact.getUuid(), ++nextDebitorNumber, givenBankAccount.getUuid()))
                        .port(port)
                    .when()
                        .post("http://localhost/api/hs/office/debitors")
@@ -180,8 +182,8 @@
                    .extract().header("Location");  // @formatter:on
            // finally, the new debitor can be accessed under the generated UUID
            final var newUserUuid = toCleanup(UUID.fromString(
                    location.substring(location.lastIndexOf('/') + 1)));
            final var newUserUuid = UUID.fromString(
                    location.substring(location.lastIndexOf('/') + 1));
            assertThat(newUserUuid).isNotNull();
        }
@@ -202,7 +204,7 @@
                                   "billingContactUuid": "%s",
                                   "debitorNumber": "%s"
                                 }
                            """.formatted( givenPartner.getUuid(), givenContact.getUuid(), nextDebitorNumber++))
                            """.formatted( givenPartner.getUuid(), givenContact.getUuid(), ++nextDebitorNumber))
                    .port(port)
                .when()
                    .post("http://localhost/api/hs/office/debitors")
@@ -220,8 +222,8 @@
                    .extract().header("Location");  // @formatter:on
            // finally, the new debitor can be accessed under the generated UUID
            final var newUserUuid = toCleanup(UUID.fromString(
                    location.substring(location.lastIndexOf('/') + 1)));
            final var newUserUuid = UUID.fromString(
                    location.substring(location.lastIndexOf('/') + 1));
            assertThat(newUserUuid).isNotNull();
        }
@@ -245,7 +247,7 @@
                                   "vatCountryCode": "DE",
                                   "vatBusiness": true
                                 }
                            """.formatted( givenPartner.getUuid(), givenContactUuid, nextDebitorNumber++))
                            """.formatted( givenPartner.getUuid(), givenContactUuid, ++nextDebitorNumber))
                    .port(port)
                .when()
                    .post("http://localhost/api/hs/office/debitors")
@@ -275,7 +277,7 @@
                                   "vatCountryCode": "DE",
                                   "vatBusiness": true
                                 }
                            """.formatted( givenPartnerUuid, givenContact.getUuid(), nextDebitorNumber++))
                            """.formatted( givenPartnerUuid, givenContact.getUuid(), ++nextDebitorNumber))
                    .port(port)
                .when()
                    .post("http://localhost/api/hs/office/debitors")
@@ -520,33 +522,24 @@
            final var givenPartner = partnerRepo.findPartnerByOptionalNameLike("Fourth").get(0);
            final var givenContact = contactRepo.findContactByOptionalLabelLike("forth contact").get(0);
            final var newDebitor = HsOfficeDebitorEntity.builder()
                    .uuid(UUID.randomUUID())
                    .debitorNumber(nextDebitorNumber++)
                    .debitorNumber(++nextDebitorNumber)
                    .partner(givenPartner)
                    .billingContact(givenContact)
                    .build();
            toCleanup(newDebitor.getUuid());
            return debitorRepo.save(newDebitor);
        }).assertSuccessful().returnedValue();
    }
    private UUID toCleanup(final UUID tempDebitorUuid) {
        tempDebitorUuids.add(tempDebitorUuid);
        return tempDebitorUuid;
    }
    @BeforeEach
    @AfterEach
    void cleanup() {
        tempDebitorUuids.forEach(uuid -> {
            jpaAttempt.transacted(() -> {
                context.define("superuser-alex@hostsharing.net", null);
                System.out.println("DELETING temporary debitor: " + uuid);
                final var count = debitorRepo.deleteByUuid(uuid);
                System.out.println("DELETED temporary debitor: " + uuid + (count > 0 ? " successful" : " failed"));
            });
        jpaAttempt.transacted(() -> {
            context.define("superuser-alex@hostsharing.net");
            final var count = em.createQuery(
                            "DELETE FROM HsOfficeDebitorEntity d WHERE d.debitorNumber > " + LOWEST_TEMP_DEBITOR_NUMBER)
                    .executeUpdate();
            System.out.printf("deleted %d entities%n", count);
        });
    }
}
src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorRepositoryIntegrationTest.java
@@ -79,7 +79,6 @@
            // when
            final var result = attempt(em, () -> {
                final var newDebitor = HsOfficeDebitorEntity.builder()
                        .uuid(UUID.randomUUID())
                        .debitorNumber(20001)
                        .partner(givenPartner)
                        .billingContact(givenContact)
@@ -112,7 +111,6 @@
                final var givenPartner = partnerRepo.findPartnerByOptionalNameLike("Fourth").get(0);
                final var givenContact = contactRepo.findContactByOptionalLabelLike("forth contact").get(0);
                final var newDebitor = HsOfficeDebitorEntity.builder()
                        .uuid(UUID.randomUUID())
                        .debitorNumber(20002)
                        .partner(givenPartner)
                        .billingContact(givenContact)
@@ -544,7 +542,6 @@
            final var givenBankAccount =
                    bankAccount != null ? bankAccountRepo.findByOptionalHolderLike(bankAccount).get(0) : null;
            final var newDebitor = HsOfficeDebitorEntity.builder()
                    .uuid(UUID.randomUUID())
                    .debitorNumber(20000)
                    .partner(givenPartner)
                    .billingContact(givenContact)
src/test/java/net/hostsharing/hsadminng/hs/office/debitor/TestHsOfficeDebitor.java
@@ -11,7 +11,6 @@
public class TestHsOfficeDebitor {
    public static final HsOfficeDebitorEntity TEST_DEBITOR = HsOfficeDebitorEntity.builder()
            .uuid(UUID.randomUUID())
            .debitorNumber(10001)
            .partner(TEST_PARTNER)
            .billingContact(TEST_CONTACT)
src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipControllerRestTest.java
@@ -2,26 +2,25 @@
import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.hs.office.coopassets.HsOfficeCoopAssetsTransactionRepository;
import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorEntity;
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity;
import net.hostsharing.hsadminng.mapper.Mapper;
import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.context.annotation.Import;
import org.springframework.http.MediaType;
import org.springframework.orm.jpa.JpaObjectRetrievalFailureException;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import javax.persistence.EntityManager;
import javax.persistence.EntityNotFoundException;
import java.util.UUID;
import static org.hamcrest.Matchers.is;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@@ -97,14 +96,13 @@
    }
    @Test
    void respondBadRequest_ifAnyGivenUuidCannotBeResolved() throws Exception {
    void respondBadRequest_ifAnyGivenPartnerUuidCannotBeFound() throws Exception {
        // given
        when(membershipRepo.save(any())).thenThrow(
                new JpaObjectRetrievalFailureException(
                        new EntityNotFoundException(
                                // same would happen with HsOfficePartnerEntity which could not be resolved
                                "Unable to find net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorEntity with id ...")));
        final var givenPartnerUuid = UUID.randomUUID();
        final var givenMainDebitorUuid = UUID.randomUUID();
        when(em.find(HsOfficePartnerEntity.class, givenPartnerUuid)).thenReturn(null);
        when(em.find(HsOfficeDebitorEntity.class, givenMainDebitorUuid)).thenReturn(mock(HsOfficeDebitorEntity.class));
        // when
        mockMvc.perform(MockMvcRequestBuilders
@@ -118,13 +116,44 @@
                                       "memberNumber": 20001,
                                       "validFrom": "2022-10-13"
                                     }
                                """.formatted(UUID.randomUUID(), UUID.randomUUID()))
                                """.formatted(givenPartnerUuid, givenMainDebitorUuid))
                        .accept(MediaType.APPLICATION_JSON))
                // then
                .andExpect(status().is4xxClientError())
                .andExpect(jsonPath("status", is(400)))
                .andExpect(jsonPath("error", is("Bad Request")))
                .andExpect(jsonPath("message", is("Unable to find Debitor with uuid ...")));
                .andExpect(jsonPath("message", is("Unable to find Partner with uuid " + givenPartnerUuid)));
    }
    @Test
    void respondBadRequest_ifAnyGivenDebitorUuidCannotBeFound() throws Exception {
        // given
        final var givenPartnerUuid = UUID.randomUUID();
        final var givenMainDebitorUuid = UUID.randomUUID();
        when(em.find(HsOfficePartnerEntity.class, givenPartnerUuid)).thenReturn(mock(HsOfficePartnerEntity.class));
        when(em.find(HsOfficeDebitorEntity.class, givenMainDebitorUuid)).thenReturn(null);
        // when
        mockMvc.perform(MockMvcRequestBuilders
                        .post("/api/hs/office/memberships")
                        .header("current-user", "superuser-alex@hostsharing.net")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content("""
                                   {
                                       "partnerUuid": "%s",
                                       "mainDebitorUuid": "%s",
                                       "memberNumber": 20001,
                                       "validFrom": "2022-10-13"
                                     }
                                """.formatted(givenPartnerUuid, givenMainDebitorUuid))
                        .accept(MediaType.APPLICATION_JSON))
                // then
                .andExpect(status().is4xxClientError())
                .andExpect(jsonPath("status", is(400)))
                .andExpect(jsonPath("error", is("Bad Request")))
                .andExpect(jsonPath("message", is("Unable to find Debitor with uuid " + givenMainDebitorUuid)));
    }
}
src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipRepositoryIntegrationTest.java
@@ -76,7 +76,6 @@
            // when
            final var result = attempt(em, () -> {
                final var newMembership = toCleanup(HsOfficeMembershipEntity.builder()
                        .uuid(UUID.randomUUID())
                        .memberNumber(20001)
                        .partner(givenPartner)
                        .mainDebitor(givenDebitor)
@@ -107,7 +106,6 @@
                final var givenPartner = partnerRepo.findPartnerByOptionalNameLike("First").get(0);
                final var givenDebitor = debitorRepo.findDebitorByOptionalNameLike("First").get(0);
                final var newMembership = toCleanup(HsOfficeMembershipEntity.builder()
                        .uuid(UUID.randomUUID())
                        .memberNumber(20002)
                        .partner(givenPartner)
                        .mainDebitor(givenDebitor)
@@ -409,7 +407,6 @@
            final var givenPartner = partnerRepo.findPartnerByOptionalNameLike(partnerTradeName).get(0);
            final var givenDebitor = debitorRepo.findDebitorByOptionalNameLike(debitorName).get(0);
            final var newMembership = HsOfficeMembershipEntity.builder()
                    .uuid(UUID.randomUUID())
                    .memberNumber(20002)
                    .partner(givenPartner)
                    .mainDebitor(givenDebitor)
src/test/java/net/hostsharing/hsadminng/hs/office/membership/TestHsMembership.java
@@ -11,7 +11,6 @@
    public static final HsOfficeMembershipEntity TEST_MEMBERSHIP =
            HsOfficeMembershipEntity.builder()
                    .uuid(UUID.randomUUID())
                    .partner(TEST_PARTNER)
                    .memberNumber(300001)
                    .validity(Range.closedInfinite(LocalDate.parse("2020-01-01")))
src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerControllerAcceptanceTest.java
@@ -17,8 +17,7 @@
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.transaction.annotation.Transactional;
import java.util.HashSet;
import java.util.Set;
import javax.persistence.EntityManager;
import java.util.UUID;
import static net.hostsharing.test.IsValidUuidMatcher.isUuidValid;
@@ -32,7 +31,6 @@
        webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
        classes = { HsadminNgApplication.class, JpaAttempt.class }
)
@Transactional
class HsOfficePartnerControllerAcceptanceTest {
    @LocalServerPort
@@ -56,10 +54,12 @@
    @Autowired
    JpaAttempt jpaAttempt;
    Set<UUID> tempPartnerUuids = new HashSet<>();
    @Autowired
    EntityManager em;
    @Nested
    @Accepts({ "Partner:F(Find)" })
    @Transactional
    class ListPartners {
        @Test
@@ -109,6 +109,7 @@
    @Nested
    @Accepts({ "Partner:C(Create)" })
    @Transactional
    class AddPartner {
        @Test
@@ -127,8 +128,8 @@
                                   "contactUuid": "%s",
                                   "personUuid": "%s",
                                   "details": {
                                       "registrationOffice": "Registergericht Aurich",
                                       "registrationNumber": "123456"
                                       "registrationOffice": "Temp Registergericht Aurich",
                                       "registrationNumber": "111111"
                                   }
                                 }
                            """.formatted(givenContact.getUuid(), givenPerson.getUuid()))
@@ -139,16 +140,16 @@
                        .statusCode(201)
                        .contentType(ContentType.JSON)
                        .body("uuid", isUuidValid())
                        .body("details.registrationOffice", is("Registergericht Aurich"))
                        .body("details.registrationNumber", is("123456"))
                        .body("details.registrationOffice", is("Temp Registergericht Aurich"))
                        .body("details.registrationNumber", is("111111"))
                        .body("contact.label", is(givenContact.getLabel()))
                        .body("person.tradeName", is(givenPerson.getTradeName()))
                        .header("Location", startsWith("http://localhost"))
                    .extract().header("Location");  // @formatter:on
            // finally, the new partner can be accessed under the generated UUID
            final var newUserUuid = toCleanup(UUID.fromString(
                    location.substring(location.lastIndexOf('/') + 1)));
            final var newUserUuid = UUID.fromString(
                    location.substring(location.lastIndexOf('/') + 1));
            assertThat(newUserUuid).isNotNull();
        }
@@ -209,6 +210,7 @@
    @Nested
    @Accepts({ "Partner:R(Read)" })
    @Transactional
    class GetPartner {
        @Test
@@ -275,6 +277,7 @@
    @Nested
    @Accepts({ "Partner:U(Update)" })
    @Transactional
    class PatchPartner {
        @Test
@@ -294,7 +297,7 @@
                                   "contactUuid": "%s",
                                   "personUuid": "%s",
                                   "details": {
                                       "registrationOffice": "Registergericht Hamburg",
                                       "registrationOffice": "Temp Registergericht Aurich",
                                       "registrationNumber": "222222",
                                       "birthName": "Maja Schmidt",
                                       "birthday": "1938-04-08",
@@ -320,7 +323,7 @@
                    .matches(person -> {
                        assertThat(person.getPerson().getTradeName()).isEqualTo("Third OHG");
                        assertThat(person.getContact().getLabel()).isEqualTo("forth contact");
                        assertThat(person.getDetails().getRegistrationOffice()).isEqualTo("Registergericht Hamburg");
                        assertThat(person.getDetails().getRegistrationOffice()).isEqualTo("Temp Registergericht Aurich");
                        assertThat(person.getDetails().getRegistrationNumber()).isEqualTo("222222");
                        assertThat(person.getDetails().getBirthName()).isEqualTo("Maja Schmidt");
                        assertThat(person.getDetails().getBirthday()).isEqualTo("1938-04-08");
@@ -365,8 +368,8 @@
                    .matches(person -> {
                        assertThat(person.getPerson().getTradeName()).isEqualTo(givenPartner.getPerson().getTradeName());
                        assertThat(person.getContact().getLabel()).isEqualTo(givenPartner.getContact().getLabel());
                        assertThat(person.getDetails().getRegistrationOffice()).isEqualTo(null);
                        assertThat(person.getDetails().getRegistrationNumber()).isEqualTo(null);
                        assertThat(person.getDetails().getRegistrationOffice()).isEqualTo("Temp Registergericht Leer");
                        assertThat(person.getDetails().getRegistrationNumber()).isEqualTo("333333");
                        assertThat(person.getDetails().getBirthName()).isEqualTo("Maja Schmidt");
                        assertThat(person.getDetails().getBirthday()).isEqualTo("1938-04-08");
                        assertThat(person.getDetails().getDateOfDeath()).isEqualTo("2022-01-12");
@@ -378,6 +381,7 @@
    @Nested
    @Accepts({ "Partner:D(Delete)" })
    @Transactional
    class DeletePartner {
        @Test
@@ -445,35 +449,41 @@
            final var givenPerson = personRepo.findPersonByOptionalNameLike("Erben Bessler").get(0);
            final var givenContact = contactRepo.findContactByOptionalLabelLike("forth contact").get(0);
            final var newPartner = HsOfficePartnerEntity.builder()
                    .uuid(UUID.randomUUID())
                    .person(givenPerson)
                    .contact(givenContact)
                    .details(HsOfficePartnerDetailsEntity.builder()
                            .uuid((UUID.randomUUID()))
                            .registrationOffice("Temp Registergericht Leer")
                            .registrationNumber("333333")
                            .build())
                    .build();
            toCleanup(newPartner.getUuid());
            return partnerRepo.save(newPartner);
        }).assertSuccessful().returnedValue();
    }
    private UUID toCleanup(final UUID tempPartnerUuid) {
        tempPartnerUuids.add(tempPartnerUuid);
        return tempPartnerUuid;
    }
    @AfterEach
    void cleanup() {
        tempPartnerUuids.forEach(uuid -> {
            jpaAttempt.transacted(() -> {
                context.define("superuser-alex@hostsharing.net", null);
                System.out.println("DELETING temporary partner: " + uuid);
                final var count = partnerRepo.deleteByUuid(uuid);
                System.out.println("DELETED temporary partner: " + uuid + (count > 0 ? " successful" : " failed"));
            });
        });
        final var deleted = jpaAttempt.transacted(() -> {
            context.define("superuser-alex@hostsharing.net", null);
            em.createNativeQuery("""
                            delete from hs_office_partner p
                                where p.detailsuuid in (
                                    select d.uuid from hs_office_partner_details d
                                        where d.registrationoffice like 'Temp %')
                            """)
                    .executeUpdate();
        }).assertSuccessful().returnedValue();
        final var remaining = jpaAttempt.transacted(() -> {
            em.createNativeQuery("""
                            select count(p) from hs_office_partner p
                                where p.detailsuuid in (
                                    select d.uuid from hs_office_partner_details d
                                        where d.registrationoffice like 'Temp %')
                            """)
                    .getSingleResult();
        }).assertSuccessful().returnedValue();
        System.err.println("@AfterEach" + ": " + deleted + " records deleted, " + remaining + " remaining");
    }
}
src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerRepositoryIntegrationTest.java
@@ -73,11 +73,9 @@
            // when
            final var result = attempt(em, () -> {
                final var newPartner = toCleanup(HsOfficePartnerEntity.builder()
                        .uuid(UUID.randomUUID())
                        .person(givenPerson)
                        .contact(givenContact)
                        .details(HsOfficePartnerDetailsEntity.builder()
                                .uuid(UUID.randomUUID())
                                .build())
                        .build());
                return partnerRepo.save(newPartner);
@@ -106,10 +104,9 @@
                final var givenPerson = personRepo.findPersonByOptionalNameLike("Erben Bessler").get(0);
                final var givenContact = contactRepo.findContactByOptionalLabelLike("forth contact").get(0);
                final var newPartner = toCleanup(HsOfficePartnerEntity.builder()
                        .uuid(UUID.randomUUID())
                        .person(givenPerson)
                        .contact(givenContact)
                        .details(HsOfficePartnerDetailsEntity.builder().uuid(UUID.randomUUID()).build())
                        .details(HsOfficePartnerDetailsEntity.builder().build())
                        .build());
                return partnerRepo.save(newPartner);
            });
@@ -406,12 +403,9 @@
            final var givenPerson = personRepo.findPersonByOptionalNameLike("Erben Bessler").get(0);
            final var givenContact = contactRepo.findContactByOptionalLabelLike(contact).get(0);
            final var newPartner = HsOfficePartnerEntity.builder()
                    .uuid(UUID.randomUUID())
                    .person(givenPerson)
                    .contact(givenContact)
                    .details(HsOfficePartnerDetailsEntity.builder()
                            .uuid(UUID.randomUUID())
                            .build())
                    .details(HsOfficePartnerDetailsEntity.builder().build())
                    .build();
            toCleanup(newPartner);
src/test/java/net/hostsharing/hsadminng/hs/office/partner/TestHsOfficePartner.java
@@ -13,7 +13,6 @@
    static public HsOfficePartnerEntity HsOfficePartnerWithLegalPerson(final String tradeName) {
        return HsOfficePartnerEntity.builder()
                .uuid(UUID.randomUUID())
                .person(HsOfficePersonEntity.builder()
                        .personType(LEGAL)
                        .tradeName(tradeName)
src/test/java/net/hostsharing/hsadminng/hs/office/person/HsOfficePersonControllerAcceptanceTest.java
@@ -2,14 +2,13 @@
import io.restassured.RestAssured;
import io.restassured.http.ContentType;
import net.hostsharing.test.Accepts;
import net.hostsharing.hsadminng.HsadminNgApplication;
import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.test.Accepts;
import net.hostsharing.test.JpaAttempt;
import org.apache.commons.lang3.RandomStringUtils;
import org.json.JSONException;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
@@ -17,8 +16,7 @@
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.transaction.annotation.Transactional;
import java.util.HashSet;
import java.util.Set;
import javax.persistence.EntityManager;
import java.util.UUID;
import static net.hostsharing.test.IsValidUuidMatcher.isUuidValid;
@@ -31,7 +29,6 @@
        webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
        classes = { HsadminNgApplication.class, JpaAttempt.class }
)
@Transactional
class HsOfficePersonControllerAcceptanceTest {
    @LocalServerPort
@@ -49,7 +46,8 @@
    @Autowired
    JpaAttempt jpaAttempt;
    Set<UUID> tempPersonUuids = new HashSet<>();
    @Autowired
    EntityManager em;
    @Nested
    @Accepts({ "Person:F(Find)" })
@@ -129,9 +127,7 @@
    class AddPerson {
        @Test
        void globalAdmin_withoutAssumedRole_canAddPerson() {
            context.define("superuser-alex@hostsharing.net");
        void globalAdmin_canAddPerson() {
            final var location = RestAssured // @formatter:off
                    .given()
@@ -141,7 +137,7 @@
                               {
                                   "personType": "NATURAL",
                                   "familyName": "Tester",
                                   "givenName": "Testi"
                                   "givenName": "Temp Testi"
                                 }
                            """)
                        .port(port)
@@ -153,23 +149,24 @@
                        .body("uuid", isUuidValid())
                        .body("personType", is("NATURAL"))
                        .body("familyName", is("Tester"))
                        .body("givenName", is("Testi"))
                        .body("givenName", is("Temp Testi"))
                        .header("Location", startsWith("http://localhost"))
                    .extract().header("Location");  // @formatter:on
            // finally, the new person can be accessed under the generated UUID
            final var newUserUuid = toCleanup(UUID.fromString(
                    location.substring(location.lastIndexOf('/') + 1)));
            final var newUserUuid = UUID.fromString(
                    location.substring(location.lastIndexOf('/') + 1));
            assertThat(newUserUuid).isNotNull();
        }
    }
    @Nested
    @Accepts({ "Person:R(Read)" })
    @Transactional
    class GetPerson {
        @Test
        void globalAdmin_withoutAssumedRole_canGetArbitraryPerson() {
        void globalAdmin_canGetArbitraryPerson() {
            context.define("superuser-alex@hostsharing.net");
            final var givenPersonUuid = personRepo.findPersonByOptionalNameLike("Erben").get(0).getUuid();
@@ -192,8 +189,10 @@
        @Test
        @Accepts({ "Person:X(Access Control)" })
        void normalUser_canNotGetUnrelatedPerson() {
            context.define("superuser-alex@hostsharing.net");
            final var givenPersonUuid = personRepo.findPersonByOptionalNameLike("Erben").get(0).getUuid();
            final var givenPersonUuid = jpaAttempt.transacted(() -> {
                context.define("superuser-alex@hostsharing.net");
                return personRepo.findPersonByOptionalNameLike("Erben").get(0).getUuid();
            }).returnedValue();
            RestAssured // @formatter:off
                .given()
@@ -208,8 +207,10 @@
        @Test
        @Accepts({ "Person:X(Access Control)" })
        void personOwnerUser_canGetRelatedPerson() {
            context.define("superuser-alex@hostsharing.net");
            final var givenPersonUuid = personRepo.findPersonByOptionalNameLike("Erben").get(0).getUuid();
            final var givenPersonUuid = jpaAttempt.transacted(() -> {
                context.define("superuser-alex@hostsharing.net");
                return personRepo.findPersonByOptionalNameLike("Erben").get(0).getUuid();
            }).returnedValue();
            RestAssured // @formatter:off
                .given()
@@ -233,12 +234,12 @@
    @Nested
    @Accepts({ "Person:U(Update)" })
    @Transactional
    class PatchPerson {
        @Test
        void globalAdmin_withoutAssumedRole_canPatchAllPropertiesOfArbitraryPerson() {
        void globalAdmin_canPatchAllPropertiesOfArbitraryPerson() {
            context.define("superuser-alex@hostsharing.net");
            final var givenPerson = givenSomeTemporaryPersonCreatedBy("selfregistered-test-user@hostsharing.org");
            final var location = RestAssured // @formatter:off
@@ -248,9 +249,9 @@
                    .body("""
                       {
                           "personType": "JOINT_REPRESENTATION",
                           "tradeName": "Patched Trade Name",
                           "familyName": "Patched Family Name",
                           "givenName": "Patched Given Name"
                           "tradeName": "Temp Trade Name - patched",
                           "familyName": "Temp Family Name - patched",
                           "givenName": "Temp Given Name - patched"
                       }
                       """)
                    .port(port)
@@ -261,9 +262,9 @@
                    .contentType(ContentType.JSON)
                    .body("uuid", isUuidValid())
                    .body("personType", is("JOINT_REPRESENTATION"))
                    .body("tradeName", is("Patched Trade Name"))
                    .body("familyName", is("Patched Family Name"))
                    .body("givenName", is("Patched Given Name"));
                    .body("tradeName", is("Temp Trade Name - patched"))
                    .body("familyName", is("Temp Family Name - patched"))
                    .body("givenName", is("Temp Given Name - patched"));
                // @formatter:on
            // finally, the person is actually updated
@@ -271,17 +272,16 @@
            assertThat(personRepo.findByUuid(givenPerson.getUuid())).isPresent().get()
                    .matches(person -> {
                        assertThat(person.getPersonType()).isEqualTo(HsOfficePersonType.JOINT_REPRESENTATION);
                        assertThat(person.getTradeName()).isEqualTo("Patched Trade Name");
                        assertThat(person.getFamilyName()).isEqualTo("Patched Family Name");
                        assertThat(person.getGivenName()).isEqualTo("Patched Given Name");
                        assertThat(person.getTradeName()).isEqualTo("Temp Trade Name - patched");
                        assertThat(person.getFamilyName()).isEqualTo("Temp Family Name - patched");
                        assertThat(person.getGivenName()).isEqualTo("Temp Given Name - patched");
                        return true;
                    });
        }
        @Test
        void globalAdmin_withoutAssumedRole_canPatchPartialPropertiesOfArbitraryPerson() {
        void globalAdmin_canPatchPartialPropertiesOfArbitraryPerson() {
            context.define("superuser-alex@hostsharing.net");
            final var givenPerson = givenSomeTemporaryPersonCreatedBy("selfregistered-test-user@hostsharing.org");
            final var location = RestAssured // @formatter:off
@@ -290,8 +290,8 @@
                    .contentType(ContentType.JSON)
                    .body("""
                        {
                            "familyName": "Patched Family Name",
                            "givenName": "Patched Given Name"
                            "familyName": "Temp Family Name - patched",
                            "givenName": "Temp Given Name - patched"
                        }
                        """)
                    .port(port)
@@ -303,17 +303,18 @@
                    .body("uuid", isUuidValid())
                    .body("personType", is(givenPerson.getPersonType().toString()))
                    .body("tradeName", is(givenPerson.getTradeName()))
                    .body("familyName", is("Patched Family Name"))
                    .body("givenName", is("Patched Given Name"));
                    .body("familyName", is("Temp Family Name - patched"))
                    .body("givenName", is("Temp Given Name - patched"));
            // @formatter:on
            // finally, the person is actually updated
            context.define("superuser-alex@hostsharing.net");
            assertThat(personRepo.findByUuid(givenPerson.getUuid())).isPresent().get()
                    .matches(person -> {
                        assertThat(person.getPersonType()).isEqualTo(givenPerson.getPersonType());
                        assertThat(person.getTradeName()).isEqualTo(givenPerson.getTradeName());
                        assertThat(person.getFamilyName()).isEqualTo("Patched Family Name");
                        assertThat(person.getGivenName()).isEqualTo("Patched Given Name");
                        assertThat(person.getFamilyName()).isEqualTo("Temp Family Name - patched");
                        assertThat(person.getGivenName()).isEqualTo("Temp Given Name - patched");
                        return true;
                    });
        }
@@ -321,11 +322,11 @@
    @Nested
    @Accepts({ "Person:D(Delete)" })
    @Transactional
    class DeletePerson {
        @Test
        void globalAdmin_withoutAssumedRole_canDeleteArbitraryPerson() {
            context.define("superuser-alex@hostsharing.net");
        void globalAdmin_canDeleteArbitraryPerson() {
            final var givenPerson = givenSomeTemporaryPersonCreatedBy("selfregistered-test-user@hostsharing.org");
            RestAssured // @formatter:off
@@ -338,6 +339,8 @@
                    .statusCode(204); // @formatter:on
            // then the given person is gone
            context.define("superuser-alex@hostsharing.net");
            assertThat(personRepo.findByUuid(givenPerson.getUuid())).isEmpty();
        }
@@ -362,7 +365,6 @@
        @Test
        @Accepts({ "Person:X(Access Control)" })
        void normalUser_canNotDeleteUnrelatedPerson() {
            context.define("superuser-alex@hostsharing.net");
            final var givenPerson = givenSomeTemporaryPersonCreatedBy("selfregistered-test-user@hostsharing.org");
            RestAssured // @formatter:off
@@ -376,6 +378,7 @@
            // @formatter:on
            // then the given person is still there
            context.define("superuser-alex@hostsharing.net");
            assertThat(personRepo.findByUuid(givenPerson.getUuid())).isNotEmpty();
        }
    }
@@ -388,32 +391,21 @@
                    .personType(HsOfficePersonType.LEGAL)
                    .tradeName("Temp " + Context.getCallerMethodNameFromStackFrame(2))
                    .familyName(RandomStringUtils.randomAlphabetic(10) + "@example.org")
                    .givenName("Given Name " + RandomStringUtils.randomAlphabetic(10))
                    .givenName("Temp Given Name " + RandomStringUtils.randomAlphabetic(10))
                    .build();
            toCleanup(newPerson.getUuid());
            return personRepo.save(newPerson);
        }).assertSuccessful().returnedValue();
    }
    private UUID toCleanup(final UUID tempPersonUuid) {
        tempPersonUuids.add(tempPersonUuid);
        return tempPersonUuid;
    }
    @BeforeEach
    @AfterEach
    void cleanup() {
        tempPersonUuids.forEach(uuid -> {
            jpaAttempt.transacted(() -> {
                context.define("superuser-alex@hostsharing.net", null);
                System.out.println("DELETING temporary person: " + uuid);
                final var entity = personRepo.findByUuid(uuid);
                final var count = personRepo.deleteByUuid(uuid);
                System.out.println("DELETED temporary person: " + uuid + (count > 0 ? " successful" : " failed") +
                        (" (" + entity.map(hsOfficePersonEntity -> hsOfficePersonEntity.toShortString()).orElse("null") + ")"));
            }).assertSuccessful();
        });
        jpaAttempt.transacted(() -> {
            context.define("superuser-alex@hostsharing.net", null);
            em.createQuery("""
                    DELETE FROM HsOfficePersonEntity p
                        WHERE p.tradeName LIKE 'Temp %' OR p.givenName LIKE 'Temp %'
                    """).executeUpdate();
        }).assertSuccessful();
    }
}
src/test/java/net/hostsharing/hsadminng/hs/office/person/TestHsOfficePerson.java
@@ -8,7 +8,6 @@
    static public HsOfficePersonEntity hsOfficePerson(final String tradeName) {
        return HsOfficePersonEntity.builder()
                .uuid(UUID.randomUUID())
                .personType(HsOfficePersonType.NATURAL)
                .tradeName(tradeName)
                .build();
src/test/java/net/hostsharing/hsadminng/hs/office/relationship/HsOfficeRelationshipRepositoryIntegrationTest.java
@@ -1,6 +1,5 @@
package net.hostsharing.hsadminng.hs.office.relationship;
import net.hostsharing.hsadminng.HsadminNgApplication;
import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.context.ContextBasedTest;
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRepository;
@@ -15,10 +14,8 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Import;
import org.springframework.orm.jpa.JpaSystemException;
import org.springframework.test.annotation.DirtiesContext;
import javax.persistence.EntityManager;
import javax.servlet.http.HttpServletRequest;
@@ -74,7 +71,6 @@
            // when
            final var result = attempt(em, () -> {
                final var newRelationship = toCleanup(HsOfficeRelationshipEntity.builder()
                        .uuid(UUID.randomUUID())
                        .relAnchor(givenAnchorPerson)
                        .relHolder(givenHolderPerson)
                        .relType(HsOfficeRelationshipType.JOINT_AGENT)
@@ -103,7 +99,6 @@
                final var givenHolderPerson = personRepo.findPersonByOptionalNameLike("Anita").get(0);
                final var givenContact = contactRepo.findContactByOptionalLabelLike("forth contact").get(0);
                final var newRelationship = toCleanup(HsOfficeRelationshipEntity.builder()
                        .uuid(UUID.randomUUID())
                        .relAnchor(givenAnchorPerson)
                        .relHolder(givenHolderPerson)
                        .relType(HsOfficeRelationshipType.JOINT_AGENT)
@@ -393,7 +388,6 @@
            final var givenHolderPerson = personRepo.findPersonByOptionalNameLike(holderPerson).get(0);
            final var givenContact = contactRepo.findContactByOptionalLabelLike(contact).get(0);
            final var newRelationship = HsOfficeRelationshipEntity.builder()
                    .uuid(UUID.randomUUID())
                    .relType(HsOfficeRelationshipType.JOINT_AGENT)
                    .relAnchor(givenAnchorPerson)
                    .relHolder(givenHolderPerson)
src/test/java/net/hostsharing/hsadminng/hs/office/sepamandate/HsOfficeSepaMandateControllerAcceptanceTest.java
@@ -3,14 +3,15 @@
import com.vladmihalcea.hibernate.type.range.Range;
import io.restassured.RestAssured;
import io.restassured.http.ContentType;
import net.hostsharing.test.Accepts;
import net.hostsharing.hsadminng.HsadminNgApplication;
import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountRepository;
import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorRepository;
import net.hostsharing.test.Accepts;
import net.hostsharing.test.JpaAttempt;
import org.json.JSONException;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
@@ -18,9 +19,8 @@
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.transaction.annotation.Transactional;
import javax.persistence.EntityManager;
import java.time.LocalDate;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import static net.hostsharing.test.IsValidUuidMatcher.isUuidValid;
@@ -56,7 +56,8 @@
    @Autowired
    JpaAttempt jpaAttempt;
    Set<UUID> tempSepaMandateUuids = new HashSet<>();
    @Autowired
    EntityManager em;
    @Nested
    @Accepts({ "SepaMandate:F(Find)" })
@@ -131,7 +132,7 @@
                               {
                                   "debitorUuid": "%s",
                                   "bankAccountUuid": "%s",
                                   "reference": "temp ref A",
                                   "reference": "temp ref CAT A",
                                   "validFrom": "2022-10-13"
                                 }
                            """.formatted(givenDebitor.getUuid(), givenBankAccount.getUuid()))
@@ -144,15 +145,15 @@
                        .body("uuid", isUuidValid())
                        .body("debitor.partner.person.tradeName", is("Third OHG"))
                        .body("bankAccount.iban", is("DE02200505501015871393"))
                        .body("reference", is("temp ref A"))
                        .body("reference", is("temp ref CAT A"))
                        .body("validFrom", is("2022-10-13"))
                        .body("validTo", equalTo(null))
                        .header("Location", startsWith("http://localhost"))
                    .extract().header("Location");  // @formatter:on
            // finally, the new sepaMandate can be accessed under the generated UUID
            final var newUserUuid = toCleanup(UUID.fromString(
                    location.substring(location.lastIndexOf('/') + 1)));
            final var newUserUuid = UUID.fromString(
                    location.substring(location.lastIndexOf('/') + 1));
            assertThat(newUserUuid).isNotNull();
        }
@@ -171,7 +172,7 @@
                    .body("""
                               {
                                   "bankAccountUuid": "%s",
                                   "reference": "temp ref A",
                                   "reference": "temp ref CAT B",
                                   "validFrom": "2022-10-13"
                                 }
                            """.formatted(givenBankAccount.getUuid()))
@@ -197,7 +198,7 @@
                               {
                                   "debitorUuid": "%s",
                                   "bankAccountUuid": "%s",
                                   "reference": "temp ref A",
                                   "reference": "temp ref CAT C",
                                   "validFrom": "2022-10-13",
                                   "validTo": "2024-12-31"
                                 }
@@ -226,7 +227,7 @@
                               {
                                   "debitorUuid": "%s",
                                   "bankAccountUuid": "%s",
                                   "reference": "temp ref A",
                                   "reference": "temp refCAT D",
                                   "validFrom": "2022-10-13",
                                   "validTo": "2024-12-31"
                                 }
@@ -267,7 +268,7 @@
                             "debitorNumber": 10001,
                             "billingContact": { "label": "first contact" }
                         },
                         "bankAccount": {
                         "bankAccount": {
                            "holder": "First GmbH",
                            "iban": "DE02120300000000202051"
                         },
@@ -319,7 +320,7 @@
                             "debitorNumber": 10001,
                             "billingContact": { "label": "first contact" }
                         },
                         "bankAccount": {
                         "bankAccount": {
                            "holder": "First GmbH",
                            "iban": "DE02120300000000202051"
                         },
@@ -359,7 +360,7 @@
                    .body("uuid", isUuidValid())
                    .body("debitor.partner.person.tradeName", is("First GmbH"))
                    .body("bankAccount.iban", is("DE02120300000000202051"))
                    .body("reference", is("temp ref X"))
                    .body("reference", is("temp ref CAT Z"))
                    .body("validFrom", is("2022-11-01"))
                    .body("validTo", is("2022-12-31"));
            // @formatter:on
@@ -369,7 +370,7 @@
                    .matches(mandate -> {
                        assertThat(mandate.getDebitor().toString()).isEqualTo("debitor(10001: First GmbH)");
                        assertThat(mandate.getBankAccount().toShortString()).isEqualTo("First GmbH");
                        assertThat(mandate.getReference()).isEqualTo("temp ref X");
                        assertThat(mandate.getReference()).isEqualTo("temp ref CAT Z");
                        assertThat(mandate.getValidity().asString()).isEqualTo("[2022-11-01,2023-01-01)");
                        return true;
                    });
@@ -387,7 +388,7 @@
                    .contentType(ContentType.JSON)
                    .body("""
                           {
                               "reference": "new ref"
                               "reference": "temp ref CAT new"
                           }
                           """)
                    .port(port)
@@ -405,7 +406,6 @@
                        return true;
                    });
        }
    }
    @Nested
@@ -435,7 +435,6 @@
        void bankAccountAdminUser_canNotDeleteRelatedSepaMandate() {
            context.define("superuser-alex@hostsharing.net");
            final var givenSepaMandate = givenSomeTemporarySepaMandate();
            assertThat(givenSepaMandate.getReference()).isEqualTo("temp ref X");
            RestAssured // @formatter:off
                .given()
@@ -455,7 +454,6 @@
        void normalUser_canNotDeleteUnrelatedSepaMandate() {
            context.define("superuser-alex@hostsharing.net");
            final var givenSepaMandate = givenSomeTemporarySepaMandate();
            assertThat(givenSepaMandate.getReference()).isEqualTo("temp ref X");
            RestAssured // @formatter:off
                .given()
@@ -480,32 +478,25 @@
                    .uuid(UUID.randomUUID())
                    .debitor(givenDebitor)
                    .bankAccount(givenBankAccount)
                    .reference("temp ref X")
                    .reference("temp ref CAT Z")
                    .validity(Range.closedOpen(
                            LocalDate.parse("2022-11-01"), LocalDate.parse("2023-03-31")))
                    .build();
            toCleanup(newSepaMandate.getUuid());
            return sepaMandateRepo.save(newSepaMandate);
        }).assertSuccessful().returnedValue();
    }
    private UUID toCleanup(final UUID tempSepaMandateUuid) {
        tempSepaMandateUuids.add(tempSepaMandateUuid);
        return tempSepaMandateUuid;
    }
    @BeforeEach
    @AfterEach
    void cleanup() {
        tempSepaMandateUuids.forEach(uuid -> {
            jpaAttempt.transacted(() -> {
                context.define("superuser-alex@hostsharing.net", null);
                System.out.println("DELETING temporary sepaMandate: " + uuid);
                final var count = sepaMandateRepo.deleteByUuid(uuid);
                System.out.println("DELETED temporary sepaMandate: " + uuid + (count > 0 ? " successful" : " failed"));
            });
        });
        jpaAttempt.transacted(() -> {
            context.define("superuser-alex@hostsharing.net", null);
            final var count = em.createQuery("DELETE FROM HsOfficeSepaMandateEntity s WHERE s.reference like 'temp %'")
                    .executeUpdate();
            if (count == 0) {
                System.out.println("nothing deleted");
            }
        }).assertSuccessful();
    }
}
src/test/java/net/hostsharing/hsadminng/hs/office/sepamandate/HsOfficeSepaMandateRepositoryIntegrationTest.java
@@ -16,15 +16,15 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Import;
import org.springframework.orm.jpa.JpaSystemException;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.transaction.annotation.Transactional;
import javax.persistence.EntityManager;
import javax.servlet.http.HttpServletRequest;
import java.time.LocalDate;
import java.util.*;
import java.util.Arrays;
import java.util.List;
import static net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantEntity.grantDisplaysOf;
import static net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleEntity.roleNamesOf;
@@ -33,7 +33,7 @@
import static org.assertj.core.api.Assumptions.assumeThat;
@DataJpaTest
@Import( { Context.class, JpaAttempt.class })
@Import({ Context.class, JpaAttempt.class })
class HsOfficeSepaMandateRepositoryIntegrationTest extends ContextBasedTest {
    @Autowired
@@ -52,15 +52,13 @@
    RawRbacGrantRepository rawGrantRepo;
    @Autowired
    EntityManager em;
    JpaAttempt jpaAttempt;
    @Autowired
    JpaAttempt jpaAttempt;
    EntityManager em;
    @MockBean
    HttpServletRequest request;
    Set<HsOfficeSepaMandateEntity> tempEntities = new HashSet<>();
    @Nested
    class CreateSepaMandate {
@@ -75,14 +73,13 @@
            // when
            final var result = attempt(em, () -> {
                final var newSepaMandate = toCleanup(HsOfficeSepaMandateEntity.builder()
                        .uuid(UUID.randomUUID())
                final var newSepaMandate = HsOfficeSepaMandateEntity.builder()
                        .debitor(givenDebitor)
                        .bankAccount(givenBankAccount)
                        .reference("temp ref A")
                        .validity(Range.closedOpen(
                                LocalDate.parse("2020-01-01"), LocalDate.parse("2023-01-01")))
                        .build());
                        .build();
                return sepaMandateRepo.save(newSepaMandate);
            });
@@ -108,14 +105,13 @@
            attempt(em, () -> {
                final var givenDebitor = debitorRepo.findDebitorByOptionalNameLike("First").get(0);
                final var givenBankAccount = bankAccountRepo.findByOptionalHolderLike("Paul Winkler").get(0);
                final var newSepaMandate = toCleanup(HsOfficeSepaMandateEntity.builder()
                        .uuid(UUID.randomUUID())
                final var newSepaMandate = HsOfficeSepaMandateEntity.builder()
                        .debitor(givenDebitor)
                        .bankAccount(givenBankAccount)
                        .reference("temp ref B")
                        .validity(Range.closedOpen(
                                LocalDate.parse("2020-01-01"), LocalDate.parse("2023-01-01")))
                        .build());
                        .build();
                return sepaMandateRepo.save(newSepaMandate);
            });
@@ -255,7 +251,7 @@
                context("superuser-alex@hostsharing.net");
                givenSepaMandate.setValidity(Range.closedOpen(
                        givenSepaMandate.getValidity().lower(), newValidityEnd));
                return toCleanup(sepaMandateRepo.save(givenSepaMandate));
                return sepaMandateRepo.save(givenSepaMandate);
            });
            // then
@@ -408,18 +404,10 @@
    @BeforeEach
    @AfterEach
    @Transactional
    void cleanup() {
        tempEntities.forEach(tempSepaMandate -> {
            jpaAttempt.transacted(() -> {
                context("superuser-alex@hostsharing.net", null);
                System.out.println("DELETING temporary sepaMandate: " + tempSepaMandate.toString());
                sepaMandateRepo.deleteByUuid(tempSepaMandate.getUuid());
            });
        });
        jpaAttempt.transacted(() -> {
            context("superuser-alex@hostsharing.net", null);
            em.createQuery("DELETE FROM HsOfficeSepaMandateEntity WHERE reference like 'temp ref%'");
        });
        context("superuser-alex@hostsharing.net", null);
        em.createQuery("DELETE FROM HsOfficeSepaMandateEntity WHERE reference like 'temp ref%'").executeUpdate();
    }
    private HsOfficeSepaMandateEntity givenSomeTemporarySepaMandateBessler(final String bankAccountHolder) {
@@ -428,7 +416,6 @@
            final var givenDebitor = debitorRepo.findDebitorByOptionalNameLike("First").get(0);
            final var givenBankAccount = bankAccountRepo.findByOptionalHolderLike(bankAccountHolder).get(0);
            final var newSepaMandate = HsOfficeSepaMandateEntity.builder()
                    .uuid(UUID.randomUUID())
                    .debitor(givenDebitor)
                    .bankAccount(givenBankAccount)
                    .reference("temp ref X")
@@ -436,15 +423,8 @@
                            LocalDate.parse("2020-01-01"), LocalDate.parse("2023-01-01")))
                    .build();
            toCleanup(newSepaMandate);
            return sepaMandateRepo.save(newSepaMandate);
        }).assertSuccessful().returnedValue();
    }
    private HsOfficeSepaMandateEntity toCleanup(final HsOfficeSepaMandateEntity tempEntity) {
        tempEntities.add(tempEntity);
        return tempEntity;
    }
    void exactlyTheseSepaMandatesAreReturned(
src/test/java/net/hostsharing/hsadminng/rbac/rbacrole/RbacRoleControllerRestTest.java
@@ -13,6 +13,8 @@
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import javax.persistence.EntityManager;
import static java.util.Arrays.asList;
import static net.hostsharing.hsadminng.rbac.rbacrole.TestRbacRole.*;
import static org.hamcrest.Matchers.hasSize;
@@ -35,6 +37,9 @@
    @MockBean
    RbacRoleRepository rbacRoleRepository;
    @MockBean
    EntityManager em;
    @Test
    void apiCustomersWillReturnCustomersFromRepository() throws Exception {
src/test/java/net/hostsharing/hsadminng/rbac/rbacuser/RbacUserControllerRestTest.java
@@ -13,6 +13,7 @@
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import javax.persistence.EntityManager;
import java.util.UUID;
import static net.hostsharing.test.IsValidUuidMatcher.isUuidValid;
@@ -36,6 +37,9 @@
    @MockBean
    RbacUserRepository rbacUserRepository;
    @MockBean
    EntityManager em;
    @Test
    void createUserUsesGivenUuid() throws Exception {
        // given
src/test/java/net/hostsharing/hsadminng/test/cust/TestCustomer.java
@@ -8,6 +8,6 @@
    static final TestCustomerEntity yyy = hsCustomer("yyy", 10002, "yyy@example.com");
    static public TestCustomerEntity hsCustomer(final String prefix, final int reference, final String adminName) {
        return new TestCustomerEntity(randomUUID(), prefix, reference, adminName);
        return new TestCustomerEntity(null, prefix, reference, adminName);
    }
}
src/test/java/net/hostsharing/hsadminng/test/cust/TestCustomerControllerAcceptanceTest.java
@@ -6,6 +6,7 @@
import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.test.JpaAttempt;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
@@ -13,8 +14,7 @@
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.transaction.annotation.Transactional;
import java.util.HashSet;
import java.util.Set;
import javax.persistence.EntityManager;
import java.util.UUID;
import static org.assertj.core.api.Assertions.assertThat;
@@ -43,7 +43,8 @@
    @Autowired
    JpaAttempt jpaAttempt;
    Set<UUID> tempPartnerUuids = new HashSet<>();
    @Autowired
    EntityManager em;
    @Nested
    class ListCustomers {
@@ -144,49 +145,11 @@
                    .extract().header("Location");  // @formatter:on
            // finally, the new customer can be viewed by its own admin
            final var newUserUuid = toCleanup(UUID.fromString(
                    location.substring(location.lastIndexOf('/') + 1)));
            final var newUserUuid = UUID.fromString(
                    location.substring(location.lastIndexOf('/') + 1));
            context.define("customer-admin@uuu.example.com");
            assertThat(testCustomerRepository.findByUuid(newUserUuid))
                    .hasValueSatisfying(c -> assertThat(c.getPrefix()).isEqualTo("uuu"));
        }
        @Test
        void globalAdmin_withoutAssumedRole_canAddCustomerWithGivenUuid() {
            final var givenUuid = toCleanup(UUID.randomUUID());
            final var location = RestAssured // @formatter:off
                    .given()
                    .header("current-user", "superuser-alex@hostsharing.net")
                    .contentType(ContentType.JSON)
                    .body("""
                              {
                                "uuid": "%s",
                                "reference": 90010,
                                "prefix": "vvv",
                                "adminUserName": "customer-admin@vvv.example.com"
                              }
                              """.formatted(givenUuid))
                    .port(port)
                    .when()
                    .post("http://localhost/api/test/customers")
                    .then().assertThat()
                    .statusCode(201)
                    .contentType(ContentType.JSON)
                    .body("prefix", is("vvv"))
                    .header("Location", startsWith("http://localhost"))
                    .extract().header("Location");  // @formatter:on
            // finally, the new customer can be viewed by its own admin
            final var newUserUuid = UUID.fromString(
                    location.substring(location.lastIndexOf('/') + 1));
            context.define("customer-admin@vvv.example.com");
            assertThat(testCustomerRepository.findByUuid(newUserUuid))
                    .hasValueSatisfying(c -> {
                        assertThat(c.getPrefix()).isEqualTo("vvv");
                        assertThat(c.getUuid()).isEqualTo(givenUuid);
                    });
        }
        @Test
@@ -266,26 +229,14 @@
                    .body("message", containsString("line: 1, column: 1"));
            // @formatter:on
        }
    }
    private UUID toCleanup(final UUID tempPartnerUuid) {
        tempPartnerUuids.add(tempPartnerUuid);
        return tempPartnerUuid;
    }
    @BeforeEach
    @AfterEach
    void cleanup() {
        tempPartnerUuids.forEach(uuid -> {
            jpaAttempt.transacted(() -> {
                context.define("superuser-alex@hostsharing.net", null);
                System.out.println("DELETING temporary partner: " + uuid);
                final var entity = testCustomerRepository.findByUuid(uuid);
                final var count = testCustomerRepository.deleteByUuid(uuid);
                System.out.println(
                        "DELETED temporary partner: " + uuid + (count > 0 ? " successful" : " failed") + " (" + entity.map(
                                TestCustomerEntity::getPrefix).orElse("???") + ")");
            }).assertSuccessful();
        });
        jpaAttempt.transacted(() -> {
            context.define("superuser-alex@hostsharing.net", null);
            em.createQuery("DELETE FROM TestCustomerEntity c WHERE c.reference < 99900").executeUpdate();
        }).assertSuccessful();
    }
}
src/test/java/net/hostsharing/hsadminng/test/cust/TestCustomerRepositoryIntegrationTest.java
@@ -8,9 +8,7 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Import;
import org.springframework.test.annotation.DirtiesContext;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceException;
src/test/java/net/hostsharing/test/JpaAttempt.java
@@ -1,5 +1,6 @@
package net.hostsharing.test;
import org.assertj.core.api.ObjectAssert;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.NestedExceptionUtils;
import org.springframework.stereotype.Service;
@@ -105,6 +106,11 @@
            return result;
        }
        public ObjectAssert<T> assertThatResult() {
            assertSuccessful();
            return assertThat(returnedValue());
        }
        public RuntimeException caughtException() {
            return exception;
        }
src/test/java/net/hostsharing/test/MapperUnitTest.java
@@ -1,41 +1,35 @@
package net.hostsharing.test;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.*;
import net.hostsharing.hsadminng.errors.DisplayName;
import net.hostsharing.hsadminng.mapper.Mapper;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import javax.persistence.EntityManager;
import javax.persistence.ManyToOne;
import javax.validation.ValidationException;
import java.util.List;
import java.util.UUID;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.catchThrowable;
import static org.junit.jupiter.api.Assertions.fail;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class MapperUnitTest {
    private Mapper mapper = new Mapper();
    @Mock
    EntityManager em;
    @Getter
    @Setter
    @NoArgsConstructor
    @AllArgsConstructor
    public static class SourceBean {
    @InjectMocks
    Mapper mapper;
        private String a;
        private String b;
    }
    @Getter
    @Setter
    @NoArgsConstructor
    @AllArgsConstructor
    public static class TargetBean {
        private String a;
        private String b;
        private String c;
    }
    final UUID GIVEN_UUID = UUID.randomUUID();
    @Test
    void mapsNullBeanToNull() {
@@ -46,48 +40,198 @@
    @Test
    void mapsBean() {
        final SourceBean givenSource = new SourceBean("1234", "Text");
        final SourceBean givenSource = SourceBean.builder().a("1234").b("Text").build();
        final var result = mapper.map(givenSource, TargetBean.class);
        assertThat(result).usingRecursiveComparison().isEqualTo(
                new TargetBean("1234", "Text", null)
                TargetBean.builder().a("1234").b("Text").build()
        );
    }
    @Test
    void mapsBeanWithExistingSubEntity() {
        final SourceBean givenSource = SourceBean.builder().a("1234").b("Text").s1(new SubSourceBean1(GIVEN_UUID)).build();
        when(em.find(SubTargetBean1.class, GIVEN_UUID)).thenReturn(new SubTargetBean1(GIVEN_UUID, "xxx"));
        final var result = mapper.map(givenSource, TargetBean.class);
        assertThat(result).usingRecursiveComparison().isEqualTo(
                TargetBean.builder().a("1234").b("Text").s1(new SubTargetBean1(GIVEN_UUID, "xxx")).build()
        );
    }
    @Test
    void mapsBeanWithSubEntityWithNullUuid() {
        final SourceBean givenSource = SourceBean.builder().a("1234").b("Text").s1(new SubSourceBean1(null)).build();
        final var result = mapper.map(givenSource, TargetBean.class);
        assertThat(result).usingRecursiveComparison().isEqualTo(
                TargetBean.builder().a("1234").b("Text").s1(new SubTargetBean1(null, null)).build()
        );
    }
    @Test
    void mapsBeanWithSubEntityWithoutUuidField() {
        final SourceBean givenSource = SourceBean.builder().a("1234").b("Text").s3(new SubSourceBean3("xxx")).build();
        final var result = mapper.map(givenSource, TargetBean.class);
        assertThat(result).usingRecursiveComparison().isEqualTo(
                TargetBean.builder().a("1234").b("Text").s3(new SubTargetBean3("xxx")).build()
        );
    }
    @Test
    void mapsBeanWithSubEntityNotFound() {
        final SourceBean givenSource = SourceBean.builder().a("1234").b("Text").s1(new SubSourceBean1(GIVEN_UUID)).build();
        when(em.find(SubTargetBean1.class, GIVEN_UUID)).thenReturn(null);
        final var exception = catchThrowable(() ->
                mapper.map(givenSource, TargetBean.class)
        );
        assertThat(exception).isInstanceOf(ValidationException.class)
                .hasMessage("Unable to find SubTargetBean1 with uuid " + GIVEN_UUID);
    }
    @Test
    void mapsBeanWithSubEntityNotFoundAndDisplayName() {
        final SourceBean givenSource = SourceBean.builder().a("1234").b("Text").s2(new SubSourceBean2(GIVEN_UUID)).build();
        when(em.find(SubTargetBean2.class, GIVEN_UUID)).thenReturn(null);
        final var exception = catchThrowable(() ->
                mapper.map(givenSource, TargetBean.class)
        );
        assertThat(exception).isInstanceOf(ValidationException.class)
                .hasMessage("Unable to find SomeDisplayName with uuid " + GIVEN_UUID);
    }
    @Test
    void mapsBeanWithPostmapper() {
        final SourceBean givenSource = new SourceBean("1234", "Text");
        final SourceBean givenSource = SourceBean.builder().a("1234").b("Text").build();
        final var result = mapper.map(givenSource, TargetBean.class, (s, t) -> {t.setC("Extra");});
        assertThat(result).usingRecursiveComparison().isEqualTo(
                new TargetBean("1234", "Text", "Extra")
                TargetBean.builder().a("1234").b("Text").c("Extra").build()
        );
    }
    @Test
    void mapsList() {
        final var givenSource = List.of(
                new SourceBean("111", "Text A"),
                new SourceBean("222", "Text B"),
                new SourceBean("333", "Text C"));
                SourceBean.builder().a("111").b("Text A").build(),
                SourceBean.builder().a("222").b("Text B").build(),
                SourceBean.builder().a("333").b("Text C").build());
        final var result = mapper.mapList(givenSource, TargetBean.class);
        assertThat(result).usingRecursiveComparison().isEqualTo(
                List.of(
                        new TargetBean("111", "Text A", null),
                        new TargetBean("222", "Text B", null),
                        new TargetBean("333", "Text C", null)));
                        TargetBean.builder().a("111").b("Text A").build(),
                        TargetBean.builder().a("222").b("Text B").build(),
                        TargetBean.builder().a("333").b("Text C").build()));
    }
    @Test
    void mapsListWithPostMapper() {
        final var givenSource = List.of(
                new SourceBean("111", "Text A"),
                new SourceBean("222", "Text B"),
                new SourceBean("333", "Text C"));
                SourceBean.builder().a("111").b("Text A").build(),
                SourceBean.builder().a("222").b("Text B").build(),
                SourceBean.builder().a("333").b("Text C").build());
        final var result = mapper.mapList(givenSource, TargetBean.class, (s, t) -> {t.setC("Extra");});
        assertThat(result).usingRecursiveComparison().isEqualTo(
                List.of(
                        new TargetBean("111", "Text A", "Extra"),
                        new TargetBean("222", "Text B", "Extra"),
                        new TargetBean("333", "Text C", "Extra")));
                        TargetBean.builder().a("111").b("Text A").c("Extra").build(),
                        TargetBean.builder().a("222").b("Text B").c("Extra").build(),
                        TargetBean.builder().a("333").b("Text C").c("Extra").build()));
    }
    @Getter
    @Setter
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    public static class SourceBean {
        private String a;
        private String b;
        private SubSourceBean1 s1;
        private SubSourceBean2 s2;
        private SubSourceBean3 s3;
    }
    @Getter
    @Setter
    @NoArgsConstructor
    @AllArgsConstructor
    public static class SubSourceBean1 {
        private UUID uuid;
    }
    @Getter
    @Setter
    @NoArgsConstructor
    @AllArgsConstructor
    public static class SubSourceBean2 {
        private UUID uuid;
    }
    @Getter
    @Setter
    @NoArgsConstructor
    @AllArgsConstructor
    public static class SubSourceBean3 {
        private String x;
    }
    @Getter
    @Setter
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    public static class TargetBean {
        private String a;
        private String b;
        private String c;
        @ManyToOne
        private SubTargetBean1 s1;
        @ManyToOne
        private SubTargetBean2 s2;
        @ManyToOne
        private SubTargetBean3 s3;
    }
    @Getter
    @Setter
    @NoArgsConstructor
    @AllArgsConstructor
    public static class SubTargetBean1 {
        private UUID uuid;
        private String x;
    }
    @Getter
    @Setter
    @NoArgsConstructor
    @AllArgsConstructor
    @DisplayName("SomeDisplayName")
    public static class SubTargetBean2 {
        private UUID uuid;
        private String x;
    }
    @Getter
    @Setter
    @NoArgsConstructor
    @AllArgsConstructor
    public static class SubTargetBean3 {
        private String x;
    }
}