upgrade to Spring Boot 3.4.1 and fixed most issues, except one particular strange RBAC problem

This commit is contained in:
Michael Hoennig 2025-01-12 13:40:32 +01:00
parent a7ffee9348
commit eba39ff27a
23 changed files with 221 additions and 148 deletions

View File

@ -4,5 +4,5 @@ export HSADMINNG_POSTGRES_ADMIN_PASSWORD=
export HSADMINNG_POSTGRES_RESTRICTED_USERNAME=restricted export HSADMINNG_POSTGRES_RESTRICTED_USERNAME=restricted
export HSADMINNG_SUPERUSER=superuser-alex@hostsharing.net export HSADMINNG_SUPERUSER=superuser-alex@hostsharing.net
export HSADMINNG_MIGRATION_DATA_PATH=migration export HSADMINNG_MIGRATION_DATA_PATH=migration
export LIQUIBASE_CONTEXT= export LIQUIBASE_COMMAND_CONTEXT_FILTER=
export LANG=en_US.UTF-8 export LANG=en_US.UTF-8

View File

@ -4,5 +4,5 @@ unset HSADMINNG_POSTGRES_ADMIN_PASSWORD
unset HSADMINNG_POSTGRES_RESTRICTED_USERNAME unset HSADMINNG_POSTGRES_RESTRICTED_USERNAME
unset HSADMINNG_SUPERUSER unset HSADMINNG_SUPERUSER
unset HSADMINNG_MIGRATION_DATA_PATH unset HSADMINNG_MIGRATION_DATA_PATH
unset LIQUIBASE_CONTEXT unset LIQUIBASE_COMMAND_CONTEXT_FILTER

View File

@ -1,11 +1,11 @@
plugins { plugins {
id 'java' id 'java'
id 'org.springframework.boot' version '3.3.7' id 'org.springframework.boot' version '3.4.1'
id 'io.spring.dependency-management' version '1.1.7' id 'io.spring.dependency-management' version '1.1.7'
id 'io.openapiprocessor.openapi-processor' version '2023.2' id 'io.openapiprocessor.openapi-processor' version '2023.2'
id 'com.github.jk1.dependency-license-report' version '2.9' id 'com.github.jk1.dependency-license-report' version '2.9'
id "org.owasp.dependencycheck" version "11.1.1" id "org.owasp.dependencycheck" version "11.1.1"
id "com.diffplug.spotless" version "7.0.0" id "com.diffplug.spotless" version "7.0.1"
id 'jacoco' id 'jacoco'
id 'info.solidsoft.pitest' version '1.15.0' id 'info.solidsoft.pitest' version '1.15.0'
id 'se.patrikerdes.use-latest-versions' version '0.2.18' id 'se.patrikerdes.use-latest-versions' version '0.2.18'
@ -20,6 +20,8 @@ wrapper {
gradleVersion = '8.5' gradleVersion = '8.5'
} }
// FIXME: Mockito is currently self-attaching to enable the inline-mock-maker. This will no longer work in future releases of the JDK. Please add Mockito as an agent to your build what is described in Mockito's documentation: https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html#0.3
configurations { configurations {
compileOnly { compileOnly {
extendsFrom annotationProcessor extendsFrom annotationProcessor
@ -61,7 +63,7 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-actuator' implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'com.github.gavlyukovskiy:datasource-proxy-spring-boot-starter:1.10.0' implementation 'com.github.gavlyukovskiy:datasource-proxy-spring-boot-starter:1.10.0'
implementation 'org.springdoc:springdoc-openapi:2.6.0' implementation 'org.springdoc:springdoc-openapi:2.8.1'
implementation 'org.postgresql:postgresql:42.7.4' implementation 'org.postgresql:postgresql:42.7.4'
implementation 'org.liquibase:liquibase-core:4.30.0' implementation 'org.liquibase:liquibase-core:4.30.0'
implementation 'io.hypersistence:hypersistence-utils-hibernate-63:3.9.0' implementation 'io.hypersistence:hypersistence-utils-hibernate-63:3.9.0'
@ -71,7 +73,7 @@ dependencies {
implementation 'net.java.dev.jna:jna:5.16.0' implementation 'net.java.dev.jna:jna:5.16.0'
implementation 'org.modelmapper:modelmapper:3.2.2' implementation 'org.modelmapper:modelmapper:3.2.2'
implementation 'org.iban4j:iban4j:3.2.10-RELEASE' implementation 'org.iban4j:iban4j:3.2.10-RELEASE'
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.6.0' implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.1'
implementation 'org.reflections:reflections:0.10.2' implementation 'org.reflections:reflections:0.10.2'
compileOnly 'org.projectlombok:lombok' compileOnly 'org.projectlombok:lombok'

View File

@ -5,9 +5,23 @@
{ "moduleLicense": "Apache-2.0" }, { "moduleLicense": "Apache-2.0" },
{ "moduleLicense": "Apache License 2.0" }, { "moduleLicense": "Apache License 2.0" },
{ "moduleLicense": "Apache License v2.0" }, { "moduleLicense": "Apache License v2.0" },
{ "moduleLicense": "Apache License Version 2.0" },
{ "moduleLicense": "Apache License, Version 2.0" }, { "moduleLicense": "Apache License, Version 2.0" },
{ "moduleLicense": "The Apache License, Version 2.0" },
{ "moduleLicense": "The Apache Software License, Version 2.0" }, { "moduleLicense": "The Apache Software License, Version 2.0" },
{
"moduleLicense": null,
"#moduleLicense": "Apache License 2.0, see https://github.com/springdoc/springdoc-openapi/blob/main/LICENSE",
"moduleVersion": "2.4.0",
"moduleName": "org.springdoc:springdoc-openapi"
},
{
"moduleLicense": null,
"moduleVersion": "1.0.0",
"moduleName": "org.jspecify:jspecify"
},
{ "moduleLicense": "BSD License" }, { "moduleLicense": "BSD License" },
{ "moduleLicense": "BSD-2-Clause" }, { "moduleLicense": "BSD-2-Clause" },
{ "moduleLicense": "BSD-3-Clause" }, { "moduleLicense": "BSD-3-Clause" },
@ -46,14 +60,8 @@
{ {
"moduleLicense": "Public Domain, per Creative Commons CC0", "moduleLicense": "Public Domain, per Creative Commons CC0",
"moduleVersion": "2.0.3" "moduleVersion": "2.0.3"
},
{
"moduleLicense": null,
"#moduleLicense": "Apache License 2.0, see https://github.com/springdoc/springdoc-openapi/blob/main/LICENSE",
"moduleVersion": "2.4.0",
"moduleName": "org.springdoc:springdoc-openapi"
} }
] ]
} }

View File

@ -97,6 +97,7 @@ public class RestResponseEntityExceptionHandler
return errorResponse(request, HttpStatus.valueOf(statusCode.value()), return errorResponse(request, HttpStatus.valueOf(statusCode.value()),
Optional.ofNullable(response.getBody()).map(Object::toString).orElse(firstMessageLine(exc))); Optional.ofNullable(response.getBody()).map(Object::toString).orElse(firstMessageLine(exc)));
} }
@Override @Override
@SuppressWarnings("unchecked,rawtypes") @SuppressWarnings("unchecked,rawtypes")
protected ResponseEntity handleHttpMessageNotReadable( protected ResponseEntity handleHttpMessageNotReadable(
@ -131,7 +132,7 @@ public class RestResponseEntityExceptionHandler
final HttpStatusCode status, final HttpStatusCode status,
final WebRequest request) { final WebRequest request) {
final var errorList = exc final var errorList = exc
.getAllValidationResults() .getAllValidationResults() // FIXME: deprecated
.stream() .stream()
.map(ParameterValidationResult::getResolvableErrors) .map(ParameterValidationResult::getResolvableErrors)
.flatMap(Collection::stream) .flatMap(Collection::stream)

View File

@ -6,6 +6,7 @@ import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType;
import net.hostsharing.hsadminng.hs.validation.PropertiesProvider; import net.hostsharing.hsadminng.hs.validation.PropertiesProvider;
import jakarta.persistence.EntityManager; import jakarta.persistence.EntityManager;
import jakarta.persistence.FlushModeType;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import static net.hostsharing.hsadminng.hs.validation.BooleanProperty.booleanProperty; import static net.hostsharing.hsadminng.hs.validation.BooleanProperty.booleanProperty;
@ -53,6 +54,7 @@ class HsUnixUserHostingAssetValidator extends HostingAssetEntityValidator {
} }
private static Integer computeUserId(final EntityManager em, final PropertiesProvider propertiesProvider) { private static Integer computeUserId(final EntityManager em, final PropertiesProvider propertiesProvider) {
em.setFlushMode(FlushModeType.COMMIT); // FIXME: check and remove or reset
final Object result = em.createNativeQuery("SELECT nextval('hs_hosting.asset_unixuser_system_id_seq')", Integer.class) final Object result = em.createNativeQuery("SELECT nextval('hs_hosting.asset_unixuser_system_id_seq')", Integer.class)
.getSingleResult(); .getSingleResult();
return (Integer) result; return (Integer) result;

View File

@ -8,7 +8,7 @@ import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficeCoopShar
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeCoopSharesTransactionInsertResource; import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeCoopSharesTransactionInsertResource;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeCoopSharesTransactionResource; import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeCoopSharesTransactionResource;
import net.hostsharing.hsadminng.errors.MultiValidationException; import net.hostsharing.hsadminng.errors.MultiValidationException;
import net.hostsharing.hsadminng.mapper.StandardMapper; import net.hostsharing.hsadminng.mapper.StrictMapper;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.format.annotation.DateTimeFormat; import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.format.annotation.DateTimeFormat.ISO; import org.springframework.format.annotation.DateTimeFormat.ISO;
@ -33,14 +33,13 @@ public class HsOfficeCoopSharesTransactionController implements HsOfficeCoopShar
private Context context; private Context context;
@Autowired @Autowired
private StandardMapper mapper; private StrictMapper mapper;
@Autowired @Autowired
private HsOfficeCoopSharesTransactionRepository coopSharesTransactionRepo; private HsOfficeCoopSharesTransactionRepository coopSharesTransactionRepo;
@Override @Override
@Transactional(readOnly = true) @Transactional(readOnly = true)
@Timed("app.office.coopShares.api.getListOfCoopShares") @Timed("app.office.coopShares.api.getListOfCoopShares")
public ResponseEntity<List<HsOfficeCoopSharesTransactionResource>> getListOfCoopShares( public ResponseEntity<List<HsOfficeCoopSharesTransactionResource>> getListOfCoopShares(
final String currentSubject, final String currentSubject,

View File

@ -2,13 +2,14 @@ package net.hostsharing.hsadminng.hs.office.debitor;
import io.micrometer.core.annotation.Timed; import io.micrometer.core.annotation.Timed;
import net.hostsharing.hsadminng.context.Context; import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountRepository;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficeDebitorsApi; import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficeDebitorsApi;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeDebitorInsertResource; import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeDebitorInsertResource;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeDebitorPatchResource; import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeDebitorPatchResource;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeDebitorResource; import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeDebitorResource;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealEntity; import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealEntity;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealRepository; import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealRepository;
import net.hostsharing.hsadminng.mapper.StandardMapper; import net.hostsharing.hsadminng.mapper.StrictMapper;
import net.hostsharing.hsadminng.persistence.EntityExistsValidator; import net.hostsharing.hsadminng.persistence.EntityExistsValidator;
import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.Validate;
import org.hibernate.Hibernate; import org.hibernate.Hibernate;
@ -36,13 +37,16 @@ public class HsOfficeDebitorController implements HsOfficeDebitorsApi {
private Context context; private Context context;
@Autowired @Autowired
private StandardMapper mapper; private StrictMapper mapper;
@Autowired @Autowired
private HsOfficeDebitorRepository debitorRepo; private HsOfficeDebitorRepository debitorRepo;
@Autowired @Autowired
private HsOfficeRelationRealRepository relrealRepo; private HsOfficeRelationRealRepository realRelRepo;
@Autowired
private HsOfficeBankAccountRepository bankAccountRepo;
@Autowired @Autowired
private EntityExistsValidator entityValidator; private EntityExistsValidator entityValidator;
@ -88,18 +92,18 @@ public class HsOfficeDebitorController implements HsOfficeDebitorsApi {
Validate.isTrue(body.getDebitorRel() == null || body.getDebitorRel().getMark() == null, Validate.isTrue(body.getDebitorRel() == null || body.getDebitorRel().getMark() == null,
"ERROR: [400] debitorRel.mark must be null"); "ERROR: [400] debitorRel.mark must be null");
final var entityToSave = mapper.map(body, HsOfficeDebitorEntity.class); final var entityToSave = mapper.map(body, HsOfficeDebitorEntity.class, RESOURCE_TO_ENTITY_POSTMAPPER);
if (body.getDebitorRel() != null) { if (body.getDebitorRel() != null) { // FIXME: move this into RESOURCE_TO_ENTITY_POSTMAPPER
final var debitorRel = mapper.map("debitorRel.", body.getDebitorRel(), HsOfficeRelationRealEntity.class); final var debitorRel = mapper.map("debitorRel.", body.getDebitorRel(), HsOfficeRelationRealEntity.class);
debitorRel.setType(DEBITOR); debitorRel.setType(DEBITOR);
entityValidator.validateEntityExists("debitorRel.anchorUuid", debitorRel.getAnchor()); entityValidator.validateEntityExists("debitorRel.anchorUuid", debitorRel.getAnchor());
entityValidator.validateEntityExists("debitorRel.holderUuid", debitorRel.getHolder()); entityValidator.validateEntityExists("debitorRel.holderUuid", debitorRel.getHolder());
entityValidator.validateEntityExists("debitorRel.contactUuid", debitorRel.getContact()); entityValidator.validateEntityExists("debitorRel.contactUuid", debitorRel.getContact());
entityToSave.setDebitorRel(relrealRepo.save(debitorRel)); entityToSave.setDebitorRel(realRelRepo.save(debitorRel));
} else { } else {
final var debitorRelOptional = relrealRepo.findByUuid(body.getDebitorRelUuid()); final var debitorRelOptional = realRelRepo.findByUuid(body.getDebitorRelUuid());
debitorRelOptional.ifPresentOrElse( debitorRelOptional.ifPresentOrElse(
debitorRel -> {entityToSave.setDebitorRel(relrealRepo.save(debitorRel));}, debitorRel -> {entityToSave.setDebitorRel(realRelRepo.save(debitorRel));},
() -> { () -> {
throw new ValidationException( throw new ValidationException(
"Unable to find RealRelation by debitorRelUuid: " + body.getDebitorRelUuid()); "Unable to find RealRelation by debitorRelUuid: " + body.getDebitorRelUuid());
@ -107,7 +111,7 @@ public class HsOfficeDebitorController implements HsOfficeDebitorsApi {
} }
final var savedEntity = debitorRepo.save(entityToSave); final var savedEntity = debitorRepo.save(entityToSave);
em.flush(); em.flush(); // FIXME: necessary?
em.refresh(savedEntity); em.refresh(savedEntity);
final var uri = final var uri =
@ -191,6 +195,14 @@ public class HsOfficeDebitorController implements HsOfficeDebitorsApi {
return ResponseEntity.ok(mapped); return ResponseEntity.ok(mapped);
} }
final BiConsumer<HsOfficeDebitorInsertResource, HsOfficeDebitorEntity> RESOURCE_TO_ENTITY_POSTMAPPER = (resource, entity) -> {
if (resource.getRefundBankAccountUuid() != null) {
final var bankAccountEntity = bankAccountRepo.findByUuid(resource.getRefundBankAccountUuid())
.orElseThrow(() -> new ValidationException()); // FIXME
entity.setRefundBankAccount(bankAccountEntity);
}
};
final BiConsumer<HsOfficeDebitorEntity, HsOfficeDebitorResource> ENTITY_TO_RESOURCE_POSTMAPPER = (entity, resource) -> { final BiConsumer<HsOfficeDebitorEntity, HsOfficeDebitorResource> ENTITY_TO_RESOURCE_POSTMAPPER = (entity, resource) -> {
resource.setDebitorNumber(entity.getTaggedDebitorNumber()); resource.setDebitorNumber(entity.getTaggedDebitorNumber());
}; };

View File

@ -7,13 +7,15 @@ import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeMember
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeMembershipPatchResource; import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeMembershipPatchResource;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeMembershipResource; import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeMembershipResource;
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity; import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity;
import net.hostsharing.hsadminng.mapper.StandardMapper; import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerRepository;
import net.hostsharing.hsadminng.mapper.StrictMapper;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder; import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder;
import jakarta.persistence.EntityNotFoundException;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
@ -28,7 +30,10 @@ public class HsOfficeMembershipController implements HsOfficeMembershipsApi {
private Context context; private Context context;
@Autowired @Autowired
private StandardMapper mapper; private StrictMapper mapper;
@Autowired
private HsOfficePartnerRepository partnerRepo;
@Autowired @Autowired
private HsOfficeMembershipRepository membershipRepo; private HsOfficeMembershipRepository membershipRepo;
@ -68,7 +73,7 @@ public class HsOfficeMembershipController implements HsOfficeMembershipsApi {
context.define(currentSubject, assumedRoles); context.define(currentSubject, assumedRoles);
final var entityToSave = mapper.map(body, HsOfficeMembershipEntity.class); final var entityToSave = mapper.map(body, HsOfficeMembershipEntity.class, SEPA_MANDATE_RESOURCE_TO_ENTITY_POSTMAPPER);
final var saved = membershipRepo.save(entityToSave); final var saved = membershipRepo.save(entityToSave);
@ -164,5 +169,12 @@ public class HsOfficeMembershipController implements HsOfficeMembershipsApi {
if (entity.getValidity().hasUpperBound()) { if (entity.getValidity().hasUpperBound()) {
resource.setValidTo(entity.getValidity().upper().minusDays(1)); resource.setValidTo(entity.getValidity().upper().minusDays(1));
} }
resource.getPartner().setPartnerNumber(entity.getPartner().getTaggedPartnerNumber()); // FIXME: use partner mapper?
};
final BiConsumer<HsOfficeMembershipInsertResource, HsOfficeMembershipEntity> SEPA_MANDATE_RESOURCE_TO_ENTITY_POSTMAPPER = (resource, entity) -> {
entity.setPartner(partnerRepo.findByUuid(resource.getPartnerUuid())
.orElseThrow(() -> new EntityNotFoundException(
"ERROR: [400] partnerUuid %s not found".formatted(resource.getPartnerUuid()))));
}; };
} }

View File

@ -2,18 +2,18 @@ package net.hostsharing.hsadminng.hs.office.membership;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeMembershipPatchResource; import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeMembershipPatchResource;
import net.hostsharing.hsadminng.mapper.EntityPatcher; import net.hostsharing.hsadminng.mapper.EntityPatcher;
import net.hostsharing.hsadminng.mapper.StandardMapper;
import net.hostsharing.hsadminng.mapper.OptionalFromJson; import net.hostsharing.hsadminng.mapper.OptionalFromJson;
import net.hostsharing.hsadminng.mapper.StrictMapper;
import java.util.Optional; import java.util.Optional;
public class HsOfficeMembershipEntityPatcher implements EntityPatcher<HsOfficeMembershipPatchResource> { public class HsOfficeMembershipEntityPatcher implements EntityPatcher<HsOfficeMembershipPatchResource> {
private final StandardMapper mapper; private final StrictMapper mapper;
private final HsOfficeMembershipEntity entity; private final HsOfficeMembershipEntity entity;
public HsOfficeMembershipEntityPatcher( public HsOfficeMembershipEntityPatcher(
final StandardMapper mapper, final StrictMapper mapper,
final HsOfficeMembershipEntity entity) { final HsOfficeMembershipEntity entity) {
this.mapper = mapper; this.mapper = mapper;
this.entity = entity; this.entity = entity;

View File

@ -16,18 +16,33 @@ public interface HsOfficePartnerRepository extends Repository<HsOfficePartnerEnt
@Timed("app.office.partners.repo.findAll") @Timed("app.office.partners.repo.findAll")
List<HsOfficePartnerEntity> findAll(); // TODO.refa: move to a repo in test sources List<HsOfficePartnerEntity> findAll(); // TODO.refa: move to a repo in test sources
@Query(""" // @Query("""
SELECT partner FROM HsOfficePartnerEntity partner // SELECT partner FROM HsOfficePartnerEntity partner
JOIN HsOfficeRelationRealEntity rel ON rel.uuid = partner.partnerRel.uuid // JOIN HsOfficeRelationRealEntity rel ON rel.uuid = partner.partnerRel.uuid
JOIN HsOfficeContactRealEntity contact ON contact.uuid = rel.contact.uuid // JOIN HsOfficeContactRealEntity contact ON contact.uuid = rel.contact.uuid
JOIN HsOfficePersonRealEntity person ON person.uuid = rel.holder.uuid // JOIN HsOfficePersonRealEntity person ON person.uuid = rel.holder.uuid
WHERE :name is null // LEFT JOIN HsOfficePartnerDetailsEntity details ON partner.details.uuid = details.uuid
OR partner.details.birthName like concat(cast(:name as text), '%') // WHERE :name is null
OR contact.caption like concat(cast(:name as text), '%') // OR (details IS NOT NULL AND details.birthName like concat(cast(:name as text), '%'))
OR person.tradeName like concat(cast(:name as text), '%') // OR contact.caption like concat(cast(:name as text), '%')
OR person.givenName like concat(cast(:name as text), '%') // OR person.tradeName like concat(cast(:name as text), '%')
OR person.familyName like concat(cast(:name as text), '%') // OR person.givenName like concat(cast(:name as text), '%')
""") // OR person.familyName like concat(cast(:name as text), '%')
// """)
@Query(value = """
select partner.uuid, partner.detailsuuid, partner.partnernumber, partner.partnerreluuid, partner.version
from hs_office.partner_rv partner
join hs_office.relation partnerRel on partnerRel.uuid = partner.partnerreluuid
join hs_office.contact contact on contact.uuid = partnerRel.contactuuid
join hs_office.person partnerPerson on partnerPerson.uuid = partnerRel.holderuuid
left join hs_office.partner_details_rv partnerDetails on partnerDetails.uuid = partner.detailsuuid
where :name is null
or (partnerDetails.uuid is not null and partnerDetails.birthname like (cast(:name as text) || '%') escape '')
or contact.caption like (cast(:name as text) || '%') escape ''
or partnerPerson.tradename like (cast(:name as text) || '%') escape ''
or partnerPerson.givenname like (cast(:name as text) || '%') escape ''
or partnerPerson.familyname like (cast(:name as text) || '%') escape ''
""", nativeQuery = true)
@Timed("app.office.partners.repo.findPartnerByOptionalNameLike") @Timed("app.office.partners.repo.findPartnerByOptionalNameLike")
List<HsOfficePartnerEntity> findPartnerByOptionalNameLike(String name); List<HsOfficePartnerEntity> findPartnerByOptionalNameLike(String name);

View File

@ -3,7 +3,6 @@ package net.hostsharing.hsadminng.hs.office.person;
import lombok.Getter; import lombok.Getter;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import lombok.Setter; import lombok.Setter;
import lombok.experimental.FieldNameConstants;
import lombok.experimental.SuperBuilder; import lombok.experimental.SuperBuilder;
import net.hostsharing.hsadminng.errors.DisplayAs; import net.hostsharing.hsadminng.errors.DisplayAs;
@ -17,7 +16,6 @@ import jakarta.persistence.Table;
@Setter @Setter
@NoArgsConstructor @NoArgsConstructor
@SuperBuilder(toBuilder = true) @SuperBuilder(toBuilder = true)
@FieldNameConstants
@DisplayAs("RealPerson") @DisplayAs("RealPerson")
public class HsOfficePersonRealEntity extends HsOfficePerson<HsOfficePersonRealEntity> { public class HsOfficePersonRealEntity extends HsOfficePerson<HsOfficePersonRealEntity> {
} }

View File

@ -2,11 +2,14 @@ package net.hostsharing.hsadminng.hs.office.sepamandate;
import io.micrometer.core.annotation.Timed; import io.micrometer.core.annotation.Timed;
import net.hostsharing.hsadminng.context.Context; 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.hsadminng.hs.office.generated.api.v1.api.HsOfficeSepaMandatesApi; import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficeSepaMandatesApi;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeDebitorResource;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeSepaMandateInsertResource; 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.HsOfficeSepaMandatePatchResource;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeSepaMandateResource; import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeSepaMandateResource;
import net.hostsharing.hsadminng.mapper.StandardMapper; import net.hostsharing.hsadminng.mapper.StrictMapper;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@ -15,6 +18,7 @@ import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBui
import jakarta.persistence.EntityManager; import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext; import jakarta.persistence.PersistenceContext;
import jakarta.validation.ValidationException;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
@ -29,7 +33,13 @@ public class HsOfficeSepaMandateController implements HsOfficeSepaMandatesApi {
private Context context; private Context context;
@Autowired @Autowired
private StandardMapper mapper; private StrictMapper mapper;
@Autowired
private HsOfficeDebitorRepository debitorRepo;
@Autowired
private HsOfficeBankAccountRepository bankAccountRepo;
@Autowired @Autowired
private HsOfficeSepaMandateRepository sepaMandateRepo; private HsOfficeSepaMandateRepository sepaMandateRepo;
@ -137,10 +147,22 @@ public class HsOfficeSepaMandateController implements HsOfficeSepaMandatesApi {
if (entity.getValidity().hasUpperBound()) { if (entity.getValidity().hasUpperBound()) {
resource.setValidTo(entity.getValidity().upper().minusDays(1)); resource.setValidTo(entity.getValidity().upper().minusDays(1));
} }
resource.setDebitor(mapper.map(entity.getDebitor(), HsOfficeDebitorResource.class));
resource.getDebitor().setDebitorNumber(entity.getDebitor().getTaggedDebitorNumber()); resource.getDebitor().setDebitorNumber(entity.getDebitor().getTaggedDebitorNumber());
resource.getDebitor().getPartner().setPartnerNumber(entity.getDebitor().getPartner().getTaggedPartnerNumber());
}; };
final BiConsumer<HsOfficeSepaMandateInsertResource, HsOfficeSepaMandateEntity> SEPA_MANDATE_RESOURCE_TO_ENTITY_POSTMAPPER = (resource, entity) -> { final BiConsumer<HsOfficeSepaMandateInsertResource, HsOfficeSepaMandateEntity> SEPA_MANDATE_RESOURCE_TO_ENTITY_POSTMAPPER = (resource, entity) -> {
entity.setValidity(toPostgresDateRange(resource.getValidFrom(), resource.getValidTo())); entity.setValidity(toPostgresDateRange(resource.getValidFrom(), resource.getValidTo()));
entity.setDebitor(debitorRepo.findByUuid(resource.getDebitorUuid()).orElseThrow( () ->
new ValidationException(
"debitor.uuid='" + resource.getDebitorUuid() + "' not found or not accessible"
)
));
entity.setBankAccount(bankAccountRepo.findByUuid(resource.getBankAccountUuid()).orElseThrow( () ->
new ValidationException(
"bankAccount.uuid='" + resource.getBankAccountUuid() + "' not found or not accessible"
)
));
}; };
} }

View File

@ -90,6 +90,7 @@ components:
type: boolean type: boolean
vatReverseCharge: vatReverseCharge:
type: boolean type: boolean
# TODO.feat: alternatively the complete refundBankAccount
refundBankAccount.uuid: refundBankAccount.uuid:
type: string type: string
format: uuid format: uuid

View File

@ -374,7 +374,6 @@ class HsOfficeContactControllerAcceptanceTest extends ContextBasedTestWithCleanu
return jpaAttempt.transacted(() -> { return jpaAttempt.transacted(() -> {
context.define(creatingUser); context.define(creatingUser);
final var newContact = HsOfficeContactRbacEntity.builder() final var newContact = HsOfficeContactRbacEntity.builder()
.uuid(UUID.randomUUID())
.caption("Temp from " + Context.getCallerMethodNameFromStackFrame(1) ) .caption("Temp from " + Context.getCallerMethodNameFromStackFrame(1) )
.postalAddress(Map.ofEntries( .postalAddress(Map.ofEntries(
entry("name", RandomStringUtils.randomAlphabetic(6) + " " + RandomStringUtils.randomAlphabetic(10)), entry("name", RandomStringUtils.randomAlphabetic(6) + " " + RandomStringUtils.randomAlphabetic(10)),

View File

@ -197,7 +197,7 @@ class HsOfficeCoopSharesTransactionControllerAcceptanceTest extends ContextBased
@Test @Test
void globalAdmin_canAddCoopSharesReversalTransaction() { void globalAdmin_canAddCoopSharesReversalTransaction() {
context.define("superuser-alex@hostsharing.net"); context.define("superuser-alex@hostsharing.net", "global#global:ADMIN");
final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000101).orElseThrow(); final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000101).orElseThrow();
final var givenTransaction = jpaAttempt.transacted(() -> { final var givenTransaction = jpaAttempt.transacted(() -> {
// TODO.impl: introduce something like transactedAsSuperuser / transactedAs("...", ...) // TODO.impl: introduce something like transactedAsSuperuser / transactedAs("...", ...)
@ -269,7 +269,10 @@ class HsOfficeCoopSharesTransactionControllerAcceptanceTest extends ContextBased
final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000101).orElseThrow(); final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000101).orElseThrow();
RestAssured // @formatter:off RestAssured // @formatter:off
.given().header("current-subject", "superuser-alex@hostsharing.net").contentType(ContentType.JSON).body(""" .given()
.header("current-subject", "superuser-alex@hostsharing.net")
.contentType(ContentType.JSON)
.body("""
{ {
"membership.uuid": "%s", "membership.uuid": "%s",
"transactionType": "CANCELLATION", "transactionType": "CANCELLATION",
@ -278,7 +281,16 @@ class HsOfficeCoopSharesTransactionControllerAcceptanceTest extends ContextBased
"reference": "temp ref X", "reference": "temp ref X",
"comment": "just some test coop shares transaction" "comment": "just some test coop shares transaction"
} }
""".formatted(givenMembership.getUuid())).port(port).when().post("http://localhost/api/hs/office/coopsharestransactions").then().log().all().assertThat().statusCode(400).contentType(ContentType.JSON).body("", lenientlyEquals(""" """.formatted(givenMembership.getUuid()))
.port(port)
.when()
.post("http://localhost/api/hs/office/coopsharestransactions")
.then()
.log().all()
.assertThat()
.statusCode(400)
.contentType(ContentType.JSON)
.body("", lenientlyEquals("""
{ {
"statusCode": 400, "statusCode": 400,
"statusPhrase": "Bad Request", "statusPhrase": "Bad Request",

View File

@ -1,7 +1,7 @@
package net.hostsharing.hsadminng.hs.office.coopshares; package net.hostsharing.hsadminng.hs.office.coopshares;
import net.hostsharing.hsadminng.context.Context; import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.mapper.StandardMapper; import net.hostsharing.hsadminng.mapper.StrictMapper;
import net.hostsharing.hsadminng.rbac.test.JsonBuilder; import net.hostsharing.hsadminng.rbac.test.JsonBuilder;
import net.hostsharing.hsadminng.config.DisableSecurityConfig; import net.hostsharing.hsadminng.config.DisableSecurityConfig;
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.ParameterizedTest;
@ -36,7 +36,7 @@ class HsOfficeCoopSharesTransactionControllerRestTest {
@MockBean @MockBean
@SuppressWarnings("unused") // not used in test, but in controller class @SuppressWarnings("unused") // not used in test, but in controller class
StandardMapper mapper; StrictMapper mapper;
@MockBean @MockBean
HsOfficeCoopSharesTransactionRepository coopSharesTransactionRepo; HsOfficeCoopSharesTransactionRepository coopSharesTransactionRepo;

View File

@ -430,7 +430,6 @@ class HsOfficeMembershipControllerAcceptanceTest extends ContextBasedTestWithCle
context.define("superuser-alex@hostsharing.net"); context.define("superuser-alex@hostsharing.net");
final var givenPartner = partnerRepo.findPartnerByOptionalNameLike(partnerName).get(0); final var givenPartner = partnerRepo.findPartnerByOptionalNameLike(partnerName).get(0);
final var newMembership = HsOfficeMembershipEntity.builder() final var newMembership = HsOfficeMembershipEntity.builder()
.uuid(UUID.randomUUID())
.partner(givenPartner) .partner(givenPartner)
.memberNumberSuffix(TEMP_MEMBER_NUMBER_SUFFIX) .memberNumberSuffix(TEMP_MEMBER_NUMBER_SUFFIX)
.validity(Range.closedInfinite(LocalDate.parse("2022-11-01"))) .validity(Range.closedInfinite(LocalDate.parse("2022-11-01")))

View File

@ -4,7 +4,7 @@ import io.hypersistence.utils.hibernate.type.range.Range;
import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorEntity; import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorEntity;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeMembershipPatchResource; import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeMembershipPatchResource;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeMembershipStatusResource; import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeMembershipStatusResource;
import net.hostsharing.hsadminng.mapper.StandardMapper; import net.hostsharing.hsadminng.mapper.StrictMapper;
import net.hostsharing.hsadminng.persistence.EntityManagerWrapper; import net.hostsharing.hsadminng.persistence.EntityManagerWrapper;
import net.hostsharing.hsadminng.rbac.test.PatchUnitTestBase; import net.hostsharing.hsadminng.rbac.test.PatchUnitTestBase;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
@ -40,7 +40,7 @@ class HsOfficeMembershipEntityPatcherUnitTest extends PatchUnitTestBase<
@Mock @Mock
private EntityManagerWrapper em; private EntityManagerWrapper em;
private StandardMapper mapper = new StandardMapper(em); private StrictMapper mapper = new StrictMapper(em);
@BeforeEach @BeforeEach
void initMocks() { void initMocks() {

View File

@ -16,7 +16,7 @@ import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.boot.test.mock.mockito.MockBean; // FIXME: use MockitoBean
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import org.springframework.orm.jpa.JpaSystemException; import org.springframework.orm.jpa.JpaSystemException;
@ -32,6 +32,7 @@ import static net.hostsharing.hsadminng.rbac.role.RawRbacObjectEntity.objectDisp
import static net.hostsharing.hsadminng.rbac.role.RawRbacRoleEntity.distinctRoleNamesOf; import static net.hostsharing.hsadminng.rbac.role.RawRbacRoleEntity.distinctRoleNamesOf;
import static net.hostsharing.hsadminng.mapper.Array.from; import static net.hostsharing.hsadminng.mapper.Array.from;
import static net.hostsharing.hsadminng.rbac.role.RbacRoleType.ADMIN; import static net.hostsharing.hsadminng.rbac.role.RbacRoleType.ADMIN;
import static net.hostsharing.hsadminng.rbac.role.RbacRoleType.AGENT;
import static net.hostsharing.hsadminng.rbac.test.JpaAttempt.attempt; import static net.hostsharing.hsadminng.rbac.test.JpaAttempt.attempt;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -207,9 +208,23 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTestWithClean
} }
@Test @Test
public void normalUser_canViewOnlyRelatedPartners() { public void partnerAgent_canViewOnlyRelatedPartnersWithoutDetails() {
// given: // given:
context("person-FirstGmbH@example.com"); context("person-FirstGmbH@example.com",
"hs_office.relation#HostsharingeG-with-PARTNER-FirstGmbH:AGENT");
// when:
final var result = partnerRepo.findPartnerByOptionalNameLike(null);
// then:
exactlyThesePartnersAreReturned(result, "partner(P-10001: LP First GmbH, first contact)");
}
@Test
public void partnerTenant_canViewRelatedPartnersButWithoutDetails() {
// given:
context("person-FirstGmbH@example.com",
"hs_office.relation#HostsharingeG-with-PARTNER-FirstGmbH:TENANT");
// when: // when:
final var result = partnerRepo.findPartnerByOptionalNameLike(null); final var result = partnerRepo.findPartnerByOptionalNameLike(null);
@ -289,19 +304,19 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTestWithClean
} }
@Test @Test
public void partnerRelationAgent_canUpdateRelatedPartner() { public void partnerRelationAgent_canUpdateRelatedPartnerDetails() {
// given // given
context("superuser-alex@hostsharing.net"); context("superuser-alex@hostsharing.net");
final var givenPartner = givenSomeTemporaryHostsharingPartner(20037, "Erben Bessler", "ninth"); final var givenPartner = givenSomeTemporaryHostsharingPartner(20037, "Erben Bessler", "ninth");
assertThatPartnerIsVisibleForUserWithRole( assertThatPartnerIsVisibleForUserWithRole(
givenPartner, givenPartner,
"hs_office.person#ErbenBesslerMelBessler:ADMIN"); givenPartner.getPartnerRel().roleId(AGENT));
assertThatPartnerActuallyInDatabase(givenPartner); assertThatPartnerActuallyInDatabase(givenPartner);
// when // when
final var result = jpaAttempt.transacted(() -> { final var result = jpaAttempt.transacted(() -> {
context("superuser-alex@hostsharing.net", context("superuser-alex@hostsharing.net",
"hs_office.person#ErbenBesslerMelBessler:ADMIN"); givenPartner.getPartnerRel().roleId(AGENT));
givenPartner.getDetails().setBirthName("new birthname"); givenPartner.getDetails().setBirthName("new birthname");
return partnerRepo.save(givenPartner); return partnerRepo.save(givenPartner);
}); });
@ -310,30 +325,6 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTestWithClean
result.assertSuccessful(); result.assertSuccessful();
} }
@Test
public void partnerRelationTenant_canNotUpdateRelatedPartner() {
// given
context("superuser-alex@hostsharing.net");
final var givenPartner = givenSomeTemporaryHostsharingPartner(20037, "Erben Bessler", "ninth");
assertThatPartnerIsVisibleForUserWithRole(
givenPartner,
"hs_office.person#ErbenBesslerMelBessler:ADMIN");
assertThatPartnerActuallyInDatabase(givenPartner);
// when
final var result = jpaAttempt.transacted(() -> {
context("superuser-alex@hostsharing.net",
"hs_office.relation#HostsharingeG-with-PARTNER-ErbenBesslerMelBessler:TENANT");
givenPartner.getDetails().setBirthName("new birthname");
return partnerRepo.save(givenPartner);
});
// then
result.assertExceptionWithRootCauseMessage(JpaSystemException.class,
"ERROR: [403] insert into hs_office.partner_details ",
" not allowed for current subjects {hs_office.relation#HostsharingeG-with-PARTNER-ErbenBesslerMelBessler:TENANT}");
}
private void assertThatPartnerActuallyInDatabase(final HsOfficePartnerEntity saved) { private void assertThatPartnerActuallyInDatabase(final HsOfficePartnerEntity saved) {
final var found = partnerRepo.findByUuid(saved.getUuid()); final var found = partnerRepo.findByUuid(saved.getUuid());
assertThat(found).isNotEmpty().get().isNotSameAs(saved).extracting(HsOfficePartnerEntity::toString).isEqualTo(saved.toString()); assertThat(found).isNotEmpty().get().isNotSameAs(saved).extracting(HsOfficePartnerEntity::toString).isEqualTo(saved.toString());
@ -463,7 +454,10 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTestWithClean
.details(HsOfficePartnerDetailsEntity.builder().build()) .details(HsOfficePartnerDetailsEntity.builder().build())
.build(); .build();
return partnerRepo.save(newPartner); final var savedPartner = partnerRepo.save(newPartner);
em.flush();
final var partner = em.find(savedPartner.getClass(), savedPartner.getUuid());
return savedPartner;
}).assertSuccessful().returnedValue(); }).assertSuccessful().returnedValue();
} }
@ -484,13 +478,13 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTestWithClean
void exactlyThesePartnersAreReturned(final List<HsOfficePartnerEntity> actualResult, final String... partnerNames) { void exactlyThesePartnersAreReturned(final List<HsOfficePartnerEntity> actualResult, final String... partnerNames) {
assertThat(actualResult) assertThat(actualResult)
.extracting(partnerEntity -> partnerEntity.toString()) .extracting(HsOfficePartnerEntity::toString)
.containsExactlyInAnyOrder(partnerNames); .containsExactlyInAnyOrder(partnerNames);
} }
void allThesePartnersAreReturned(final List<HsOfficePartnerEntity> actualResult, final String... partnerNames) { void allThesePartnersAreReturned(final List<HsOfficePartnerEntity> actualResult, final String... partnerNames) {
assertThat(actualResult) assertThat(actualResult)
.extracting(partnerEntity -> partnerEntity.toString()) .extracting(HsOfficePartnerEntity::toString)
.contains(partnerNames); .contains(partnerNames);
} }

View File

@ -331,7 +331,6 @@ class HsOfficePersonControllerAcceptanceTest extends ContextBasedTestWithCleanup
return jpaAttempt.transacted(() -> { return jpaAttempt.transacted(() -> {
context.define(creatingUser); context.define(creatingUser);
final var newPerson = HsOfficePersonRealEntity.builder() final var newPerson = HsOfficePersonRealEntity.builder()
.uuid(UUID.randomUUID())
.personType(HsOfficePersonType.LEGAL_PERSON) .personType(HsOfficePersonType.LEGAL_PERSON)
.tradeName("Temp " + Context.getCallerMethodNameFromStackFrame(2)) .tradeName("Temp " + Context.getCallerMethodNameFromStackFrame(2))
.familyName(RandomStringUtils.randomAlphabetic(10) + "@example.org") .familyName(RandomStringUtils.randomAlphabetic(10) + "@example.org")

View File

@ -180,10 +180,9 @@ class HsOfficeSepaMandateControllerAcceptanceTest extends ContextBasedTestWithCl
void globalAdmin_canNotPostNewSepaMandateWhenDebitorUuidIsMissing() { void globalAdmin_canNotPostNewSepaMandateWhenDebitorUuidIsMissing() {
context.define("superuser-alex@hostsharing.net"); context.define("superuser-alex@hostsharing.net");
final var givenDebitor = debitorRepo.findDebitorsByOptionalNameLike("Third").get(0);
final var givenBankAccount = bankAccountRepo.findByIbanOrderByIbanAsc("DE02200505501015871393").get(0); final var givenBankAccount = bankAccountRepo.findByIbanOrderByIbanAsc("DE02200505501015871393").get(0);
final var location = RestAssured // @formatter:off RestAssured // @formatter:off
.given() .given()
.header("current-subject", "superuser-alex@hostsharing.net") .header("current-subject", "superuser-alex@hostsharing.net")
.contentType(ContentType.JSON) .contentType(ContentType.JSON)
@ -227,12 +226,12 @@ class HsOfficeSepaMandateControllerAcceptanceTest extends ContextBasedTestWithCl
.post("http://localhost/api/hs/office/sepamandates") .post("http://localhost/api/hs/office/sepamandates")
.then().log().all().assertThat() .then().log().all().assertThat()
.statusCode(400) .statusCode(400)
.body("message", is("ERROR: [400] Unable to find BankAccount with uuid 00000000-0000-0000-0000-000000000000")); .body("message", is("ERROR: [400] bankAccount.uuid='00000000-0000-0000-0000-000000000000' not found or not accessible"));
// @formatter:on // @formatter:on
} }
@Test @Test
void globalAdmin_canNotPostNewSepaMandate_ifPersonDoesNotExist() { void globalAdmin_canNotPostNewSepaMandate_ifDebitorDoesNotExist() {
context.define("superuser-alex@hostsharing.net"); context.define("superuser-alex@hostsharing.net");
final var givenDebitorUuid = UUID.fromString("00000000-0000-0000-0000-000000000000"); final var givenDebitorUuid = UUID.fromString("00000000-0000-0000-0000-000000000000");
@ -257,7 +256,7 @@ class HsOfficeSepaMandateControllerAcceptanceTest extends ContextBasedTestWithCl
.post("http://localhost/api/hs/office/sepamandates") .post("http://localhost/api/hs/office/sepamandates")
.then().log().all().assertThat() .then().log().all().assertThat()
.statusCode(400) .statusCode(400)
.body("message", is("ERROR: [400] Unable to find Debitor with uuid 00000000-0000-0000-0000-000000000000")); .body("message", is("ERROR: [400] debitor.uuid='00000000-0000-0000-0000-000000000000' not found or not accessible"));
// @formatter:on // @formatter:on
} }
} }
@ -529,7 +528,6 @@ class HsOfficeSepaMandateControllerAcceptanceTest extends ContextBasedTestWithCl
.orElse(givenDebitor.getPartner().getPartnerRel().getHolder().getFamilyName()); .orElse(givenDebitor.getPartner().getPartnerRel().getHolder().getFamilyName());
final var givenBankAccount = bankAccountRepo.findByOptionalHolderLike(bankAccountHolder).get(0); final var givenBankAccount = bankAccountRepo.findByOptionalHolderLike(bankAccountHolder).get(0);
final var newSepaMandate = HsOfficeSepaMandateEntity.builder() final var newSepaMandate = HsOfficeSepaMandateEntity.builder()
.uuid(UUID.randomUUID())
.debitor(givenDebitor) .debitor(givenDebitor)
.bankAccount(givenBankAccount) .bankAccount(givenBankAccount)
.reference("temp ref CAT Z") .reference("temp ref CAT Z")

View File

@ -6,7 +6,7 @@ management:
endpoints: endpoints:
web: web:
exposure: exposure:
include: info, health, metrics, metric-links include: info, health, metrics, metric-links, mappings, openapi, swaggerui
spring: spring:
sql: sql: