Upgrade to SpringBoot 3.4.1 and dependencies (#147)

Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #147
Reviewed-by: Marc Sandlus <marc.sandlus@hostsharing.net>
This commit is contained in:
Michael Hoennig 2025-01-15 13:43:20 +01:00
parent a7ffee9348
commit 9c8d7616e3
111 changed files with 1095 additions and 758 deletions

View File

@ -93,7 +93,8 @@ alias gw-spotless='./gradlew spotlessApply -x pitest -x test -x :processResource
alias gw-test='. .aliases; ./gradlew test'
alias gw-check='. .aliases; gw test check -x pitest'
alias cas-curl='bin/cas-curl'
alias howto=bin/howto
alias cas-curl=bin/cas-curl
# etc/docker-compose.yml limits CPUs+MEM and includes a PostgreSQL config for analysing slow queries
alias gw-importOfficeData-in-docker-compose='
@ -108,3 +109,4 @@ source .environment
alias scenario-reports-upload='./gradlew scenarioTests convertMarkdownToHtml && ssh hsh03-hsngdev@h50.hostsharing.net "rm -f doms/hsngdev.hs-example.de/htdocs-ssl/scenarios/office/*.html" && scp build/doc/scenarios/*.html hsh03-hsngdev@h50.hostsharing.net:doms/hsngdev.hs-example.de/htdocs-ssl/scenarios/office'
alias scenario-reports-open='open https://hsngdev.hs-example.de/scenarios/office'

View File

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

View File

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

View File

@ -617,6 +617,11 @@ Besides the following *How Tos* you can also find several *How Tos* in the sourc
grep -r HOWTO src
```
also try this (assumed you've sourced .aliases):
```sh
howto
```
### How to Configure .pgpass for the Default PostgreSQL Database?
To access the default database schema as used during development, add this line to your `.pgpass` file in your users home directory:

86
bin/howto Executable file
View File

@ -0,0 +1,86 @@
#!/usr/bin/python3
import os
import sys
from urllib.parse import urljoin, quote
def path_to_file_uri(path):
"""
Converts a file path to a file URI.
Handles absolute and relative paths.
"""
abs_path = os.path.abspath(path)
return urljoin("file://", quote(abs_path))
def is_binary_file(filepath, chunk_size=1024):
"""
Prüft, ob eine Datei binär ist, indem sie den Inhalt der Datei auf nicht-druckbare Zeichen untersucht.
"""
try:
with open(filepath, "rb") as file:
chunk = file.read(chunk_size)
if b"\0" in chunk: # Nullbyte ist ein typisches Zeichen für Binärdateien
return True
return False
except Exception as e:
print(f"Fehler beim Prüfen, ob Datei binär ist: {filepath}: {e}")
return True
def search_keywords_in_files(keywords):
if not keywords:
print("Bitte geben Sie mindestens ein Stichwort an.")
sys.exit(1)
# Allowed comment symbols
comment_symbols = {"//", "#", ";"}
for root, dirs, files in os.walk("."):
# Ausschließen bestimmter Verzeichnisse
dirs[:] = [d for d in dirs if d not in {".git", "build"}]
for file in files:
filepath = os.path.join(root, file)
# Überspringen von Binärdateien
if is_binary_file(filepath):
continue
try:
with open(filepath, "r", encoding="utf-8") as f:
lines = f.readlines()
for line_number, line in enumerate(lines, start=1):
stripped_line = line.lstrip() # Entfernt führende Leerzeichen
for symbol in comment_symbols:
if stripped_line.startswith(symbol):
# Entfernt das Kommentarzeichen und nachfolgende Leerzeichen
howtoMatch = stripped_line[len(symbol):].lstrip()
if howtoMatch.startswith(("HOWTO ", "HOWTO: ", "How to ")):
if all(keyword in howtoMatch.lower() for keyword in keywords):
# Titelzeile ohne Kommentarzeichen
print(howtoMatch.rstrip())
# Ausgabe nachfolgender Zeilen mit dem gleichen Kommentar-Präfix
for subsequent_line in lines[line_number:]:
subsequent_line = subsequent_line.lstrip()
if subsequent_line.startswith(symbol):
# Entfernt Kommentarzeichen aus Folgezeilen
print("\t" + subsequent_line[len(symbol):].strip())
else:
break
# Link mit Zeilennummer
print(f"--> {path_to_file_uri(filepath)}:{line_number}")
# Abstand zwischen Matches
print()
break
except Exception as e:
print(f"Fehler beim Lesen der Datei {filepath}: {e}")
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Verwendung: bin/howto <Stichwort1> <Stichwort2> ...")
sys.exit(1)
search_keywords_in_files([arg.lower() for arg in sys.argv[1:]])

View File

@ -1,17 +1,20 @@
plugins {
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.openapiprocessor.openapi-processor' version '2023.2'
id 'com.github.jk1.dependency-license-report' version '2.9'
id "org.owasp.dependencycheck" version "11.1.1"
id "com.diffplug.spotless" version "7.0.0"
id "org.owasp.dependencycheck" version "12.0.0"
id "com.diffplug.spotless" version "7.0.2"
id 'jacoco'
id 'info.solidsoft.pitest' version '1.15.0'
id 'se.patrikerdes.use-latest-versions' version '0.2.18'
id 'com.github.ben-manes.versions' version '0.51.0'
}
// HOWTO: find out which dependency versions are managed by Spring Boot:
// https://docs.spring.io/spring-boot/appendix/dependency-versions/coordinates.html
group = 'net.hostsharing'
version = '0.0.1-SNAPSHOT'
@ -20,6 +23,9 @@ wrapper {
gradleVersion = '8.5'
}
// TODO.impl: self-attaching is deprecated, see:
// https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html#0.3
configurations {
compileOnly {
extendsFrom annotationProcessor
@ -61,23 +67,23 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'com.github.gavlyukovskiy:datasource-proxy-spring-boot-starter:1.10.0'
implementation 'org.springdoc:springdoc-openapi:2.6.0'
implementation 'org.postgresql:postgresql:42.7.4'
implementation 'org.liquibase:liquibase-core:4.30.0'
implementation 'org.springdoc:springdoc-openapi:2.8.3'
implementation 'org.postgresql:postgresql'
implementation 'org.liquibase:liquibase-core'
implementation 'io.hypersistence:hypersistence-utils-hibernate-63:3.9.0'
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.18.2'
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310'
implementation 'org.openapitools:jackson-databind-nullable:0.2.6'
implementation 'org.apache.commons:commons-text:1.13.0'
implementation 'net.java.dev.jna:jna:5.16.0'
implementation 'org.modelmapper:modelmapper:3.2.2'
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.3'
implementation 'org.reflections:reflections:0.10.2'
compileOnly 'org.projectlombok:lombok'
testCompileOnly 'org.projectlombok:lombok'
// FIXME: developmentOnly 'org.springframework.boot:spring-boot-devtools'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
annotationProcessor 'org.projectlombok:lombok'
testAnnotationProcessor 'org.projectlombok:lombok'
@ -89,7 +95,7 @@ dependencies {
testImplementation 'org.testcontainers:postgresql'
testImplementation 'com.tngtech.archunit:archunit-junit5:1.3.0'
testImplementation 'io.rest-assured:spring-mock-mvc'
testImplementation 'org.hamcrest:hamcrest-core:3.0'
testImplementation 'org.hamcrest:hamcrest-core'
testImplementation 'org.pitest:pitest-junit5-plugin:1.2.1'
testImplementation 'org.junit.jupiter:junit-jupiter-api'
testImplementation 'org.wiremock:wiremock-standalone:3.10.0'

View File

@ -5,9 +5,23 @@
{ "moduleLicense": "Apache-2.0" },
{ "moduleLicense": "Apache License 2.0" },
{ "moduleLicense": "Apache License v2.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": 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-2-Clause" },
{ "moduleLicense": "BSD-3-Clause" },
@ -46,14 +60,8 @@
{
"moduleLicense": "Public Domain, per Creative Commons CC0",
"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()),
Optional.ofNullable(response.getBody()).map(Object::toString).orElse(firstMessageLine(exc)));
}
@Override
@SuppressWarnings("unchecked,rawtypes")
protected ResponseEntity handleHttpMessageNotReadable(
@ -131,7 +132,7 @@ public class RestResponseEntityExceptionHandler
final HttpStatusCode status,
final WebRequest request) {
final var errorList = exc
.getAllValidationResults()
.getParameterValidationResults()
.stream()
.map(ParameterValidationResult::getResolvableErrors)
.flatMap(Collection::stream)

View File

@ -5,6 +5,7 @@ import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import net.hostsharing.hsadminng.errors.DisplayAs;
import net.hostsharing.hsadminng.rbac.role.WithRoleId;
import net.hostsharing.hsadminng.repr.Stringify;
import net.hostsharing.hsadminng.repr.Stringifyable;
@ -24,7 +25,7 @@ import static net.hostsharing.hsadminng.repr.Stringify.stringify;
@NoArgsConstructor
@AllArgsConstructor
@DisplayAs("BookingDebitor")
public class HsBookingDebitorEntity implements Stringifyable {
public class HsBookingDebitorEntity implements Stringifyable, WithRoleId {
public static final String DEBITOR_NUMBER_TAG = "D-";

View File

@ -7,7 +7,7 @@ import net.hostsharing.hsadminng.hs.booking.generated.api.v1.api.HsBookingProjec
import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsBookingProjectInsertResource;
import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsBookingProjectPatchResource;
import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsBookingProjectResource;
import net.hostsharing.hsadminng.mapper.StandardMapper;
import net.hostsharing.hsadminng.mapper.StrictMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.transaction.annotation.Transactional;
@ -26,7 +26,7 @@ public class HsBookingProjectController implements HsBookingProjectsApi {
private Context context;
@Autowired
private StandardMapper mapper;
private StrictMapper mapper;
@Autowired
private HsBookingProjectRbacRepository bookingProjectRepo;

View File

@ -89,7 +89,7 @@ public abstract class HsHostingAsset implements Stringifyable, BaseEntity<HsHost
@JoinColumn(name = "alarmcontactuuid")
private HsOfficeContactRealEntity alarmContact;
@OneToMany(cascade = CascadeType.REFRESH, orphanRemoval = true, fetch = FetchType.LAZY)
@OneToMany(cascade = { CascadeType.PERSIST, CascadeType.REFRESH }, orphanRemoval = true, fetch = FetchType.LAZY)
@JoinColumn(name = "parentassetuuid", referencedColumnName = "uuid")
private List<HsHostingAssetRealEntity> subHostingAssets;

View File

@ -12,7 +12,7 @@ import net.hostsharing.hsadminng.hs.hosting.generated.api.v1.model.HsHostingAsse
import net.hostsharing.hsadminng.hs.hosting.generated.api.v1.model.HsHostingAssetResource;
import net.hostsharing.hsadminng.hs.hosting.generated.api.v1.model.HsHostingAssetTypeResource;
import net.hostsharing.hsadminng.mapper.KeyValueMap;
import net.hostsharing.hsadminng.mapper.StandardMapper;
import net.hostsharing.hsadminng.mapper.StrictMapper;
import net.hostsharing.hsadminng.persistence.EntityManagerWrapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
@ -36,7 +36,7 @@ public class HsHostingAssetController implements HsHostingAssetsApi {
private Context context;
@Autowired
private StandardMapper mapper;
private StrictMapper mapper;
@Autowired
private HsHostingAssetRbacRepository rbacAssetRepo;

View File

@ -9,7 +9,7 @@ import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetRealEntity;
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType;
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRealEntity;
import net.hostsharing.hsadminng.lambda.Reducer;
import net.hostsharing.hsadminng.mapper.StandardMapper;
import net.hostsharing.hsadminng.mapper.StrictMapper;
import net.hostsharing.hsadminng.mapper.ToStringConverter;
import net.hostsharing.hsadminng.persistence.EntityManagerWrapper;
@ -31,8 +31,8 @@ public class DomainSetupHostingAssetFactory extends HostingAssetFactory {
final EntityManagerWrapper emw,
final HsBookingItemRealEntity newBookingItemRealEntity,
final HsHostingAssetAutoInsertResource asset,
final StandardMapper standardMapper) {
super(emw, newBookingItemRealEntity, asset, standardMapper);
final StrictMapper StrictMapper) {
super(emw, newBookingItemRealEntity, asset, StrictMapper);
}
@Override

View File

@ -6,7 +6,7 @@ import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsHostingAsse
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemRealEntity;
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAsset;
import net.hostsharing.hsadminng.hs.hosting.asset.validators.HostingAssetEntitySaveProcessor;
import net.hostsharing.hsadminng.mapper.StandardMapper;
import net.hostsharing.hsadminng.mapper.StrictMapper;
import net.hostsharing.hsadminng.persistence.EntityManagerWrapper;
@ -16,7 +16,7 @@ abstract class HostingAssetFactory {
final EntityManagerWrapper emw;
final HsBookingItemRealEntity fromBookingItem;
final HsHostingAssetAutoInsertResource asset;
final StandardMapper standardMapper;
final StrictMapper StrictMapper;
protected abstract HsHostingAsset create();

View File

@ -9,7 +9,7 @@ import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsHostingAsse
import net.hostsharing.hsadminng.hs.booking.item.BookingItemCreatedAppEvent;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemRealEntity;
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAsset;
import net.hostsharing.hsadminng.mapper.StandardMapper;
import net.hostsharing.hsadminng.mapper.StrictMapper;
import net.hostsharing.hsadminng.persistence.EntityManagerWrapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
@ -25,7 +25,7 @@ public class HsBookingItemCreatedListener implements ApplicationListener<Booking
private ObjectMapper jsonMapper;
@Autowired
private StandardMapper standardMapper;
private StrictMapper StrictMapper;
@Override
@SneakyThrows
@ -44,9 +44,9 @@ public class HsBookingItemCreatedListener implements ApplicationListener<Booking
final var asset = jsonMapper.readValue(event.getEntity().getAssetJson(), HsHostingAssetAutoInsertResource.class);
final var factory = switch (newBookingItemRealEntity.getType()) {
case PRIVATE_CLOUD, CLOUD_SERVER, MANAGED_SERVER ->
forNowNoAutomaticHostingAssetCreationPossible(emw, newBookingItemRealEntity, asset, standardMapper);
case MANAGED_WEBSPACE -> new ManagedWebspaceHostingAssetFactory(emw, newBookingItemRealEntity, asset, standardMapper);
case DOMAIN_SETUP -> new DomainSetupHostingAssetFactory(emw, newBookingItemRealEntity, asset, standardMapper);
forNowNoAutomaticHostingAssetCreationPossible(emw, newBookingItemRealEntity, asset, StrictMapper);
case MANAGED_WEBSPACE -> new ManagedWebspaceHostingAssetFactory(emw, newBookingItemRealEntity, asset, StrictMapper);
case DOMAIN_SETUP -> new DomainSetupHostingAssetFactory(emw, newBookingItemRealEntity, asset, StrictMapper);
};
if (factory != null) {
final var statusMessage = factory.createAndPersist();
@ -62,9 +62,9 @@ public class HsBookingItemCreatedListener implements ApplicationListener<Booking
final EntityManagerWrapper emw,
final HsBookingItemRealEntity fromBookingItem,
final HsHostingAssetAutoInsertResource asset,
final StandardMapper standardMapper
final StrictMapper StrictMapper
) {
return new HostingAssetFactory(emw, fromBookingItem, asset, standardMapper) {
return new HostingAssetFactory(emw, fromBookingItem, asset, StrictMapper) {
@Override
protected HsHostingAsset create() {

View File

@ -5,7 +5,7 @@ import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsHostingAsse
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemRealEntity;
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAsset;
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetRealEntity;
import net.hostsharing.hsadminng.mapper.StandardMapper;
import net.hostsharing.hsadminng.mapper.StrictMapper;
import net.hostsharing.hsadminng.persistence.EntityManagerWrapper;
import jakarta.validation.ValidationException;
@ -19,8 +19,8 @@ public class ManagedWebspaceHostingAssetFactory extends HostingAssetFactory {
final EntityManagerWrapper emw,
final HsBookingItemRealEntity newBookingItemRealEntity,
final HsHostingAssetAutoInsertResource asset,
final StandardMapper standardMapper) {
super(emw, newBookingItemRealEntity, asset, standardMapper);
final StrictMapper StrictMapper) {
super(emw, newBookingItemRealEntity, asset, StrictMapper);
}
@Override
@ -32,7 +32,7 @@ public class ManagedWebspaceHostingAssetFactory extends HostingAssetFactory {
.map(Enum::name)
.orElse(null));
}
final var managedWebspaceHostingAsset = standardMapper.map(asset, HsHostingAssetRealEntity.class);
final var managedWebspaceHostingAsset = StrictMapper.map(asset, HsHostingAssetRealEntity.class);
managedWebspaceHostingAsset.setBookingItem(fromBookingItem);
emw.createQuery(
"SELECT asset FROM HsHostingAssetRealEntity asset WHERE asset.bookingItem.uuid=:bookingItemUuid",

View File

@ -5,7 +5,7 @@ import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficeBankAccountsApi;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeBankAccountInsertResource;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeBankAccountResource;
import net.hostsharing.hsadminng.mapper.StandardMapper;
import net.hostsharing.hsadminng.mapper.StrictMapper;
import org.iban4j.BicUtil;
import org.iban4j.IbanUtil;
import org.springframework.beans.factory.annotation.Autowired;
@ -25,7 +25,7 @@ public class HsOfficeBankAccountController implements HsOfficeBankAccountsApi {
private Context context;
@Autowired
private StandardMapper mapper;
private StrictMapper mapper;
@Autowired
private HsOfficeBankAccountRepository bankAccountRepo;

View File

@ -12,6 +12,7 @@ import lombok.experimental.SuperBuilder;
import net.hostsharing.hsadminng.errors.DisplayAs;
import net.hostsharing.hsadminng.mapper.PatchableMapWrapper;
import net.hostsharing.hsadminng.persistence.BaseEntity;
import net.hostsharing.hsadminng.rbac.role.WithRoleId;
import net.hostsharing.hsadminng.repr.Stringify;
import net.hostsharing.hsadminng.repr.Stringifyable;
import org.hibernate.annotations.GenericGenerator;
@ -37,7 +38,7 @@ import static net.hostsharing.hsadminng.repr.Stringify.stringify;
@SuperBuilder(toBuilder = true)
@FieldNameConstants
@DisplayAs("Contact")
public class HsOfficeContact implements Stringifyable, BaseEntity<HsOfficeContact> {
public class HsOfficeContact implements Stringifyable, BaseEntity<HsOfficeContact>, WithRoleId {
private static Stringify<HsOfficeContact> toString = stringify(HsOfficeContact.class, "contact")
.withProp(Fields.caption, HsOfficeContact::getCaption)

View File

@ -1,7 +1,7 @@
package net.hostsharing.hsadminng.hs.office.contact;
import io.micrometer.core.annotation.Timed;
import net.hostsharing.hsadminng.mapper.StandardMapper;
import net.hostsharing.hsadminng.mapper.StrictMapper;
import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficeContactsApi;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeContactInsertResource;
@ -28,7 +28,7 @@ public class HsOfficeContactController implements HsOfficeContactsApi {
private Context context;
@Autowired
private StandardMapper mapper;
private StrictMapper mapper;
@Autowired
private HsOfficeContactRbacRepository contactRepo;

View File

@ -1,4 +1,3 @@
package net.hostsharing.hsadminng.hs.office.coopassets;
import lombok.AllArgsConstructor;
@ -10,11 +9,22 @@ import net.hostsharing.hsadminng.errors.DisplayAs;
import net.hostsharing.hsadminng.hs.office.membership.HsOfficeMembershipEntity;
import net.hostsharing.hsadminng.persistence.BaseEntity;
import net.hostsharing.hsadminng.rbac.generator.RbacSpec;
import net.hostsharing.hsadminng.rbac.generator.RbacSpec.SQL;
import net.hostsharing.hsadminng.repr.Stringify;
import net.hostsharing.hsadminng.repr.Stringifyable;
import org.hibernate.annotations.GenericGenerator;
import jakarta.persistence.*;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToOne;
import jakarta.persistence.Table;
import jakarta.persistence.Version;
import java.io.IOException;
import java.math.BigDecimal;
import java.time.LocalDate;
@ -57,8 +67,7 @@ public class HsOfficeCoopAssetsTransactionEntity implements Stringifyable, BaseE
.quotedValues(false);
@Id
@GeneratedValue(generator = "UUID")
@GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator")
@GeneratedValue
private UUID uuid;
@Version
@ -122,15 +131,15 @@ public class HsOfficeCoopAssetsTransactionEntity implements Stringifyable, BaseE
return this;
}
public String getTaggedMemberNumber() {
return ofNullable(membership).map(HsOfficeMembershipEntity::toShortString).orElse("M-???????");
}
@Override
public String toString() {
return stringify.apply(this);
}
public String getTaggedMemberNumber() {
return ofNullable(membership).map(HsOfficeMembershipEntity::toShortString).orElse("M-???????");
}
@Override
public String toShortString() {
return "%s:%.3s:%+1.2f".formatted(
@ -141,7 +150,7 @@ public class HsOfficeCoopAssetsTransactionEntity implements Stringifyable, BaseE
public static RbacSpec rbac() {
return rbacViewFor("coopAssetsTransaction", HsOfficeCoopAssetsTransactionEntity.class)
.withIdentityView(RbacSpec.SQL.projection("reference"))
.withIdentityView(SQL.projection("reference"))
.withUpdatableColumns("comment")
.importEntityAlias("membership", HsOfficeMembershipEntity.class, usingDefaultCase(),
dependsOnColumn("membershipUuid"),

View File

@ -1,14 +1,13 @@
package net.hostsharing.hsadminng.hs.office.coopshares;
import jakarta.persistence.EntityNotFoundException;
import io.micrometer.core.annotation.Timed;
import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.errors.MultiValidationException;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficeCoopSharesApi;
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.errors.MultiValidationException;
import net.hostsharing.hsadminng.mapper.StandardMapper;
import net.hostsharing.hsadminng.hs.office.membership.HsOfficeMembershipRepository;
import net.hostsharing.hsadminng.mapper.StrictMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.format.annotation.DateTimeFormat.ISO;
@ -25,6 +24,7 @@ import java.util.function.BiConsumer;
import static net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeCoopSharesTransactionTypeResource.CANCELLATION;
import static net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeCoopSharesTransactionTypeResource.SUBSCRIPTION;
import static net.hostsharing.hsadminng.hs.validation.UuidResolver.resolve;
@RestController
public class HsOfficeCoopSharesTransactionController implements HsOfficeCoopSharesApi {
@ -33,14 +33,16 @@ public class HsOfficeCoopSharesTransactionController implements HsOfficeCoopShar
private Context context;
@Autowired
private StandardMapper mapper;
private StrictMapper mapper;
@Autowired
private HsOfficeCoopSharesTransactionRepository coopSharesTransactionRepo;
@Autowired
private HsOfficeMembershipRepository membershipRepo;
@Override
@Transactional(readOnly = true)
@Timed("app.office.coopShares.api.getListOfCoopShares")
public ResponseEntity<List<HsOfficeCoopSharesTransactionResource>> getListOfCoopShares(
final String currentSubject,
@ -55,7 +57,10 @@ public class HsOfficeCoopSharesTransactionController implements HsOfficeCoopShar
fromValueDate,
toValueDate);
final var resources = mapper.mapList(entities, HsOfficeCoopSharesTransactionResource.class);
final var resources = mapper.mapList(
entities,
HsOfficeCoopSharesTransactionResource.class,
ENTITY_TO_RESOURCE_POSTMAPPER);
return ResponseEntity.ok(resources);
}
@ -70,7 +75,10 @@ public class HsOfficeCoopSharesTransactionController implements HsOfficeCoopShar
context.define(currentSubject, assumedRoles);
validate(requestBody);
final var entityToSave = mapper.map(requestBody, HsOfficeCoopSharesTransactionEntity.class, RESOURCE_TO_ENTITY_POSTMAPPER);
final var entityToSave = mapper.map(
requestBody,
HsOfficeCoopSharesTransactionEntity.class,
RESOURCE_TO_ENTITY_POSTMAPPER);
final var saved = coopSharesTransactionRepo.save(entityToSave);
@ -79,7 +87,7 @@ public class HsOfficeCoopSharesTransactionController implements HsOfficeCoopShar
.path("/api/hs/office/coopsharestransactions/{id}")
.buildAndExpand(saved.getUuid())
.toUri();
final var mapped = mapper.map(saved, HsOfficeCoopSharesTransactionResource.class);
final var mapped = mapper.map(saved, HsOfficeCoopSharesTransactionResource.class, ENTITY_TO_RESOURCE_POSTMAPPER);
return ResponseEntity.created(uri).body(mapped);
}
@ -87,15 +95,18 @@ public class HsOfficeCoopSharesTransactionController implements HsOfficeCoopShar
@Transactional(readOnly = true)
@Timed("app.office.coopShares.repo.getSingleCoopShareTransactionByUuid")
public ResponseEntity<HsOfficeCoopSharesTransactionResource> getSingleCoopShareTransactionByUuid(
final String currentSubject, final String assumedRoles, final UUID shareTransactionUuid) {
final String currentSubject, final String assumedRoles, final UUID shareTransactionUuid) {
context.define(currentSubject, assumedRoles);
context.define(currentSubject, assumedRoles);
final var result = coopSharesTransactionRepo.findByUuid(shareTransactionUuid);
if (result.isEmpty()) {
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok(mapper.map(result.get(), HsOfficeCoopSharesTransactionResource.class));
final var result = coopSharesTransactionRepo.findByUuid(shareTransactionUuid);
if (result.isEmpty()) {
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok(mapper.map(
result.get(),
HsOfficeCoopSharesTransactionResource.class,
ENTITY_TO_RESOURCE_POSTMAPPER));
}
@ -137,9 +148,16 @@ public class HsOfficeCoopSharesTransactionController implements HsOfficeCoopShar
}
final BiConsumer<HsOfficeCoopSharesTransactionInsertResource, HsOfficeCoopSharesTransactionEntity> RESOURCE_TO_ENTITY_POSTMAPPER = (resource, entity) -> {
if ( resource.getRevertedShareTxUuid() != null ) {
entity.setRevertedShareTx(coopSharesTransactionRepo.findByUuid(resource.getRevertedShareTxUuid())
.orElseThrow(() -> new EntityNotFoundException("ERROR: [400] revertedShareTxUuid %s not found".formatted(resource.getRevertedShareTxUuid()))));
entity.setMembership(resolve("membership.uuid", resource.getMembershipUuid(), membershipRepo::findByUuid));
if (resource.getRevertedShareTxUuid() != null) {
entity.setRevertedShareTx(resolve(
"revertedShareTx.uuid",
resource.getRevertedShareTxUuid(),
coopSharesTransactionRepo::findByUuid));
}
};
final BiConsumer<HsOfficeCoopSharesTransactionEntity, HsOfficeCoopSharesTransactionResource> ENTITY_TO_RESOURCE_POSTMAPPER = (entity, resource) -> {
resource.setMembershipUuid(entity.getMembership().getUuid());
};
}

View File

@ -7,13 +7,23 @@ import lombok.NoArgsConstructor;
import lombok.Setter;
import net.hostsharing.hsadminng.errors.DisplayAs;
import net.hostsharing.hsadminng.hs.office.membership.HsOfficeMembershipEntity;
import net.hostsharing.hsadminng.rbac.generator.RbacSpec;
import net.hostsharing.hsadminng.persistence.BaseEntity;
import net.hostsharing.hsadminng.rbac.generator.RbacSpec;
import net.hostsharing.hsadminng.rbac.generator.RbacSpec.SQL;
import net.hostsharing.hsadminng.repr.Stringify;
import net.hostsharing.hsadminng.repr.Stringifyable;
import jakarta.persistence.*;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToOne;
import jakarta.persistence.Table;
import jakarta.persistence.Version;
import java.io.IOException;
import java.time.LocalDate;
import java.util.UUID;
@ -132,6 +142,7 @@ public class HsOfficeCoopSharesTransactionEntity implements Stringifyable, BaseE
directlyFetchedByDependsOnColumn(),
NOT_NULL)
// the membership:ADMIN is not to be confused with the member itself, it's an account manager of the coop
.toRole("membership", ADMIN).grantPermission(INSERT)
.toRole("membership", ADMIN).grantPermission(UPDATE)
.toRole("membership", AGENT).grantPermission(SELECT);

View File

@ -2,14 +2,16 @@ package net.hostsharing.hsadminng.hs.office.debitor;
import io.micrometer.core.annotation.Timed;
import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountRepository;
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRealRepository;
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.HsOfficeDebitorPatchResource;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeDebitorResource;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRealRepository;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealEntity;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealRepository;
import net.hostsharing.hsadminng.mapper.StandardMapper;
import net.hostsharing.hsadminng.persistence.EntityExistsValidator;
import net.hostsharing.hsadminng.mapper.StrictMapper;
import org.apache.commons.lang3.Validate;
import org.hibernate.Hibernate;
import org.springframework.beans.factory.annotation.Autowired;
@ -26,6 +28,7 @@ import java.util.UUID;
import java.util.function.BiConsumer;
import static net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationType.DEBITOR;
import static net.hostsharing.hsadminng.hs.validation.UuidResolver.resolve;
import static net.hostsharing.hsadminng.repr.TaggedNumber.cropTag;
@RestController
@ -36,16 +39,22 @@ public class HsOfficeDebitorController implements HsOfficeDebitorsApi {
private Context context;
@Autowired
private StandardMapper mapper;
private StrictMapper mapper;
@Autowired
private HsOfficeDebitorRepository debitorRepo;
@Autowired
private HsOfficeRelationRealRepository relrealRepo;
private HsOfficeRelationRealRepository realRelRepo;
@Autowired
private EntityExistsValidator entityValidator;
private HsOfficePersonRealRepository realPersonRepo;
@Autowired
private HsOfficeContactRealRepository realContactRepo;
@Autowired
private HsOfficeBankAccountRepository bankAccountRepo;
@PersistenceContext
private EntityManager em;
@ -63,9 +72,9 @@ public class HsOfficeDebitorController implements HsOfficeDebitorsApi {
final var entities = partnerNumber != null
? debitorRepo.findDebitorsByPartnerNumber(cropTag("P-", partnerNumber))
: partnerUuid != null
? debitorRepo.findDebitorsByPartnerUuid(partnerUuid)
: debitorRepo.findDebitorsByOptionalNameLike(name);
: partnerUuid != null
? debitorRepo.findDebitorsByPartnerUuid(partnerUuid)
: debitorRepo.findDebitorsByOptionalNameLike(name);
final var resources = mapper.mapList(entities, HsOfficeDebitorResource.class, ENTITY_TO_RESOURCE_POSTMAPPER);
return ResponseEntity.ok(resources);
@ -81,34 +90,19 @@ public class HsOfficeDebitorController implements HsOfficeDebitorsApi {
context.define(currentSubject, assumedRoles);
Validate.isTrue(body.getDebitorRel() == null || body.getDebitorRelUuid() == null,
Validate.isTrue(
body.getDebitorRel() == null || body.getDebitorRelUuid() == null,
"ERROR: [400] exactly one of debitorRel and debitorRelUuid must be supplied, but found both");
Validate.isTrue(body.getDebitorRel() != null || body.getDebitorRelUuid() != null,
Validate.isTrue(
body.getDebitorRel() != null || body.getDebitorRelUuid() != null,
"ERROR: [400] exactly one of debitorRel and debitorRelUuid must be supplied, but found none");
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");
final var entityToSave = mapper.map(body, HsOfficeDebitorEntity.class);
if (body.getDebitorRel() != null) {
final var debitorRel = mapper.map("debitorRel.", body.getDebitorRel(), HsOfficeRelationRealEntity.class);
debitorRel.setType(DEBITOR);
entityValidator.validateEntityExists("debitorRel.anchorUuid", debitorRel.getAnchor());
entityValidator.validateEntityExists("debitorRel.holderUuid", debitorRel.getHolder());
entityValidator.validateEntityExists("debitorRel.contactUuid", debitorRel.getContact());
entityToSave.setDebitorRel(relrealRepo.save(debitorRel));
} else {
final var debitorRelOptional = relrealRepo.findByUuid(body.getDebitorRelUuid());
debitorRelOptional.ifPresentOrElse(
debitorRel -> {entityToSave.setDebitorRel(relrealRepo.save(debitorRel));},
() -> {
throw new ValidationException(
"Unable to find RealRelation by debitorRelUuid: " + body.getDebitorRelUuid());
});
}
final var entityToSave = mapper.map(body, HsOfficeDebitorEntity.class, RESOURCE_TO_ENTITY_POSTMAPPER);
final var savedEntity = debitorRepo.save(entityToSave);
em.flush();
em.refresh(savedEntity);
final var savedEntity = debitorRepo.save(entityToSave).reload(em);
final var uri =
MvcUriComponentsBuilder.fromController(getClass())
@ -181,7 +175,7 @@ public class HsOfficeDebitorController implements HsOfficeDebitorsApi {
context.define(currentSubject, assumedRoles);
final var current = debitorRepo.findByUuid(debitorUuid).orElseThrow();
final var current = debitorRepo.findByUuid(debitorUuid).orElseThrow().reload(em);
new HsOfficeDebitorEntityPatcher(em, current).apply(body);
@ -191,7 +185,39 @@ public class HsOfficeDebitorController implements HsOfficeDebitorsApi {
return ResponseEntity.ok(mapped);
}
final BiConsumer<HsOfficeDebitorInsertResource, HsOfficeDebitorEntity> RESOURCE_TO_ENTITY_POSTMAPPER = (resource, entity) -> {
if (resource.getDebitorRel() != null) {
final var debitorRel = realRelRepo.save(HsOfficeRelationRealEntity.builder()
.type(DEBITOR)
.anchor(resolve(
"debitorRel.anchor.uuid", resource.getDebitorRel().getAnchorUuid(), realPersonRepo::findByUuid))
.holder(resolve(
"debitorRel.holder.uuid", resource.getDebitorRel().getHolderUuid(), realPersonRepo::findByUuid))
.contact(resolve(
"debitorRel.contact.uuid", resource.getDebitorRel().getContactUuid(), realContactRepo::findByUuid))
.build());
entity.setDebitorRel(debitorRel);
} else {
final var debitorRelOptional = realRelRepo.findByUuid(resource.getDebitorRelUuid());
debitorRelOptional.ifPresentOrElse(
debitorRel -> {
entity.setDebitorRel(realRelRepo.save(debitorRel));
},
() -> {
throw new ValidationException(
"Unable to find debitorRel.uuid: " + resource.getDebitorRelUuid());
});
}
if (resource.getRefundBankAccountUuid() != null) {
entity.setRefundBankAccount(resolve(
"refundBankAccount.uuid", resource.getRefundBankAccountUuid(), bankAccountRepo::findByUuid));
}
};
final BiConsumer<HsOfficeDebitorEntity, HsOfficeDebitorResource> ENTITY_TO_RESOURCE_POSTMAPPER = (entity, resource) -> {
resource.setDebitorNumber(entity.getTaggedDebitorNumber());
resource.getPartner().setPartnerNumber(entity.getPartner().getTaggedPartnerNumber());
};
}

View File

@ -7,7 +7,8 @@ import lombok.NoArgsConstructor;
import lombok.Setter;
import net.hostsharing.hsadminng.errors.DisplayAs;
import net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountEntity;
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity;
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartner;
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerRealEntity;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelation;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealEntity;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRbacEntity;
@ -16,7 +17,6 @@ import net.hostsharing.hsadminng.rbac.generator.RbacSpec;
import net.hostsharing.hsadminng.rbac.generator.RbacSpec.SQL;
import net.hostsharing.hsadminng.repr.Stringify;
import net.hostsharing.hsadminng.repr.Stringifyable;
import org.hibernate.annotations.GenericGenerator;
import org.hibernate.annotations.JoinFormula;
import org.hibernate.annotations.NotFound;
import org.hibernate.annotations.NotFoundAction;
@ -75,7 +75,6 @@ public class HsOfficeDebitorEntity implements BaseEntity<HsOfficeDebitorEntity>,
@Id
@GeneratedValue
@GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator")
private UUID uuid;
@Version
@ -87,16 +86,16 @@ public class HsOfficeDebitorEntity implements BaseEntity<HsOfficeDebitorEntity>,
value = """
(
SELECT DISTINCT partner.uuid
FROM hs_office.partner_rv partner
FROM hs_office.partner partner
JOIN hs_office.relation dRel
ON dRel.uuid = debitorreluuid AND dRel.type = 'DEBITOR'
ON dRel.uuid = debitorRelUuid AND dRel.type = 'DEBITOR'
JOIN hs_office.relation pRel
ON pRel.uuid = partner.partnerRelUuid AND pRel.type = 'PARTNER'
WHERE pRel.holderUuid = dRel.anchorUuid
)
""")
@NotFound(action = NotFoundAction.IGNORE) // TODO.impl: map a simplified raw-PartnerEntity, just for the partner-number
private HsOfficePartnerEntity partner;
@NotFound(action = NotFoundAction.EXCEPTION) // TODO.impl: map a simplified raw-PartnerEntity, just for the partner-number
private HsOfficePartnerRealEntity partner;
@Column(name = "debitornumbersuffix", length = 2)
@Pattern(regexp = TWO_DECIMAL_DIGITS)
@ -132,9 +131,7 @@ public class HsOfficeDebitorEntity implements BaseEntity<HsOfficeDebitorEntity>,
@Override
public HsOfficeDebitorEntity load() {
BaseEntity.super.load();
if (partner != null) {
partner.load();
}
partner.load();
debitorRel.load();
if (refundBankAccount != null) {
refundBankAccount.load();
@ -145,7 +142,7 @@ public class HsOfficeDebitorEntity implements BaseEntity<HsOfficeDebitorEntity>,
public String getTaggedDebitorNumber() {
return ofNullable(partner)
.filter(partner -> debitorNumberSuffix != null)
.map(HsOfficePartnerEntity::getPartnerNumber)
.map(HsOfficePartner::getPartnerNumber)
.map(partnerNumber -> DEBITOR_NUMBER_TAG + partnerNumber + debitorNumberSuffix)
.orElse(null);
}

View File

@ -19,7 +19,7 @@ public interface HsOfficeDebitorRepository extends Repository<HsOfficeDebitorEnt
@Query("""
SELECT debitor FROM HsOfficeDebitorEntity debitor
JOIN HsOfficePartnerEntity partner
JOIN HsOfficePartnerRealEntity partner
ON partner.partnerRel.holder = debitor.debitorRel.anchor
AND partner.partnerRel.type = 'PARTNER' AND debitor.debitorRel.type = 'DEBITOR'
WHERE partner.partnerNumber = :partnerNumber
@ -42,7 +42,7 @@ public interface HsOfficeDebitorRepository extends Repository<HsOfficeDebitorEnt
@Query("""
SELECT debitor FROM HsOfficeDebitorEntity debitor
JOIN HsOfficePartnerEntity partner
JOIN HsOfficePartnerRealEntity partner
ON partner.partnerRel.holder = debitor.debitorRel.anchor
AND partner.partnerRel.type = 'PARTNER' AND debitor.debitorRel.type = 'DEBITOR'
JOIN HsOfficePersonRealEntity person

View File

@ -6,14 +6,16 @@ import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficeMembersh
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeMembershipInsertResource;
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.partner.HsOfficePartnerEntity;
import net.hostsharing.hsadminng.mapper.StandardMapper;
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerRbacEntity;
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerRealRepository;
import net.hostsharing.hsadminng.mapper.StrictMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder;
import jakarta.persistence.EntityNotFoundException;
import java.util.List;
import java.util.UUID;
import java.util.function.BiConsumer;
@ -28,7 +30,10 @@ public class HsOfficeMembershipController implements HsOfficeMembershipsApi {
private Context context;
@Autowired
private StandardMapper mapper;
private StrictMapper mapper;
@Autowired
private HsOfficePartnerRealRepository partnerRepo;
@Autowired
private HsOfficeMembershipRepository membershipRepo;
@ -47,7 +52,7 @@ public class HsOfficeMembershipController implements HsOfficeMembershipsApi {
final var entities = partnerNumber != null
? membershipRepo.findMembershipsByPartnerNumber(
cropTag(HsOfficePartnerEntity.PARTNER_NUMBER_TAG, partnerNumber))
cropTag(HsOfficePartnerRbacEntity.PARTNER_NUMBER_TAG, partnerNumber))
: partnerUuid != null
? membershipRepo.findMembershipsByPartnerUuid(partnerUuid)
: membershipRepo.findAll();
@ -68,7 +73,7 @@ public class HsOfficeMembershipController implements HsOfficeMembershipsApi {
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);
@ -164,5 +169,12 @@ public class HsOfficeMembershipController implements HsOfficeMembershipsApi {
if (entity.getValidity().hasUpperBound()) {
resource.setValidTo(entity.getValidity().upper().minusDays(1));
}
resource.getPartner().setPartnerNumber(entity.getPartner().getTaggedPartnerNumber()); // TODO.refa: 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

@ -8,11 +8,12 @@ import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import net.hostsharing.hsadminng.errors.DisplayAs;
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerRealEntity;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRbacEntity;
import net.hostsharing.hsadminng.persistence.BaseEntity;
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity;
import net.hostsharing.hsadminng.rbac.generator.RbacSpec;
import net.hostsharing.hsadminng.rbac.generator.RbacSpec.SQL;
import net.hostsharing.hsadminng.rbac.role.WithRoleId;
import net.hostsharing.hsadminng.repr.Stringify;
import net.hostsharing.hsadminng.repr.Stringifyable;
import org.hibernate.annotations.Type;
@ -63,7 +64,7 @@ import static net.hostsharing.hsadminng.repr.Stringify.stringify;
@NoArgsConstructor
@AllArgsConstructor
@DisplayAs("Membership")
public class HsOfficeMembershipEntity implements BaseEntity<HsOfficeMembershipEntity>, Stringifyable {
public class HsOfficeMembershipEntity implements BaseEntity<HsOfficeMembershipEntity>, Stringifyable, WithRoleId {
public static final String MEMBER_NUMBER_TAG = "M-";
public static final String TWO_DECIMAL_DIGITS = "^([0-9]{2})$";
@ -84,7 +85,7 @@ public class HsOfficeMembershipEntity implements BaseEntity<HsOfficeMembershipEn
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "partneruuid")
private HsOfficePartnerEntity partner;
private HsOfficePartnerRealEntity partner;
@Column(name = "membernumbersuffix", length = 2)
@Pattern(regexp = TWO_DECIMAL_DIGITS)

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.mapper.EntityPatcher;
import net.hostsharing.hsadminng.mapper.StandardMapper;
import net.hostsharing.hsadminng.mapper.OptionalFromJson;
import net.hostsharing.hsadminng.mapper.StrictMapper;
import java.util.Optional;
public class HsOfficeMembershipEntityPatcher implements EntityPatcher<HsOfficeMembershipPatchResource> {
private final StandardMapper mapper;
private final StrictMapper mapper;
private final HsOfficeMembershipEntity entity;
public HsOfficeMembershipEntityPatcher(
final StandardMapper mapper,
final StrictMapper mapper,
final HsOfficeMembershipEntity entity) {
this.mapper = mapper;
this.entity = entity;

View File

@ -0,0 +1,103 @@
package net.hostsharing.hsadminng.hs.office.partner;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.experimental.SuperBuilder;
import net.hostsharing.hsadminng.errors.DisplayAs;
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContact;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePerson;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelation;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealEntity;
import net.hostsharing.hsadminng.persistence.BaseEntity;
import net.hostsharing.hsadminng.repr.Stringify;
import net.hostsharing.hsadminng.repr.Stringifyable;
import org.hibernate.annotations.NotFound;
import org.hibernate.annotations.NotFoundAction;
import jakarta.persistence.Column;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.MappedSuperclass;
import jakarta.persistence.Version;
import java.util.UUID;
import static jakarta.persistence.CascadeType.DETACH;
import static jakarta.persistence.CascadeType.MERGE;
import static jakarta.persistence.CascadeType.PERSIST;
import static jakarta.persistence.CascadeType.REFRESH;
import static java.util.Optional.ofNullable;
import static net.hostsharing.hsadminng.repr.Stringify.stringify;
@MappedSuperclass
@Getter
@Setter
@SuperBuilder(toBuilder = true)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor(access = AccessLevel.PROTECTED)
@DisplayAs("Partner")
public class HsOfficePartner<T extends HsOfficePartner<?>> implements Stringifyable, BaseEntity<T> {
public static final String PARTNER_NUMBER_TAG = "P-";
protected static Stringify<HsOfficePartner> stringify = stringify(HsOfficePartner.class, "partner")
.withIdProp(HsOfficePartner::toShortString)
.withProp(p -> ofNullable(p.getPartnerRel())
.map(HsOfficeRelation::getHolder)
.map(HsOfficePerson::toShortString)
.orElse(null))
.withProp(p -> ofNullable(p.getPartnerRel())
.map(HsOfficeRelation::getContact)
.map(HsOfficeContact::toShortString)
.orElse(null))
.quotedValues(false);
@Id
@GeneratedValue
private UUID uuid;
@Version
private int version;
@Column(name = "partnernumber", columnDefinition = "numeric(5) not null")
private Integer partnerNumber;
@ManyToOne(cascade = { PERSIST, MERGE, REFRESH, DETACH }, optional = false, fetch = FetchType.LAZY)
@JoinColumn(name = "partnerreluuid", nullable = false)
private HsOfficeRelationRealEntity partnerRel;
@ManyToOne(cascade = { PERSIST, MERGE, REFRESH, DETACH }, optional = true, fetch = FetchType.LAZY)
@JoinColumn(name = "detailsuuid")
@NotFound(action = NotFoundAction.IGNORE)
private HsOfficePartnerDetailsEntity details;
@Override
public T load() {
BaseEntity.super.load();
partnerRel.load();
if (details != null) {
details.load();
}
//noinspection unchecked
return (T) this;
}
public String getTaggedPartnerNumber() {
return PARTNER_NUMBER_TAG + partnerNumber;
}
@Override
public String toString() {
return stringify.apply(this);
}
@Override
public String toShortString() {
return getTaggedPartnerNumber();
}
}

View File

@ -13,7 +13,7 @@ import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRealEntity;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealEntity;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealRepository;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationType;
import net.hostsharing.hsadminng.mapper.StandardMapper;
import net.hostsharing.hsadminng.mapper.StrictMapper;
import net.hostsharing.hsadminng.persistence.BaseEntity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
@ -26,6 +26,7 @@ import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import java.util.List;
import java.util.UUID;
import java.util.function.BiConsumer;
import static net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationType.EX_PARTNER;
import static net.hostsharing.hsadminng.repr.TaggedNumber.cropTag;
@ -38,10 +39,10 @@ public class HsOfficePartnerController implements HsOfficePartnersApi {
private Context context;
@Autowired
private StandardMapper mapper;
private StrictMapper mapper;
@Autowired
private HsOfficePartnerRepository partnerRepo;
private HsOfficePartnerRbacRepository partnerRepo;
@Autowired
private HsOfficeRelationRealRepository relationRepo;
@ -60,7 +61,7 @@ public class HsOfficePartnerController implements HsOfficePartnersApi {
final var entities = partnerRepo.findPartnerByOptionalNameLike(name);
final var resources = mapper.mapList(entities, HsOfficePartnerResource.class);
final var resources = mapper.mapList(entities, HsOfficePartnerResource.class, ENTITY_TO_RESOURCE_POSTMAPPER);
return ResponseEntity.ok(resources);
}
@ -83,7 +84,7 @@ public class HsOfficePartnerController implements HsOfficePartnersApi {
.path("/api/hs/office/partners/{id}")
.buildAndExpand(saved.getUuid())
.toUri();
final var mapped = mapper.map(saved, HsOfficePartnerResource.class);
final var mapped = mapper.map(saved, HsOfficePartnerResource.class, ENTITY_TO_RESOURCE_POSTMAPPER);
return ResponseEntity.created(uri).body(mapped);
}
@ -101,7 +102,8 @@ public class HsOfficePartnerController implements HsOfficePartnersApi {
if (result.isEmpty()) {
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok(mapper.map(result.get(), HsOfficePartnerResource.class));
final var mapped = mapper.map(result.get(), HsOfficePartnerResource.class, ENTITY_TO_RESOURCE_POSTMAPPER);
return ResponseEntity.ok(mapped);
}
@Override
@ -118,7 +120,8 @@ public class HsOfficePartnerController implements HsOfficePartnersApi {
if (result.isEmpty()) {
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok(mapper.map(result.get(), HsOfficePartnerResource.class));
final var mapped = mapper.map(result.get(), HsOfficePartnerResource.class, ENTITY_TO_RESOURCE_POSTMAPPER);
return ResponseEntity.ok(mapped);
}
@Override
@ -161,20 +164,20 @@ public class HsOfficePartnerController implements HsOfficePartnersApi {
final var saved = partnerRepo.save(current);
optionallyCreateExPartnerRelation(saved, previousPartnerRel);
final var mapped = mapper.map(saved, HsOfficePartnerResource.class);
final var mapped = mapper.map(saved, HsOfficePartnerResource.class, ENTITY_TO_RESOURCE_POSTMAPPER);
return ResponseEntity.ok(mapped);
}
private void optionallyCreateExPartnerRelation(final HsOfficePartnerEntity saved, final HsOfficeRelationRealEntity previousPartnerRel) {
private void optionallyCreateExPartnerRelation(final HsOfficePartnerRbacEntity saved, final HsOfficeRelationRealEntity previousPartnerRel) {
if (!saved.getPartnerRel().getUuid().equals(previousPartnerRel.getUuid())) {
// TODO.impl: we also need to use the new partner-person as the anchor
relationRepo.save(previousPartnerRel.toBuilder().uuid(null).type(EX_PARTNER).build());
}
}
private HsOfficePartnerEntity createPartnerEntity(final HsOfficePartnerInsertResource body) {
final var entityToSave = new HsOfficePartnerEntity();
entityToSave.setPartnerNumber(cropTag(HsOfficePartnerEntity.PARTNER_NUMBER_TAG, body.getPartnerNumber()));
private HsOfficePartnerRbacEntity createPartnerEntity(final HsOfficePartnerInsertResource body) {
final var entityToSave = new HsOfficePartnerRbacEntity();
entityToSave.setPartnerNumber(cropTag(HsOfficePartnerRbacEntity.PARTNER_NUMBER_TAG, body.getPartnerNumber()));
entityToSave.setPartnerRel(persistPartnerRel(body.getPartnerRel()));
entityToSave.setDetails(mapper.map(body.getDetails(), HsOfficePartnerDetailsEntity.class));
return entityToSave;
@ -197,4 +200,8 @@ public class HsOfficePartnerController implements HsOfficePartnersApi {
throw new ReferenceNotFoundException(entityClass, uuid, exc);
}
}
final BiConsumer<HsOfficePartnerRbacEntity, HsOfficePartnerResource> ENTITY_TO_RESOURCE_POSTMAPPER = (entity, resource) -> {
resource.setPartnerNumber(entity.getTaggedPartnerNumber());
};
}

View File

@ -1,128 +0,0 @@
package net.hostsharing.hsadminng.hs.office.partner;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import net.hostsharing.hsadminng.errors.DisplayAs;
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContact;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePerson;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealEntity;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRbacEntity;
import net.hostsharing.hsadminng.persistence.BaseEntity;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelation;
import net.hostsharing.hsadminng.rbac.generator.RbacSpec;
import net.hostsharing.hsadminng.rbac.generator.RbacSpec.SQL;
import net.hostsharing.hsadminng.repr.Stringify;
import net.hostsharing.hsadminng.repr.Stringifyable;
import org.hibernate.annotations.NotFound;
import org.hibernate.annotations.NotFoundAction;
import jakarta.persistence.*;
import java.io.IOException;
import java.util.UUID;
import static jakarta.persistence.CascadeType.*;
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Column.dependsOnColumn;
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.ColumnValue.usingDefaultCase;
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.GLOBAL;
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Permission.*;
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Permission.SELECT;
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Role.*;
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.SQL.directlyFetchedByDependsOnColumn;
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.rbacViewFor;
import static java.util.Optional.ofNullable;
import static net.hostsharing.hsadminng.repr.Stringify.stringify;
@Entity
@Table(schema = "hs_office", name = "partner_rv")
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@DisplayAs("Partner")
public class HsOfficePartnerEntity implements Stringifyable, BaseEntity<HsOfficePartnerEntity> {
public static final String PARTNER_NUMBER_TAG = "P-";
private static Stringify<HsOfficePartnerEntity> stringify = stringify(HsOfficePartnerEntity.class, "partner")
.withIdProp(HsOfficePartnerEntity::toShortString)
.withProp(p -> ofNullable(p.getPartnerRel())
.map(HsOfficeRelation::getHolder)
.map(HsOfficePerson::toShortString)
.orElse(null))
.withProp(p -> ofNullable(p.getPartnerRel())
.map(HsOfficeRelation::getContact)
.map(HsOfficeContact::toShortString)
.orElse(null))
.quotedValues(false);
@Id
@GeneratedValue
private UUID uuid;
@Version
private int version;
@Column(name = "partnernumber", columnDefinition = "numeric(5) not null")
private Integer partnerNumber;
@ManyToOne(cascade = { PERSIST, MERGE, REFRESH, DETACH }, optional = false, fetch = FetchType.LAZY)
@JoinColumn(name = "partnerreluuid", nullable = false)
private HsOfficeRelationRealEntity partnerRel;
@ManyToOne(cascade = { PERSIST, MERGE, REFRESH, DETACH }, optional = true, fetch = FetchType.LAZY)
@JoinColumn(name = "detailsuuid")
@NotFound(action = NotFoundAction.IGNORE)
private HsOfficePartnerDetailsEntity details;
@Override
public HsOfficePartnerEntity load() {
BaseEntity.super.load();
partnerRel.load();
details.load();
return this;
}
public String getTaggedPartnerNumber() {
return PARTNER_NUMBER_TAG + partnerNumber;
}
@Override
public String toString() {
return stringify.apply(this);
}
@Override
public String toShortString() {
return getTaggedPartnerNumber();
}
public static RbacSpec rbac() {
return rbacViewFor("partner", HsOfficePartnerEntity.class)
.withIdentityView(SQL.projection("'P-' || partnerNumber"))
.withUpdatableColumns("partnerRelUuid")
.toRole(GLOBAL, ADMIN).grantPermission(INSERT)
.importRootEntityAliasProxy("partnerRel", HsOfficeRelationRbacEntity.class,
usingDefaultCase(),
directlyFetchedByDependsOnColumn(),
dependsOnColumn("partnerRelUuid"))
.createPermission(DELETE).grantedTo("partnerRel", OWNER)
.createPermission(UPDATE).grantedTo("partnerRel", ADMIN)
.createPermission(SELECT).grantedTo("partnerRel", TENANT)
.importSubEntityAlias("partnerDetails", HsOfficePartnerDetailsEntity.class,
directlyFetchedByDependsOnColumn(),
dependsOnColumn("detailsUuid"))
.createPermission("partnerDetails", DELETE).grantedTo("partnerRel", OWNER)
.createPermission("partnerDetails", UPDATE).grantedTo("partnerRel", AGENT)
.createPermission("partnerDetails", SELECT).grantedTo("partnerRel", AGENT); // not TENANT!
}
public static void main(String[] args) throws IOException {
rbac().generateWithBaseFileName("5-hs-office/504-partner/5043-hs-office-partner-rbac");
}
}

View File

@ -9,10 +9,10 @@ import jakarta.persistence.EntityManager;
class HsOfficePartnerEntityPatcher implements EntityPatcher<HsOfficePartnerPatchResource> {
private final EntityManager em;
private final HsOfficePartnerEntity entity;
private final HsOfficePartnerRbacEntity entity;
HsOfficePartnerEntityPatcher(
final EntityManager em,
final HsOfficePartnerEntity entity) {
final HsOfficePartnerRbacEntity entity) {
this.em = em;
this.entity = entity;
}

View File

@ -0,0 +1,59 @@
package net.hostsharing.hsadminng.hs.office.partner;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.experimental.SuperBuilder;
import net.hostsharing.hsadminng.errors.DisplayAs;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRbacEntity;
import net.hostsharing.hsadminng.rbac.generator.RbacSpec;
import net.hostsharing.hsadminng.rbac.generator.RbacSpec.SQL;
import jakarta.persistence.*;
import java.io.IOException;
import static jakarta.persistence.CascadeType.*;
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Column.dependsOnColumn;
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.ColumnValue.usingDefaultCase;
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.GLOBAL;
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Permission.*;
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Permission.SELECT;
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Role.*;
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.SQL.directlyFetchedByDependsOnColumn;
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.rbacViewFor;
@Entity
@Table(schema = "hs_office", name = "partner_rv")
@Getter
@Setter
@SuperBuilder(toBuilder = true)
@NoArgsConstructor
@DisplayAs("RbacPartner")
public class HsOfficePartnerRbacEntity extends HsOfficePartner<HsOfficePartnerRbacEntity> {
public static RbacSpec rbac() {
return rbacViewFor("partner", HsOfficePartnerRbacEntity.class)
.withIdentityView(SQL.projection("'P-' || partnerNumber"))
.withUpdatableColumns("partnerRelUuid")
.toRole(GLOBAL, ADMIN).grantPermission(INSERT)
.importRootEntityAliasProxy("partnerRel", HsOfficeRelationRbacEntity.class,
usingDefaultCase(),
directlyFetchedByDependsOnColumn(),
dependsOnColumn("partnerRelUuid"))
.createPermission(DELETE).grantedTo("partnerRel", OWNER)
.createPermission(UPDATE).grantedTo("partnerRel", ADMIN)
.createPermission(SELECT).grantedTo("partnerRel", TENANT)
.importSubEntityAlias("partnerDetails", HsOfficePartnerDetailsEntity.class,
directlyFetchedByDependsOnColumn(),
dependsOnColumn("detailsUuid"))
.createPermission("partnerDetails", DELETE).grantedTo("partnerRel", OWNER)
.createPermission("partnerDetails", UPDATE).grantedTo("partnerRel", AGENT)
.createPermission("partnerDetails", SELECT).grantedTo("partnerRel", AGENT); // not TENANT!
}
public static void main(String[] args) throws IOException {
rbac().generateWithBaseFileName("5-hs-office/504-partner/5043-hs-office-partner-rbac");
}
}

View File

@ -0,0 +1,47 @@
package net.hostsharing.hsadminng.hs.office.partner;
import io.micrometer.core.annotation.Timed;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.Repository;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
public interface HsOfficePartnerRbacRepository extends Repository<HsOfficePartnerRbacEntity, UUID> {
@Timed("app.office.partners.repo.findByUuid.rbac")
Optional<HsOfficePartnerRbacEntity> findByUuid(UUID id);
@Timed("app.office.partners.repo.findAll.rbac")
List<HsOfficePartnerRbacEntity> findAll(); // TODO.refa: move to a repo in test sources
@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.rbac")
List<HsOfficePartnerRbacEntity> findPartnerByOptionalNameLike(String name);
@Timed("app.office.partners.repo.findPartnerByPartnerNumber.rbac")
Optional<HsOfficePartnerRbacEntity> findPartnerByPartnerNumber(Integer partnerNumber);
@Timed("app.office.partners.repo.save.rbac")
HsOfficePartnerRbacEntity save(final HsOfficePartnerRbacEntity entity);
@Timed("app.office.partners.repo.count.rbac")
long count();
@Timed("app.office.partners.repo.deleteByUuid.rbac")
int deleteByUuid(UUID uuid);
}

View File

@ -0,0 +1,21 @@
package net.hostsharing.hsadminng.hs.office.partner;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.experimental.SuperBuilder;
import net.hostsharing.hsadminng.errors.DisplayAs;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
@Entity
@Table(schema = "hs_office", name = "partner")
@Getter
@Setter
@SuperBuilder(toBuilder = true)
@NoArgsConstructor
@DisplayAs("RealPartner")
public class HsOfficePartnerRealEntity extends HsOfficePartner<HsOfficePartnerRealEntity> {
}

View File

@ -0,0 +1,41 @@
package net.hostsharing.hsadminng.hs.office.partner;
import io.micrometer.core.annotation.Timed;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.Repository;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
public interface HsOfficePartnerRealRepository extends Repository<HsOfficePartnerRealEntity, UUID> {
@Timed("app.office.partners.repo.findByUuid.real")
Optional<HsOfficePartnerRealEntity> findByUuid(UUID id);
@Timed("app.office.partners.repo.findAll.real")
List<HsOfficePartnerRbacEntity> findAll(); // TODO.refa: move to a repo in test sources
@Query(value = """
select partner.uuid, partner.detailsuuid, partner.partnernumber, partner.partnerreluuid, partner.version
from hs_office.partner 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.real")
List<HsOfficePartnerRealEntity> findPartnerByOptionalNameLike(String name);
@Timed("app.office.partners.repo.findPartnerByPartnerNumber.real")
Optional<HsOfficePartnerRealEntity> findPartnerByPartnerNumber(Integer partnerNumber);
@Timed("app.office.partners.repo.save.real")
HsOfficePartnerRealEntity save(final HsOfficePartnerRealEntity entity);
}

View File

@ -1,45 +0,0 @@
package net.hostsharing.hsadminng.hs.office.partner;
import io.micrometer.core.annotation.Timed;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.Repository;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
public interface HsOfficePartnerRepository extends Repository<HsOfficePartnerEntity, UUID> {
@Timed("app.office.partners.repo.findByUuid")
Optional<HsOfficePartnerEntity> findByUuid(UUID id);
@Timed("app.office.partners.repo.findAll")
List<HsOfficePartnerEntity> findAll(); // TODO.refa: move to a repo in test sources
@Query("""
SELECT partner FROM HsOfficePartnerEntity partner
JOIN HsOfficeRelationRealEntity rel ON rel.uuid = partner.partnerRel.uuid
JOIN HsOfficeContactRealEntity contact ON contact.uuid = rel.contact.uuid
JOIN HsOfficePersonRealEntity person ON person.uuid = rel.holder.uuid
WHERE :name is null
OR partner.details.birthName like concat(cast(:name as text), '%')
OR contact.caption like concat(cast(:name as text), '%')
OR person.tradeName like concat(cast(:name as text), '%')
OR person.givenName like concat(cast(:name as text), '%')
OR person.familyName like concat(cast(:name as text), '%')
""")
@Timed("app.office.partners.repo.findPartnerByOptionalNameLike")
List<HsOfficePartnerEntity> findPartnerByOptionalNameLike(String name);
@Timed("app.office.partners.repo.findPartnerByPartnerNumber")
Optional<HsOfficePartnerEntity> findPartnerByPartnerNumber(Integer partnerNumber);
@Timed("app.office.partners.repo.save")
HsOfficePartnerEntity save(final HsOfficePartnerEntity entity);
@Timed("app.office.partners.repo.count")
long count();
@Timed("app.office.partners.repo.deleteByUuid")
int deleteByUuid(UUID uuid);
}

View File

@ -1,7 +1,7 @@
package net.hostsharing.hsadminng.hs.office.person;
import io.micrometer.core.annotation.Timed;
import net.hostsharing.hsadminng.mapper.StandardMapper;
import net.hostsharing.hsadminng.mapper.StrictMapper;
import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficePersonsApi;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficePersonInsertResource;
@ -24,7 +24,7 @@ public class HsOfficePersonController implements HsOfficePersonsApi {
private Context context;
@Autowired
private StandardMapper mapper;
private StrictMapper mapper;
@Autowired
private HsOfficePersonRbacRepository personRepo;

View File

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

View File

@ -9,7 +9,7 @@ import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficeRelation
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.*;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRealEntity;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRealRepository;
import net.hostsharing.hsadminng.mapper.StandardMapper;
import net.hostsharing.hsadminng.mapper.StrictMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.transaction.annotation.Transactional;
@ -32,7 +32,7 @@ public class HsOfficeRelationController implements HsOfficeRelationsApi {
private Context context;
@Autowired
private StandardMapper mapper;
private StrictMapper mapper;
@Autowired
private HsOfficeRelationRbacRepository rbacRelationRepo;

View File

@ -2,11 +2,14 @@ package net.hostsharing.hsadminng.hs.office.sepamandate;
import io.micrometer.core.annotation.Timed;
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.model.HsOfficeDebitorResource;
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.StandardMapper;
import net.hostsharing.hsadminng.mapper.StrictMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
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.PersistenceContext;
import jakarta.validation.ValidationException;
import java.util.List;
import java.util.UUID;
import java.util.function.BiConsumer;
@ -29,7 +33,13 @@ public class HsOfficeSepaMandateController implements HsOfficeSepaMandatesApi {
private Context context;
@Autowired
private StandardMapper mapper;
private StrictMapper mapper;
@Autowired
private HsOfficeDebitorRepository debitorRepo;
@Autowired
private HsOfficeBankAccountRepository bankAccountRepo;
@Autowired
private HsOfficeSepaMandateRepository sepaMandateRepo;
@ -137,10 +147,22 @@ public class HsOfficeSepaMandateController implements HsOfficeSepaMandatesApi {
if (entity.getValidity().hasUpperBound()) {
resource.setValidTo(entity.getValidity().upper().minusDays(1));
}
resource.setDebitor(mapper.map(entity.getDebitor(), HsOfficeDebitorResource.class));
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) -> {
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

@ -0,0 +1,17 @@
package net.hostsharing.hsadminng.hs.validation;
import lombok.experimental.UtilityClass;
import jakarta.validation.ValidationException;
import java.util.Optional;
import java.util.UUID;
import java.util.function.Function;
@UtilityClass
public class UuidResolver {
public static <T> T resolve(final String jsonPath, final UUID uuid, final Function<UUID, Optional<T>> findByUuid) {
return findByUuid.apply(uuid)
.orElseThrow(() -> new ValidationException("Unable to find " + jsonPath + ": " + uuid));
}
}

View File

@ -1,17 +0,0 @@
package net.hostsharing.hsadminng.mapper;
import net.hostsharing.hsadminng.persistence.EntityManagerWrapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* A nicer API for ModelMapper in standard mode.
*/
@Component
public class StandardMapper extends Mapper {
public StandardMapper(@Autowired final EntityManagerWrapper em) {
super(em);
getConfiguration().setAmbiguityIgnored(true);
}
}

View File

@ -3,6 +3,7 @@ package net.hostsharing.hsadminng.persistence;
import org.hibernate.Hibernate;
import jakarta.persistence.EntityManager;
import java.util.UUID;
public interface BaseEntity<T extends BaseEntity<?>> {
@ -15,4 +16,10 @@ public interface BaseEntity<T extends BaseEntity<?>> {
//noinspection unchecked
return (T) this;
};
default T reload(final EntityManager em) {
em.flush();
em.refresh(this);
return load();
}
}

View File

@ -1,38 +0,0 @@
package net.hostsharing.hsadminng.persistence;
import net.hostsharing.hsadminng.errors.DisplayAs.DisplayName;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import jakarta.persistence.Entity;
import jakarta.validation.ValidationException;
@Service
public class EntityExistsValidator {
@Autowired
private EntityManagerWrapper em;
public <T extends BaseEntity<T>> void validateEntityExists(final String property, final T entitySkeleton) {
final var foundEntity = em.find(entityClass(entitySkeleton), entitySkeleton.getUuid());
if ( foundEntity == null) {
throw new ValidationException("Unable to find " + DisplayName.of(entitySkeleton) + " by " + property + ": " + entitySkeleton.getUuid());
}
}
private static <T extends BaseEntity<T>> Class<?> entityClass(final T entityOrProxy) {
final var entityClass = entityClass(entityOrProxy.getClass());
if (entityClass == null) {
throw new IllegalArgumentException("@Entity not found in superclass hierarchy of " + entityOrProxy.getClass());
}
return entityClass;
}
private static Class<?> entityClass(final Class<?> entityOrProxyClass) {
return entityOrProxyClass.isAnnotationPresent(Entity.class)
? entityOrProxyClass
: entityOrProxyClass.getSuperclass() == null
? null
: entityClass(entityOrProxyClass.getSuperclass());
}
}

View File

@ -2,7 +2,7 @@ package net.hostsharing.hsadminng.rbac.grant;
import io.micrometer.core.annotation.Timed;
import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.mapper.StandardMapper;
import net.hostsharing.hsadminng.mapper.StrictMapper;
import net.hostsharing.hsadminng.rbac.generated.api.v1.api.RbacGrantsApi;
import net.hostsharing.hsadminng.rbac.generated.api.v1.model.RbacGrantResource;
import org.springframework.beans.factory.annotation.Autowired;
@ -23,7 +23,7 @@ public class RbacGrantController implements RbacGrantsApi {
private Context context;
@Autowired
private StandardMapper mapper;
private StrictMapper mapper;
@Autowired
private RbacGrantRepository rbacGrantRepository;

View File

@ -30,7 +30,7 @@ public class RbacGrantsDiagramService {
try (BufferedWriter writer = new BufferedWriter(new FileWriter(fileName))) {
writer.write("""
### all grants to %s
```mermaid
%s
```
@ -49,8 +49,18 @@ public class RbacGrantsDiagramService {
NON_TEST_ENTITIES;
public static final EnumSet<Include> ALL = EnumSet.allOf(Include.class);
public static final EnumSet<Include> ALL_TEST_ENTITY_RELATED = EnumSet.of(USERS, DETAILS, NOT_ASSUMED, TEST_ENTITIES, PERMISSIONS);
public static final EnumSet<Include> ALL_NON_TEST_ENTITY_RELATED = EnumSet.of(USERS, DETAILS, NOT_ASSUMED, NON_TEST_ENTITIES, PERMISSIONS);
public static final EnumSet<Include> ALL_TEST_ENTITY_RELATED = EnumSet.of(
USERS,
DETAILS,
NOT_ASSUMED,
TEST_ENTITIES,
PERMISSIONS);
public static final EnumSet<Include> ALL_NON_TEST_ENTITY_RELATED = EnumSet.of(
USERS,
DETAILS,
NOT_ASSUMED,
NON_TEST_ENTITIES,
PERMISSIONS);
}
@Autowired
@ -66,9 +76,9 @@ public class RbacGrantsDiagramService {
public String allGrantsTocurrentSubject(final EnumSet<Include> includes) {
final var graph = new LimitedHashSet<RawRbacGrantEntity>();
for ( UUID subjectUuid: context.fetchCurrentSubjectOrAssumedRolesUuids() ) {
for (UUID subjectUuid : context.fetchCurrentSubjectOrAssumedRolesUuids()) {
traverseGrantsTo(graph, subjectUuid, includes);
}
}
return toMermaidFlowchart(graph, includes);
}
@ -78,7 +88,7 @@ public class RbacGrantsDiagramService {
if (!includes.contains(PERMISSIONS) && g.getDescendantIdName().startsWith("perm:")) {
return;
}
if ( !g.getDescendantIdName().startsWith("role:rbac.global")) {
if (!g.getDescendantIdName().startsWith("role:rbac.global")) {
if (!includes.contains(TEST_ENTITIES) && g.getDescendantIdName().contains(":rbactest.")) {
return;
}
@ -94,12 +104,17 @@ public class RbacGrantsDiagramService {
}
public String allGrantsFrom(final UUID targetObject, final String op, final EnumSet<Include> includes) {
final var refUuid = (UUID) em.createNativeQuery("SELECT uuid FROM rbac.permission WHERE objectuuid=:targetObject AND op=:op")
final var graph = new LimitedHashSet<RawRbacGrantEntity>();
@SuppressWarnings("unchecked") // List -> List<List<UUID>>
final var refUuidLists = (List<List<UUID>>) em.createNativeQuery(
"select uuid from rbac.permission where objectUuid=:targetObject and op=:op",
List.class)
.setParameter("targetObject", targetObject)
.setParameter("op", op)
.getSingleResult();
final var graph = new LimitedHashSet<RawRbacGrantEntity>();
traverseGrantsFrom(graph, refUuid, includes);
.getResultList();
refUuidLists.stream().flatMap(Collection::stream)
.forEach(refUuid -> traverseGrantsFrom(graph, refUuid, includes));
return toMermaidFlowchart(graph, includes);
}
@ -125,20 +140,20 @@ public class RbacGrantsDiagramService {
final var entities =
includes.contains(DETAILS)
? graph.stream()
.flatMap(g -> Stream.of(
new Node(g.getAscendantIdName(), g.getAscendingUuid()),
new Node(g.getDescendantIdName(), g.getDescendantUuid()))
)
.collect(groupingBy(RbacGrantsDiagramService::renderEntityIdName))
.entrySet().stream()
.map(entity -> "subgraph " + cleanId(entity.getKey()) + renderSubgraph(entity.getKey()) + "\n\n "
+ entity.getValue().stream()
.map(n -> renderNode(n.idName(), n.uuid()).replace("\n", "\n "))
.sorted()
.distinct()
.collect(joining("\n\n ")))
.collect(joining("\n\nend\n\n"))
+ "\n\nend\n\n"
.flatMap(g -> Stream.of(
new Node(g.getAscendantIdName(), g.getAscendingUuid()),
new Node(g.getDescendantIdName(), g.getDescendantUuid()))
)
.collect(groupingBy(RbacGrantsDiagramService::renderEntityIdName))
.entrySet().stream()
.map(entity -> "subgraph " + cleanId(entity.getKey()) + renderSubgraph(entity.getKey()) + "\n\n "
+ entity.getValue().stream()
.map(n -> renderNode(n.idName(), n.uuid()).replace("\n", "\n "))
.sorted()
.distinct()
.collect(joining("\n\n ")))
.collect(joining("\n\nend\n\n"))
+ "\n\nend\n\n"
: "";
final var grants = graph.stream()
@ -193,7 +208,7 @@ public class RbacGrantsDiagramService {
final var refType = refType(idName);
if (refType.equals("user")) {
final var displayName = idName.substring(refType.length()+1);
final var displayName = idName.substring(refType.length() + 1);
return "(" + displayName + "\nref:" + uuid + ")";
}
if (refType.equals("role")) {
@ -215,15 +230,20 @@ public class RbacGrantsDiagramService {
@NotNull
private static String cleanId(final String idName) {
return idName.replaceAll("@.*", "")
.replace("[", "").replace("]", "").replace("(", "").replace(")", "").replace(",", "").replace(">", ":").replace("|", "_");
.replace("[", "")
.replace("]", "")
.replace("(", "")
.replace(")", "")
.replace(",", "")
.replace(">", ":")
.replace("|", "_");
}
static class LimitedHashSet<T> extends HashSet<T> {
@Override
public boolean add(final T t) {
if (size() < GRANT_LIMIT ) {
if (size() < GRANT_LIMIT) {
return super.add(t);
} else {
return false;

View File

@ -2,7 +2,7 @@ package net.hostsharing.hsadminng.rbac.role;
import io.micrometer.core.annotation.Timed;
import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.mapper.StandardMapper;
import net.hostsharing.hsadminng.mapper.StrictMapper;
import net.hostsharing.hsadminng.rbac.generated.api.v1.api.RbacRolesApi;
import net.hostsharing.hsadminng.rbac.generated.api.v1.model.RbacRoleResource;
import org.springframework.beans.factory.annotation.Autowired;
@ -19,7 +19,7 @@ public class RbacRoleController implements RbacRolesApi {
private Context context;
@Autowired
private StandardMapper mapper;
private StrictMapper mapper;
@Autowired
private RbacRoleRepository rbacRoleRepository;

View File

@ -2,7 +2,7 @@ package net.hostsharing.hsadminng.rbac.subject;
import io.micrometer.core.annotation.Timed;
import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.mapper.StandardMapper;
import net.hostsharing.hsadminng.mapper.StrictMapper;
import net.hostsharing.hsadminng.rbac.generated.api.v1.api.RbacSubjectsApi;
import net.hostsharing.hsadminng.rbac.generated.api.v1.model.RbacSubjectPermissionResource;
import net.hostsharing.hsadminng.rbac.generated.api.v1.model.RbacSubjectResource;
@ -22,7 +22,7 @@ public class RbacSubjectController implements RbacSubjectsApi {
private Context context;
@Autowired
private StandardMapper mapper;
private StrictMapper mapper;
@Autowired
private RbacSubjectRepository rbacSubjectRepository;

View File

@ -1,7 +1,7 @@
package net.hostsharing.hsadminng.rbac.test.cust;
import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.mapper.StandardMapper;
import net.hostsharing.hsadminng.mapper.StrictMapper;
import net.hostsharing.hsadminng.test.generated.api.v1.api.TestCustomersApi;
import net.hostsharing.hsadminng.test.generated.api.v1.model.TestCustomerResource;
import org.springframework.beans.factory.annotation.Autowired;
@ -21,7 +21,7 @@ public class TestCustomerController implements TestCustomersApi {
private Context context;
@Autowired
private StandardMapper mapper;
private StrictMapper mapper;
@Autowired
private TestCustomerRepository testCustomerRepository;

View File

@ -1,6 +1,6 @@
package net.hostsharing.hsadminng.rbac.test.pac;
import net.hostsharing.hsadminng.mapper.StandardMapper;
import net.hostsharing.hsadminng.mapper.StrictMapper;
import net.hostsharing.hsadminng.mapper.OptionalFromJson;
import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.test.generated.api.v1.api.TestPackagesApi;
@ -21,7 +21,7 @@ public class TestPackageController implements TestPackagesApi {
private Context context;
@Autowired
private StandardMapper mapper;
private StrictMapper mapper;
@Autowired
private TestPackageRepository testPackageRepository;

View File

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

View File

@ -43,7 +43,10 @@ end; $$;
do language plpgsql $$
begin
call base.defineContext('creating coopSharesTransaction test-data');
call base.defineContext('creating coopSharesTransaction test-data',
null,
'superuser-alex@hostsharing.net',
'rbac.global#global:ADMIN');
SET CONSTRAINTS ALL DEFERRED;
call hs_office.coopsharetx_create_test_data(10001, '01');

View File

@ -49,7 +49,10 @@ end; $$;
do language plpgsql $$
begin
call base.defineContext('creating coopAssetsTransaction test-data');
call base.defineContext('creating coopAssetsTransaction test-data',
null,
'superuser-alex@hostsharing.net',
'rbac.global#global:ADMIN');
SET CONSTRAINTS ALL DEFERRED;
call hs_office.coopassettx_create_test_data(10001, '01');

View File

@ -81,8 +81,7 @@ class RestResponseEntityExceptionHandlerUnitTest {
void handleJpaObjectRetrievalFailureExceptionWithDisplayName() {
// given
final var givenException = new JpaObjectRetrievalFailureException(
new EntityNotFoundException(
"Unable to find net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity with id 12345-123454")
new EntityNotFoundException("Unable to find Entity with id 12345-123454")
);
final var givenWebRequest = mock(WebRequest.class);
@ -91,7 +90,7 @@ class RestResponseEntityExceptionHandlerUnitTest {
// then
assertThat(errorResponse.getBody().getStatusCode()).isEqualTo(400);
assertThat(errorResponse.getBody().getMessage()).isEqualTo("ERROR: [400] Unable to find Partner with uuid 12345-123454");
assertThat(errorResponse.getBody().getMessage()).isEqualTo("ERROR: [400] Unable to find Entity with id 12345-123454");
}
@Test

View File

@ -643,7 +643,6 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
final var givenProject = realProjectRepo.findByCaption(projectCaption).stream()
.findAny().orElseThrow();
final var newBookingItem = HsBookingItemRealEntity.builder()
.uuid(UUID.randomUUID())
.project(givenProject)
.type(hsBookingItemType)
.caption("some test-booking")

View File

@ -14,7 +14,7 @@ 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.context.TestConfiguration;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.http.MediaType;
@ -48,23 +48,23 @@ class HsBookingItemControllerRestTest {
@Autowired
MockMvc mockMvc;
@MockBean
@MockitoBean
Context contextMock;
@Autowired
@SuppressWarnings("unused") // not used in test, but in controller class
StrictMapper mapper;
@MockBean
@MockitoBean
EntityManagerWrapper em;
@MockBean
@MockitoBean
EntityManagerFactory emf;
@MockBean
@MockitoBean
HsBookingProjectRealRepository realProjectRepo;
@MockBean
@MockitoBean
HsBookingItemRbacRepository rbacBookingItemRepo;
@TestConfiguration

View File

@ -13,7 +13,7 @@ import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
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.test.context.bean.override.mockito.MockitoBean;
import org.springframework.context.annotation.Import;
import org.springframework.orm.jpa.JpaSystemException;
@ -61,7 +61,7 @@ class HsBookingItemRepositoryIntegrationTest extends ContextBasedTestWithCleanup
@PersistenceContext
EntityManager em;
@MockBean
@MockitoBean
HttpServletRequest request;
@Test

View File

@ -270,7 +270,6 @@ class HsBookingProjectControllerAcceptanceTest extends ContextBasedTestWithClean
context.define("superuser-alex@hostsharing.net");
final var givenDebitor = debitorRepo.findByDebitorNumber(debitorNumber).stream().findAny().orElseThrow();
final var newBookingProject = HsBookingProjectRealEntity.builder()
.uuid(UUID.randomUUID())
.debitor(givenDebitor)
.caption(caption)
.build();

View File

@ -13,7 +13,7 @@ import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
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.test.context.bean.override.mockito.MockitoBean;
import org.springframework.context.annotation.Import;
import org.springframework.orm.jpa.JpaSystemException;
@ -56,7 +56,7 @@ class HsBookingProjectRepositoryIntegrationTest extends ContextBasedTestWithClea
@PersistenceContext
EntityManager em;
@MockBean
@MockitoBean
HttpServletRequest request;
@Test

View File

@ -488,7 +488,6 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
final var givenAsset = givenSomeTemporaryHostingAsset(() ->
HsHostingAssetRealEntity.builder()
.uuid(UUID.randomUUID())
.bookingItem(givenSomeNewBookingItem(
"D-1000111 default project",
HsBookingItemType.MANAGED_SERVER,
@ -571,7 +570,6 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
final var givenAsset = givenSomeTemporaryHostingAsset(() ->
HsHostingAssetRealEntity.builder()
.uuid(UUID.randomUUID())
.type(UNIX_USER)
.parentAsset(givenRealHostingAsset(MANAGED_WEBSPACE, "fir01"))
.identifier("fir01-temp")
@ -648,7 +646,6 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
context.define("superuser-alex@hostsharing.net");
final var givenAsset = givenSomeTemporaryHostingAsset(() ->
HsHostingAssetRealEntity.builder()
.uuid(UUID.randomUUID())
.bookingItem(givenSomeNewBookingItem(
"D-1000111 default project",
HsBookingItemType.MANAGED_SERVER,
@ -681,7 +678,6 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
context.define("superuser-alex@hostsharing.net");
final var givenAsset = givenSomeTemporaryHostingAsset(() ->
HsHostingAssetRealEntity.builder()
.uuid(UUID.randomUUID())
.bookingItem(givenSomeNewBookingItem(
"D-1000111 default project",
HsBookingItemType.MANAGED_SERVER,

View File

@ -9,7 +9,7 @@ import net.hostsharing.hsadminng.config.JsonObjectMapperConfiguration;
import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemRealRepository;
import net.hostsharing.hsadminng.mapper.Array;
import net.hostsharing.hsadminng.mapper.StandardMapper;
import net.hostsharing.hsadminng.mapper.StrictMapper;
import net.hostsharing.hsadminng.persistence.EntityManagerWrapper;
import net.hostsharing.hsadminng.config.DisableSecurityConfig;
import org.junit.jupiter.api.BeforeEach;
@ -24,7 +24,7 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.http.MediaType;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
@ -54,7 +54,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@WebMvcTest(HsHostingAssetController.class)
@Import({ StandardMapper.class, JsonObjectMapperConfiguration.class, DisableSecurityConfig.class })
@Import({ StrictMapper.class, JsonObjectMapperConfiguration.class, DisableSecurityConfig.class })
@RunWith(SpringRunner.class)
@ActiveProfiles("test")
public class HsHostingAssetControllerRestTest {
@ -62,27 +62,27 @@ public class HsHostingAssetControllerRestTest {
@Autowired
MockMvc mockMvc;
@MockBean
@MockitoBean
Context contextMock;
@Autowired
@SuppressWarnings("unused") // not used in test, but in controller class
StandardMapper mapper;
StrictMapper mapper;
@MockBean
@MockitoBean
EntityManagerWrapper em;
@MockBean
@MockitoBean
EntityManagerFactory emf;
@MockBean
@MockitoBean
@SuppressWarnings("unused") // bean needs to be present for HsHostingAssetController
private HsBookingItemRealRepository realBookingItemRepo;
@MockBean
@MockitoBean
private HsHostingAssetRealRepository realAssetRepo;
@MockBean
@MockitoBean
private HsHostingAssetRbacRepository rbacAssetRepo;
@TestConfiguration

View File

@ -17,7 +17,7 @@ import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
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.test.context.bean.override.mockito.MockitoBean;
import org.springframework.context.annotation.Import;
import org.springframework.orm.jpa.JpaSystemException;
@ -70,7 +70,7 @@ class HsHostingAssetRepositoryIntegrationTest extends ContextBasedTestWithCleanu
@PersistenceContext
EntityManager em;
@MockBean
@MockitoBean
HttpServletRequest request;
@Test

View File

@ -12,7 +12,7 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import com.fasterxml.jackson.databind.ObjectMapper;
import net.hostsharing.hsadminng.config.JsonObjectMapperConfiguration;
import net.hostsharing.hsadminng.mapper.StandardMapper;
import net.hostsharing.hsadminng.mapper.StrictMapper;
import net.hostsharing.hsadminng.persistence.EntityManagerWrapper;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
@ -59,7 +59,7 @@ class DomainSetupHostingAssetFactoryUnitTest {
private ObjectMapper jsonMapper = new JsonObjectMapperConfiguration().customObjectMapper().build();
@Spy
private StandardMapper standardMapper = new StandardMapper(emw);
private StrictMapper StrictMapper = new StrictMapper(emw);
@InjectMocks
private HsBookingItemCreatedListener listener;

View File

@ -8,7 +8,7 @@ import net.hostsharing.hsadminng.hs.booking.item.BookingItemCreatedEventEntity;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemRealEntity;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType;
import net.hostsharing.hsadminng.lambda.Reducer;
import net.hostsharing.hsadminng.mapper.StandardMapper;
import net.hostsharing.hsadminng.mapper.StrictMapper;
import net.hostsharing.hsadminng.persistence.EntityManagerWrapper;
import net.hostsharing.hsadminng.persistence.EntityManagerWrapperFake;
import org.junit.jupiter.api.extension.ExtendWith;
@ -42,7 +42,7 @@ class HsBookingItemCreatedListenerUnitTest {
private ObjectMapper jsonMapper = new JsonObjectMapperConfiguration().customObjectMapper().build();
@Spy
private StandardMapper standardMapper = new StandardMapper(emw);
private StrictMapper StrictMapper = new StrictMapper(emw);
@InjectMocks
private HsBookingItemCreatedListener listener;

View File

@ -13,7 +13,7 @@ import net.hostsharing.hsadminng.hs.hosting.asset.validators.Dns;
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContact;
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRealEntity;
import net.hostsharing.hsadminng.lambda.Reducer;
import net.hostsharing.hsadminng.mapper.StandardMapper;
import net.hostsharing.hsadminng.mapper.StrictMapper;
import net.hostsharing.hsadminng.persistence.EntityManagerWrapper;
import net.hostsharing.hsadminng.persistence.EntityManagerWrapperFake;
import org.junit.jupiter.api.BeforeEach;
@ -55,7 +55,7 @@ class ManagedWebspaceHostingAssetFactoryUnitTest {
private ObjectMapper jsonMapper = new JsonObjectMapperConfiguration().customObjectMapper().build();
@Spy
private StandardMapper standardMapper = new StandardMapper(emw);
private StrictMapper StrictMapper = new StrictMapper(emw);
@InjectMocks
private HsBookingItemCreatedListener listener;

View File

@ -10,7 +10,7 @@ import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorEntity;
import net.hostsharing.hsadminng.hs.office.membership.HsOfficeMembershipEntity;
import net.hostsharing.hsadminng.hs.office.membership.HsOfficeMembershipStatus;
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerDetailsEntity;
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity;
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerRealEntity;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRealEntity;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonType;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelation;
@ -84,7 +84,7 @@ public abstract class BaseOfficeDataImport extends CsvDataImport {
static Map<Integer, HsOfficeContactRealEntity> contacts = new WriteOnceMap<>();
static Map<Integer, HsOfficePersonRealEntity> persons = new WriteOnceMap<>();
static Map<Integer, HsOfficePartnerEntity> partners = new WriteOnceMap<>();
static Map<Integer, HsOfficePartnerRealEntity> partners = new WriteOnceMap<>();
static Map<Integer, HsOfficeDebitorEntity> debitors = new WriteOnceMap<>();
static Map<Integer, HsOfficeMembershipEntity> memberships = new WriteOnceMap<>();
@ -743,7 +743,7 @@ public abstract class BaseOfficeDataImport extends CsvDataImport {
null // is set during contacts import depending on assigned roles
);
final var partner = HsOfficePartnerEntity.builder()
final var partner = HsOfficePartnerRealEntity.builder()
.partnerNumber(rec.getInteger("member_id"))
.details(HsOfficePartnerDetailsEntity.builder().build())
.partnerRel(partnerRel)

View File

@ -14,7 +14,7 @@ import org.junit.jupiter.api.extension.TestWatcher;
import org.opentest4j.AssertionFailedError;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import org.springframework.core.io.Resource;
import org.springframework.transaction.support.TransactionTemplate;
@ -76,7 +76,7 @@ public class CsvDataImport extends ContextBasedTest {
@Autowired
JpaAttempt jpaAttempt;
@MockBean
@MockitoBean
HttpServletRequest request;
static final LinkedHashSet<String> errors = new LinkedHashSet<>();

View File

@ -1,13 +1,13 @@
package net.hostsharing.hsadminng.hs.office.bankaccount;
import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.mapper.StandardMapper;
import net.hostsharing.hsadminng.mapper.StrictMapper;
import net.hostsharing.hsadminng.config.DisableSecurityConfig;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
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.test.context.bean.override.mockito.MockitoBean;
import org.springframework.context.annotation.Import;
import org.springframework.http.MediaType;
import org.springframework.test.context.ActiveProfiles;
@ -26,14 +26,14 @@ class HsOfficeBankAccountControllerRestTest {
@Autowired
MockMvc mockMvc;
@MockBean
@MockitoBean
Context contextMock;
@MockBean
@MockitoBean
@SuppressWarnings("unused") // not used in test, but in controller class
StandardMapper mapper;
StrictMapper mapper;
@MockBean
@MockitoBean
HsOfficeBankAccountRepository bankAccountRepo;
enum InvalidIbanTestCase {

View File

@ -11,7 +11,7 @@ import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
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.test.context.bean.override.mockito.MockitoBean;
import org.springframework.context.annotation.Import;
import jakarta.persistence.EntityManager;
@ -46,7 +46,7 @@ class HsOfficeBankAccountRepositoryIntegrationTest extends ContextBasedTestWithC
@Autowired
JpaAttempt jpaAttempt;
@MockBean
@MockitoBean
HttpServletRequest request;
@Nested

View File

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

View File

@ -11,7 +11,7 @@ import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
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.test.context.bean.override.mockito.MockitoBean;
import org.springframework.context.annotation.Import;
import jakarta.persistence.EntityManager;
@ -46,7 +46,7 @@ class HsOfficeContactRbacRepositoryIntegrationTest extends ContextBasedTestWithC
@Autowired
JpaAttempt jpaAttempt;
@MockBean
@MockitoBean
HttpServletRequest request;
@Nested

View File

@ -4,7 +4,7 @@ import net.hostsharing.hsadminng.config.JsonObjectMapperConfiguration;
import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.hs.office.membership.HsOfficeMembershipEntity;
import net.hostsharing.hsadminng.hs.office.membership.HsOfficeMembershipRepository;
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity;
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerRealEntity;
import net.hostsharing.hsadminng.mapper.StrictMapper;
import net.hostsharing.hsadminng.persistence.EntityManagerWrapper;
import net.hostsharing.hsadminng.rbac.test.JsonBuilder;
@ -17,10 +17,10 @@ import org.junit.jupiter.params.provider.EnumSource;
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.context.annotation.Import;
import org.springframework.http.MediaType;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
@ -67,7 +67,7 @@ class HsOfficeCoopAssetsTransactionControllerRestTest {
private static final String ORIGIN_MEMBER_NUMBER = "M-1111100";
public final HsOfficeMembershipEntity ORIGIN_TARGET_MEMBER_ENTITY = HsOfficeMembershipEntity.builder()
.uuid(ORIGIN_MEMBERSHIP_UUID)
.partner(HsOfficePartnerEntity.builder()
.partner(HsOfficePartnerRealEntity.builder()
.partnerNumber(partnerNumberOf(ORIGIN_MEMBER_NUMBER))
.build())
.memberNumberSuffix(suffixOf(ORIGIN_MEMBER_NUMBER))
@ -77,7 +77,7 @@ class HsOfficeCoopAssetsTransactionControllerRestTest {
private static final String AVAILABLE_TARGET_MEMBER_NUMBER = "M-1234500";
public final HsOfficeMembershipEntity AVAILABLE_MEMBER_ENTITY = HsOfficeMembershipEntity.builder()
.uuid(AVAILABLE_TARGET_MEMBERSHIP_UUID)
.partner(HsOfficePartnerEntity.builder()
.partner(HsOfficePartnerRealEntity.builder()
.partnerNumber(partnerNumberOf(AVAILABLE_TARGET_MEMBER_NUMBER))
.build())
.memberNumberSuffix(suffixOf(AVAILABLE_TARGET_MEMBER_NUMBER))
@ -499,20 +499,20 @@ class HsOfficeCoopAssetsTransactionControllerRestTest {
@Autowired
MockMvc mockMvc;
@MockBean
@MockitoBean
Context contextMock;
@Autowired
@SuppressWarnings("unused") // not used in test, but in controller class
StrictMapper mapper;
@MockBean
@MockitoBean
EntityManagerWrapper emw; // even if not used in test anymore, it's needed by base-class of StrictMapper
@MockBean
@MockitoBean
HsOfficeCoopAssetsTransactionRepository coopAssetsTransactionRepo;
@MockBean
@MockitoBean
HsOfficeMembershipRepository membershipRepo;
static final String INSERT_REQUEST_BODY_TEMPLATE = """

View File

@ -13,7 +13,7 @@ import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
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.test.context.bean.override.mockito.MockitoBean;
import org.springframework.context.annotation.Import;
import jakarta.persistence.EntityManager;
@ -51,7 +51,7 @@ class HsOfficeCoopAssetsTransactionRepositoryIntegrationTest extends ContextBase
@Autowired
JpaAttempt jpaAttempt;
@MockBean
@MockitoBean
HttpServletRequest request;
@Nested

View File

@ -170,7 +170,9 @@ class HsOfficeCoopSharesTransactionControllerAcceptanceTest extends ContextBased
final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000101).orElseThrow();
final var location = 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",
"transactionType": "SUBSCRIPTION",
@ -179,15 +181,29 @@ class HsOfficeCoopSharesTransactionControllerAcceptanceTest extends ContextBased
"reference": "temp ref A",
"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(201).contentType(ContentType.JSON).body("uuid", isUuidValid()).body("", lenientlyEquals("""
""".formatted(givenMembership.getUuid()))
.port(port)
.when()
.post("http://localhost/api/hs/office/coopsharestransactions")
.then()
.log().all()
.assertThat()
.statusCode(201)
.contentType(ContentType.JSON)
.body("uuid", isUuidValid())
.body("", lenientlyEquals("""
{
"membership.uuid": "%s",
"transactionType": "SUBSCRIPTION",
"shareCount": 8,
"valueDate": "2022-10-13",
"reference": "temp ref A",
"comment": "just some test coop shares transaction"
}
""")).header("Location", startsWith("http://localhost")).extract().header("Location"); // @formatter:on
""".formatted(givenMembership.getUuid())))
.header("Location", startsWith("http://localhost"))
.extract()
.header("Location"); // @formatter:on
// finally, the new coopSharesTransaction can be accessed under the generated UUID
final var newShareTxUuid = UUID.fromString(location.substring(location.lastIndexOf('/') + 1));
@ -197,7 +213,7 @@ class HsOfficeCoopSharesTransactionControllerAcceptanceTest extends ContextBased
@Test
void globalAdmin_canAddCoopSharesReversalTransaction() {
context.define("superuser-alex@hostsharing.net");
context.define("superuser-alex@hostsharing.net", "rbac.global#global:ADMIN");
final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000101).orElseThrow();
final var givenTransaction = jpaAttempt.transacted(() -> {
// TODO.impl: introduce something like transactedAsSuperuser / transactedAs("...", ...)
@ -214,46 +230,46 @@ class HsOfficeCoopSharesTransactionControllerAcceptanceTest extends ContextBased
final var location = RestAssured // @formatter:off
.given()
.header("current-subject", "superuser-alex@hostsharing.net")
.contentType(ContentType.JSON)
.body("""
{
"membership.uuid": "%s",
"transactionType": "REVERSAL",
"shareCount": %s,
"valueDate": "2022-10-30",
"reference": "test reversal ref",
"comment": "some coop shares reversal transaction",
"revertedShareTx.uuid": "%s"
}
""".formatted(
givenMembership.getUuid(),
-givenTransaction.getShareCount(),
givenTransaction.getUuid()))
.port(port)
.header("current-subject", "superuser-alex@hostsharing.net")
.contentType(ContentType.JSON)
.body("""
{
"membership.uuid": "%s",
"transactionType": "REVERSAL",
"shareCount": %s,
"valueDate": "2022-10-30",
"reference": "test reversal ref",
"comment": "some coop shares reversal transaction",
"revertedShareTx.uuid": "%s"
}
""".formatted(
givenMembership.getUuid(),
-givenTransaction.getShareCount(),
givenTransaction.getUuid()))
.port(port)
.when()
.post("http://localhost/api/hs/office/coopsharestransactions")
.post("http://localhost/api/hs/office/coopsharestransactions")
.then().log().all().assertThat()
.statusCode(201)
.contentType(ContentType.JSON)
.body("uuid", isUuidValid())
.body("", lenientlyEquals("""
{
"transactionType": "REVERSAL",
"shareCount": -13,
"valueDate": "2022-10-30",
"reference": "test reversal ref",
"comment": "some coop shares reversal transaction",
"revertedShareTx": {
"transactionType": "SUBSCRIPTION",
"shareCount": 13,
"valueDate": "2022-10-20",
"reference": "test ref"
}
.statusCode(201)
.contentType(ContentType.JSON)
.body("uuid", isUuidValid())
.body("", lenientlyEquals("""
{
"transactionType": "REVERSAL",
"shareCount": -13,
"valueDate": "2022-10-30",
"reference": "test reversal ref",
"comment": "some coop shares reversal transaction",
"revertedShareTx": {
"transactionType": "SUBSCRIPTION",
"shareCount": 13,
"valueDate": "2022-10-20",
"reference": "test ref"
}
"""))
.header("Location", startsWith("http://localhost"))
.extract().header("Location"); // @formatter:on
}
"""))
.header("Location", startsWith("http://localhost"))
.extract().header("Location"); // @formatter:on
// finally, the new coopAssetsTransaction can be accessed under the generated UUID
final var newShareTxUuid = UUID.fromString(
@ -269,22 +285,34 @@ class HsOfficeCoopSharesTransactionControllerAcceptanceTest extends ContextBased
final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000101).orElseThrow();
RestAssured // @formatter:off
.given().header("current-subject", "superuser-alex@hostsharing.net").contentType(ContentType.JSON).body("""
{
"membership.uuid": "%s",
"transactionType": "CANCELLATION",
"shareCount": -80,
"valueDate": "2022-10-13",
"reference": "temp ref X",
"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("""
.given()
.header("current-subject", "superuser-alex@hostsharing.net")
.contentType(ContentType.JSON)
.body("""
{
"statusCode": 400,
"statusPhrase": "Bad Request",
"message": "ERROR: [400] coop shares transaction would result in a negative number of shares"
}
""")); // @formatter:on
"membership.uuid": "%s",
"transactionType": "CANCELLATION",
"shareCount": -80,
"valueDate": "2022-10-13",
"reference": "temp ref X",
"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("""
{
"statusCode": 400,
"statusPhrase": "Bad Request",
"message": "ERROR: [400] coop shares transaction would result in a negative number of shares"
}
""")); // @formatter:on
}
}

View File

@ -1,14 +1,15 @@
package net.hostsharing.hsadminng.hs.office.coopshares;
import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.mapper.StandardMapper;
import net.hostsharing.hsadminng.hs.office.membership.HsOfficeMembershipRepository;
import net.hostsharing.hsadminng.mapper.StrictMapper;
import net.hostsharing.hsadminng.rbac.test.JsonBuilder;
import net.hostsharing.hsadminng.config.DisableSecurityConfig;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
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.test.context.bean.override.mockito.MockitoBean;
import org.springframework.context.annotation.Import;
import org.springframework.http.MediaType;
import org.springframework.test.context.ActiveProfiles;
@ -31,16 +32,19 @@ class HsOfficeCoopSharesTransactionControllerRestTest {
@Autowired
MockMvc mockMvc;
@MockBean
@MockitoBean
Context contextMock;
@MockBean
@MockitoBean
@SuppressWarnings("unused") // not used in test, but in controller class
StandardMapper mapper;
StrictMapper mapper;
@MockBean
@MockitoBean
HsOfficeCoopSharesTransactionRepository coopSharesTransactionRepo;
@MockitoBean
HsOfficeMembershipRepository membershipRepo;
static final String VALID_INSERT_REQUEST_BODY = """
{
"membership.uuid": "%s",

View File

@ -13,7 +13,7 @@ import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
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.test.context.bean.override.mockito.MockitoBean;
import org.springframework.context.annotation.Import;
import jakarta.persistence.EntityManager;
@ -50,7 +50,7 @@ class HsOfficeCoopSharesTransactionRepositoryIntegrationTest extends ContextBase
@Autowired
JpaAttempt jpaAttempt;
@MockBean
@MockitoBean
HttpServletRequest request;
@Nested

View File

@ -6,7 +6,7 @@ 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.HsOfficeContactRealRepository;
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerRepository;
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerRbacRepository;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRealRepository;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealEntity;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealRepository;
@ -28,6 +28,7 @@ import jakarta.persistence.PersistenceContext;
import java.util.UUID;
import static net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationType.DEBITOR;
import static net.hostsharing.hsadminng.rbac.role.RbacRoleType.ADMIN;
import static net.hostsharing.hsadminng.rbac.test.IsValidUuidMatcher.isUuidValid;
import static net.hostsharing.hsadminng.test.JsonMatcher.lenientlyEquals;
import static org.assertj.core.api.Assertions.assertThat;
@ -57,7 +58,7 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
HsOfficeDebitorRepository debitorRepo;
@Autowired
HsOfficePartnerRepository partnerRepo;
HsOfficePartnerRbacRepository partnerRepo;
@Autowired
HsOfficeContactRealRepository contactRealRepo;
@ -467,7 +468,7 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
.post("http://localhost/api/hs/office/debitors")
.then().log().all().assertThat()
.statusCode(400)
.body("message", is("ERROR: [400] Unable to find RealContact by debitorRel.contactUuid: 00000000-0000-0000-0000-000000000000"));
.body("message", is("ERROR: [400] Unable to find debitorRel.contact.uuid: 00000000-0000-0000-0000-000000000000"));
// @formatter:on
}
@ -495,7 +496,7 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
.post("http://localhost/api/hs/office/debitors")
.then().log().all().assertThat()
.statusCode(400)
.body("message", is("ERROR: [400] Unable to find RealRelation by debitorRelUuid: 00000000-0000-0000-0000-000000000000"));
.body("message", is("ERROR: [400] Unable to find debitorRel.uuid: 00000000-0000-0000-0000-000000000000"));
// @formatter:on
}
}
@ -695,16 +696,16 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
}
@Test
void theContactOwner_canNotPatchARelatedDebitor() {
void theContactAdmin_canNotPatchARelatedDebitor() {
context.define("superuser-alex@hostsharing.net");
final var givenDebitor = givenSomeTemporaryDebitor();
// @formatter:on
RestAssured // @formatter:off
.given()
.given()
.header("current-subject", "superuser-alex@hostsharing.net")
.header("assumed-roles", "hs_office.contact#tenthcontact:ADMIN")
.header("assumed-roles", givenDebitor.getDebitorRel().getContact().roleId(ADMIN) )
.contentType(ContentType.JSON)
.body("""
{
@ -712,9 +713,9 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
}
""")
.port(port)
.when()
.when()
.patch("http://localhost/api/hs/office/debitors/" + givenDebitor.getUuid())
.then().log().all().assertThat()
.then().log().all().assertThat()
.statusCode(403)
.body("message", containsString("ERROR: [403] Subject"))
.body("message", containsString("is not allowed to update hs_office.debitor uuid "));
@ -802,7 +803,7 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
.vatReverseCharge(false)
.build();
return debitorRepo.save(newDebitor).load();
return debitorRepo.save(newDebitor).reload(em);
}).assertSuccessful().returnedValue();
}

View File

@ -1,7 +1,7 @@
package net.hostsharing.hsadminng.hs.office.debitor;
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRealEntity;
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity;
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerRealEntity;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRealEntity;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonType;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealEntity;
@ -30,7 +30,7 @@ class HsOfficeDebitorEntityUnitTest {
.debitorNumberSuffix("67")
.debitorRel(givenDebitorRel)
.defaultPrefix("som")
.partner(HsOfficePartnerEntity.builder()
.partner(HsOfficePartnerRealEntity.builder()
.partnerNumber(12345)
.build())
.build();
@ -45,7 +45,7 @@ class HsOfficeDebitorEntityUnitTest {
final var given = HsOfficeDebitorEntity.builder()
.debitorRel(givenDebitorRel)
.debitorNumberSuffix("67")
.partner(HsOfficePartnerEntity.builder()
.partner(HsOfficePartnerRealEntity.builder()
.partnerNumber(12345)
.build())
.build();
@ -60,7 +60,7 @@ class HsOfficeDebitorEntityUnitTest {
final var given = HsOfficeDebitorEntity.builder()
.debitorRel(givenDebitorRel)
.debitorNumberSuffix("67")
.partner(HsOfficePartnerEntity.builder()
.partner(HsOfficePartnerRealEntity.builder()
.partnerNumber(12345)
.build())
.build();
@ -88,7 +88,7 @@ class HsOfficeDebitorEntityUnitTest {
final var given = HsOfficeDebitorEntity.builder()
.debitorRel(givenDebitorRel)
.debitorNumberSuffix("67")
.partner(HsOfficePartnerEntity.builder().build())
.partner(HsOfficePartnerRealEntity.builder().build())
.build();
final var result = given.getTaggedDebitorNumber();
@ -101,7 +101,7 @@ class HsOfficeDebitorEntityUnitTest {
final var given = HsOfficeDebitorEntity.builder()
.debitorRel(givenDebitorRel)
.debitorNumberSuffix(null)
.partner(HsOfficePartnerEntity.builder()
.partner(HsOfficePartnerRealEntity.builder()
.partnerNumber(12345)
.build())
.build();

View File

@ -3,7 +3,7 @@ package net.hostsharing.hsadminng.hs.office.debitor;
import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountRepository;
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRealRepository;
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerRepository;
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerRealRepository;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRealRepository;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelation;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealEntity;
@ -22,7 +22,7 @@ import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
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.test.context.bean.override.mockito.MockitoBean;
import org.springframework.context.annotation.Import;
import org.springframework.orm.jpa.JpaSystemException;
import org.springframework.transaction.annotation.Transactional;
@ -48,7 +48,7 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean
HsOfficeDebitorRepository debitorRepo;
@Autowired
HsOfficePartnerRepository partnerRepo;
HsOfficePartnerRealRepository partnerRepo;
@Autowired
HsOfficeContactRealRepository contactRealRepo;
@ -74,7 +74,7 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean
@Autowired
RbacGrantsDiagramService mermaidService;
@MockBean
@MockitoBean
HttpServletRequest request;
@Nested
class CreateDebitor {

View File

@ -5,7 +5,7 @@ import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRealEntity;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealEntity;
import static net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRealTestEntity.TEST_REAL_CONTACT;
import static net.hostsharing.hsadminng.hs.office.partner.TestHsOfficePartner.TEST_PARTNER;
import static net.hostsharing.hsadminng.hs.office.partner.HsOfficeTestRealPartner.TEST_PARTNER;
@UtilityClass
public class TestHsOfficeDebitor {

View File

@ -5,7 +5,7 @@ import io.restassured.RestAssured;
import io.restassured.http.ContentType;
import net.hostsharing.hsadminng.HsadminNgApplication;
import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerRepository;
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerRealRepository;
import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup;
import net.hostsharing.hsadminng.rbac.test.JpaAttempt;
import net.hostsharing.hsadminng.config.DisableSecurityConfig;
@ -54,7 +54,7 @@ class HsOfficeMembershipControllerAcceptanceTest extends ContextBasedTestWithCle
HsOfficeMembershipRepository membershipRepo;
@Autowired
HsOfficePartnerRepository partnerRepo;
HsOfficePartnerRealRepository partnerRepo;
@Autowired
JpaAttempt jpaAttempt;
@ -430,7 +430,6 @@ class HsOfficeMembershipControllerAcceptanceTest extends ContextBasedTestWithCle
context.define("superuser-alex@hostsharing.net");
final var givenPartner = partnerRepo.findPartnerByOptionalNameLike(partnerName).get(0);
final var newMembership = HsOfficeMembershipEntity.builder()
.uuid(UUID.randomUUID())
.partner(givenPartner)
.memberNumberSuffix(TEMP_MEMBER_NUMBER_SUFFIX)
.validity(Range.closedInfinite(LocalDate.parse("2022-11-01")))

View File

@ -2,8 +2,10 @@ package net.hostsharing.hsadminng.hs.office.membership;
import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.hs.office.coopassets.HsOfficeCoopAssetsTransactionRepository;
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity;
import net.hostsharing.hsadminng.mapper.StandardMapper;
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerRbacEntity;
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerRealEntity;
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerRealRepository;
import net.hostsharing.hsadminng.mapper.StrictMapper;
import net.hostsharing.hsadminng.persistence.EntityManagerWrapper;
import net.hostsharing.hsadminng.config.DisableSecurityConfig;
import org.junit.jupiter.api.Nested;
@ -12,7 +14,7 @@ import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
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.test.context.bean.override.mockito.MockitoBean;
import org.springframework.context.annotation.Import;
import org.springframework.http.MediaType;
import org.springframework.test.context.ActiveProfiles;
@ -34,11 +36,11 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@WebMvcTest(HsOfficeMembershipController.class)
@Import({StandardMapper.class, DisableSecurityConfig.class})
@Import({StrictMapper.class, DisableSecurityConfig.class})
@ActiveProfiles("test")
public class HsOfficeMembershipControllerRestTest {
private static final HsOfficePartnerEntity PARTNER_12345 = HsOfficePartnerEntity.builder()
private static final HsOfficePartnerRealEntity PARTNER_12345 = HsOfficePartnerRealEntity.builder()
.partnerNumber(12345)
.build();
public static final HsOfficeMembershipEntity MEMBERSHIP_1234501 = HsOfficeMembershipEntity.builder()
@ -69,16 +71,19 @@ public class HsOfficeMembershipControllerRestTest {
@Autowired
MockMvc mockMvc;
@MockBean
@MockitoBean
Context contextMock;
@MockBean
@MockitoBean
HsOfficeCoopAssetsTransactionRepository coopAssetsTransactionRepo;
@MockBean
@MockitoBean
HsOfficePartnerRealRepository partnerRepo;
@MockitoBean
HsOfficeMembershipRepository membershipRepo;
@MockBean
@MockitoBean
EntityManagerWrapper em;
@Nested
@ -252,7 +257,7 @@ public class HsOfficeMembershipControllerRestTest {
// given
final var givenPartnerUuid = UUID.randomUUID();
when(em.find(HsOfficePartnerEntity.class, givenPartnerUuid)).thenReturn(null);
when(em.find(HsOfficePartnerRbacEntity.class, givenPartnerUuid)).thenReturn(null);
// when
mockMvc.perform(MockMvcRequestBuilders
@ -275,7 +280,7 @@ public class HsOfficeMembershipControllerRestTest {
.andExpect(jsonPath("statusPhrase", is("Bad Request")))
.andExpect(jsonPath(
"message",
is("ERROR: [400] Unable to find Partner by partner.uuid: " + givenPartnerUuid)));
is("ERROR: [400] partnerUuid " + givenPartnerUuid + " not found")));
}
@ParameterizedTest

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.generated.api.v1.model.HsOfficeMembershipPatchResource;
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.rbac.test.PatchUnitTestBase;
import org.junit.jupiter.api.BeforeEach;
@ -17,7 +17,7 @@ import java.time.LocalDate;
import java.util.UUID;
import java.util.stream.Stream;
import static net.hostsharing.hsadminng.hs.office.partner.TestHsOfficePartner.TEST_PARTNER;
import static net.hostsharing.hsadminng.hs.office.partner.HsOfficeTestRealPartner.TEST_PARTNER;
import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
@ -40,7 +40,7 @@ class HsOfficeMembershipEntityPatcherUnitTest extends PatchUnitTestBase<
@Mock
private EntityManagerWrapper em;
private StandardMapper mapper = new StandardMapper(em);
private StrictMapper mapper = new StrictMapper(em);
@BeforeEach
void initMocks() {

View File

@ -1,7 +1,7 @@
package net.hostsharing.hsadminng.hs.office.membership;
import io.hypersistence.utils.hibernate.type.range.Range;
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity;
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerRealEntity;
import net.hostsharing.hsadminng.rbac.generator.RbacViewMermaidFlowchartGenerator;
import org.junit.jupiter.api.Test;
@ -10,7 +10,7 @@ import java.lang.reflect.InvocationTargetException;
import java.time.LocalDate;
import java.util.Arrays;
import static net.hostsharing.hsadminng.hs.office.partner.TestHsOfficePartner.TEST_PARTNER;
import static net.hostsharing.hsadminng.hs.office.partner.HsOfficeTestRealPartner.TEST_PARTNER;
import static org.assertj.core.api.Assertions.assertThat;
class HsOfficeMembershipEntityUnitTest {
@ -57,7 +57,7 @@ class HsOfficeMembershipEntityUnitTest {
@Test
void getMemberNumberWithoutPartnerNumberButWithSuffix() {
givenMembership.setPartner(HsOfficePartnerEntity.builder().build());
givenMembership.setPartner(HsOfficePartnerRealEntity.builder().build());
final var result = givenMembership.getMemberNumber();
assertThat(result).isEqualTo(null);
}

View File

@ -3,7 +3,7 @@ package net.hostsharing.hsadminng.hs.office.membership;
import io.hypersistence.utils.hibernate.type.range.Range;
import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorRepository;
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerRepository;
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerRealRepository;
import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup;
import net.hostsharing.hsadminng.rbac.grant.RawRbacGrantRepository;
import net.hostsharing.hsadminng.rbac.role.RawRbacRoleRepository;
@ -13,7 +13,7 @@ import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
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.test.context.bean.override.mockito.MockitoBean;
import org.springframework.context.annotation.Import;
import org.springframework.orm.jpa.JpaSystemException;
@ -37,7 +37,7 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCl
HsOfficeMembershipRepository membershipRepo;
@Autowired
HsOfficePartnerRepository partnerRepo;
HsOfficePartnerRealRepository partnerRepo;
@Autowired
HsOfficeDebitorRepository debitorRepo;
@ -54,7 +54,7 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCl
@Autowired
JpaAttempt jpaAttempt;
@MockBean
@MockitoBean
HttpServletRequest request;
@Nested

View File

@ -4,7 +4,7 @@ import io.hypersistence.utils.hibernate.type.range.Range;
import java.time.LocalDate;
import static net.hostsharing.hsadminng.hs.office.partner.TestHsOfficePartner.TEST_PARTNER;
import static net.hostsharing.hsadminng.hs.office.partner.HsOfficeTestRealPartner.TEST_PARTNER;
public class TestHsMembership {

View File

@ -42,7 +42,7 @@ class HsOfficePartnerControllerAcceptanceTest extends ContextBasedTestWithCleanu
private Integer port;
@Autowired
HsOfficePartnerRepository partnerRepo;
HsOfficePartnerRbacRepository partnerRepo;
@Autowired
HsOfficeRelationRealRepository relationRepo;
@ -541,12 +541,12 @@ class HsOfficePartnerControllerAcceptanceTest extends ContextBasedTestWithCleanu
return partnerRel;
}).assertSuccessful().returnedValue();
}
private HsOfficePartnerEntity givenSomeTemporaryPartnerBessler(final Integer partnerNumber) {
private HsOfficePartnerRbacEntity givenSomeTemporaryPartnerBessler(final Integer partnerNumber) {
return jpaAttempt.transacted(() -> {
context.define("superuser-alex@hostsharing.net");
final var partnerRel = em.merge(givenSomeTemporaryPartnerRel("Erben Bessler", "fourth contact"));
final var newPartner = HsOfficePartnerEntity.builder()
final var newPartner = HsOfficePartnerRbacEntity.builder()
.partnerRel(partnerRel)
.partnerNumber(partnerNumber)
.details(HsOfficePartnerDetailsEntity.builder()
@ -561,7 +561,7 @@ class HsOfficePartnerControllerAcceptanceTest extends ContextBasedTestWithCleanu
@AfterEach
void cleanup() {
cleanupAllNew(HsOfficePartnerEntity.class);
cleanupAllNew(HsOfficePartnerRbacEntity.class);
// TODO: should not be necessary anymore, once it's deleted via after delete trigger
cleanupAllNew(HsOfficeRelationRealEntity.class);

View File

@ -5,7 +5,7 @@ import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRbacEntity;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRealEntity;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealEntity;
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.EntityManagerWrapper;
import net.hostsharing.hsadminng.config.DisableSecurityConfig;
import org.junit.jupiter.api.BeforeEach;
@ -14,7 +14,7 @@ import org.junit.jupiter.api.Test;
import org.mockito.Mock;
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.test.context.bean.override.mockito.MockitoBean;
import org.springframework.context.annotation.Import;
import org.springframework.http.MediaType;
import org.springframework.test.context.ActiveProfiles;
@ -38,7 +38,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@WebMvcTest(HsOfficePartnerController.class)
@Import({StandardMapper.class, DisableSecurityConfig.class})
@Import({StrictMapper.class, DisableSecurityConfig.class})
@ActiveProfiles("test")
class HsOfficePartnerControllerRestTest {
@ -50,19 +50,19 @@ class HsOfficePartnerControllerRestTest {
@Autowired
MockMvc mockMvc;
@MockBean
@MockitoBean
Context contextMock;
@MockBean
HsOfficePartnerRepository partnerRepo;
@MockitoBean
HsOfficePartnerRbacRepository partnerRepo;
@MockBean
@MockitoBean
HsOfficeRelationRealRepository relationRepo;
@MockBean
@MockitoBean
EntityManagerWrapper em;
@MockBean
@MockitoBean
EntityManagerFactory emf;
@Mock
@ -75,7 +75,7 @@ class HsOfficePartnerControllerRestTest {
HsOfficeContactRbacEntity contactMock;
@Mock
HsOfficePartnerEntity partnerMock;
HsOfficePartnerRbacEntity partnerMock;
@BeforeEach
void init() {
@ -174,7 +174,7 @@ class HsOfficePartnerControllerRestTest {
@Test
void respondWithPartner_ifPartnerNumberIsAvailable() throws Exception {
// given
when(partnerRepo.findPartnerByPartnerNumber(12345)).thenReturn(Optional.of(HsOfficePartnerEntity.builder()
when(partnerRepo.findPartnerByPartnerNumber(12345)).thenReturn(Optional.of(HsOfficePartnerRbacEntity.builder()
.partnerNumber(12345)
.build()));

View File

@ -24,7 +24,7 @@ import static org.mockito.Mockito.lenient;
@ExtendWith(MockitoExtension.class)
class HsOfficePartnerEntityPatcherUnitTest extends PatchUnitTestBase<
HsOfficePartnerPatchResource,
HsOfficePartnerEntity
HsOfficePartnerRbacEntity
> {
private static final UUID INITIAL_PARTNER_UUID = UUID.randomUUID();
@ -53,8 +53,8 @@ class HsOfficePartnerEntityPatcherUnitTest extends PatchUnitTestBase<
}
@Override
protected HsOfficePartnerEntity newInitialEntity() {
final var entity = HsOfficePartnerEntity.builder()
protected HsOfficePartnerRbacEntity newInitialEntity() {
final var entity = HsOfficePartnerRbacEntity.builder()
.uuid(INITIAL_PARTNER_UUID)
.partnerNumber(12345)
.partnerRel(HsOfficeRelationRealEntity.builder()
@ -72,7 +72,7 @@ class HsOfficePartnerEntityPatcherUnitTest extends PatchUnitTestBase<
}
@Override
protected HsOfficePartnerEntityPatcher createPatcher(final HsOfficePartnerEntity partner) {
protected HsOfficePartnerEntityPatcher createPatcher(final HsOfficePartnerRbacEntity partner) {
return new HsOfficePartnerEntityPatcher(em, partner);
}
@ -83,7 +83,7 @@ class HsOfficePartnerEntityPatcherUnitTest extends PatchUnitTestBase<
"partnerRel",
HsOfficePartnerPatchResource::setPartnerRelUuid,
PATCHED_PARTNER_ROLE_UUID,
HsOfficePartnerEntity::setPartnerRel,
HsOfficePartnerRbacEntity::setPartnerRel,
newPartnerRel(PATCHED_PARTNER_ROLE_UUID))
.notNullable()
);

View File

@ -12,7 +12,7 @@ import static org.assertj.core.api.Assertions.assertThat;
class HsOfficePartnerEntityUnitTest {
private final HsOfficePartnerEntity givenPartner = HsOfficePartnerEntity.builder()
private final HsOfficePartnerRbacEntity givenPartner = HsOfficePartnerRbacEntity.builder()
.partnerNumber(12345)
.partnerRel(HsOfficeRelationRealEntity.builder()
.anchor(HsOfficePersonRealEntity.builder()
@ -42,7 +42,7 @@ class HsOfficePartnerEntityUnitTest {
@Test
void definesRbac() {
final var rbacFlowchart = new RbacViewMermaidFlowchartGenerator(HsOfficePartnerEntity.rbac()).toString();
final var rbacFlowchart = new RbacViewMermaidFlowchartGenerator(HsOfficePartnerRbacEntity.rbac()).toString();
assertThat(rbacFlowchart).isEqualTo("""
%%{init:{'flowchart':{'htmlLabels':false}}}%%
flowchart TB

View File

@ -6,19 +6,19 @@ import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRealRepository;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealEntity;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealRepository;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationType;
import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup;
import net.hostsharing.hsadminng.rbac.grant.RawRbacGrantRepository;
import net.hostsharing.hsadminng.rbac.role.RawRbacObjectRepository;
import net.hostsharing.hsadminng.rbac.role.RawRbacRoleRepository;
import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup;
import net.hostsharing.hsadminng.rbac.test.JpaAttempt;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
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.Import;
import org.springframework.orm.jpa.JpaSystemException;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
@ -27,20 +27,21 @@ import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import static net.hostsharing.hsadminng.mapper.Array.from;
import static net.hostsharing.hsadminng.rbac.grant.RawRbacGrantEntity.distinctGrantDisplaysOf;
import static net.hostsharing.hsadminng.rbac.role.RawRbacObjectEntity.objectDisplaysOf;
import static net.hostsharing.hsadminng.rbac.role.RawRbacRoleEntity.distinctRoleNamesOf;
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.AGENT;
import static net.hostsharing.hsadminng.rbac.test.JpaAttempt.attempt;
import static org.assertj.core.api.Assertions.assertThat;
@DataJpaTest
@Import( { Context.class, JpaAttempt.class })
class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTestWithCleanup {
@Import({ Context.class, JpaAttempt.class })
class HsOfficePartnerRbacRepositoryIntegrationTest extends ContextBasedTestWithCleanup {
@Autowired
HsOfficePartnerRepository partnerRepo;
HsOfficePartnerRbacRepository partnerRepo;
@Autowired
HsOfficeRelationRealRepository relationRepo;
@ -66,7 +67,7 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTestWithClean
@Autowired
JpaAttempt jpaAttempt;
@MockBean
@MockitoBean
HttpServletRequest request;
@Nested
@ -80,18 +81,19 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTestWithClean
final var partnerRel = givenSomeTemporaryHostsharingPartnerRel("Winkler", "first contact");
// when
final var result = attempt(em, () -> {
final var newPartner = HsOfficePartnerEntity.builder()
.partnerNumber(20031)
.partnerRel(partnerRel)
.details(HsOfficePartnerDetailsEntity.builder().build())
.build();
return partnerRepo.save(newPartner);
});
final var result = attempt(
em, () -> {
final var newPartner = HsOfficePartnerRbacEntity.builder()
.partnerNumber(20031)
.partnerRel(partnerRel)
.details(HsOfficePartnerDetailsEntity.builder().build())
.build();
return partnerRepo.save(newPartner);
});
// then
result.assertSuccessful();
assertThat(result.returnedValue()).isNotNull().extracting(HsOfficePartnerEntity::getUuid).isNotNull();
assertThat(result.returnedValue()).isNotNull().extracting(HsOfficePartnerRbacEntity::getUuid).isNotNull();
assertThatPartnerIsPersisted(result.returnedValue());
assertThat(partnerRepo.count()).isEqualTo(count + 1);
}
@ -108,26 +110,27 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTestWithClean
.toList();
// when
attempt(em, () -> {
final var givenPartnerPerson = personRepo.findPersonByOptionalNameLike("Erben Bessler").get(0);
final var givenContact = contactrealRepo.findContactByOptionalCaptionLike("fourth contact").get(0);
final var givenMandantPerson = personRepo.findPersonByOptionalNameLike("Hostsharing eG").get(0);
attempt(
em, () -> {
final var givenPartnerPerson = personRepo.findPersonByOptionalNameLike("Erben Bessler").get(0);
final var givenContact = contactrealRepo.findContactByOptionalCaptionLike("fourth contact").get(0);
final var givenMandantPerson = personRepo.findPersonByOptionalNameLike("Hostsharing eG").get(0);
final var newRelation = HsOfficeRelationRealEntity.builder()
.holder(givenPartnerPerson)
.type(HsOfficeRelationType.PARTNER)
.anchor(givenMandantPerson)
.contact(givenContact)
.build();
relationRepo.save(newRelation);
final var newRelation = HsOfficeRelationRealEntity.builder()
.holder(givenPartnerPerson)
.type(HsOfficeRelationType.PARTNER)
.anchor(givenMandantPerson)
.contact(givenContact)
.build();
relationRepo.save(newRelation);
final var newPartner = HsOfficePartnerEntity.builder()
.partnerNumber(20032)
.partnerRel(newRelation)
.details(HsOfficePartnerDetailsEntity.builder().build())
.build();
return partnerRepo.save(newPartner);
}).assertSuccessful();
final var newPartner = HsOfficePartnerRbacEntity.builder()
.partnerNumber(20032)
.partnerRel(newRelation)
.details(HsOfficePartnerDetailsEntity.builder().build())
.build();
return partnerRepo.save(newPartner);
}).assertSuccessful();
// then
assertThat(distinctRoleNamesOf(rawRoleRepo.findAll())).containsExactlyInAnyOrder(from(
@ -179,7 +182,7 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTestWithClean
null)));
}
private void assertThatPartnerIsPersisted(final HsOfficePartnerEntity saved) {
private void assertThatPartnerIsPersisted(final HsOfficePartnerRbacEntity saved) {
final var found = partnerRepo.findByUuid(saved.getUuid());
assertThat(found).isNotEmpty().get().extracting(Object::toString).isEqualTo(saved.toString());
}
@ -207,15 +210,32 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTestWithClean
}
@Test
public void normalUser_canViewOnlyRelatedPartners() {
public void partnerAgent_canViewOnlyRelatedPartnersWithDetails() {
// 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)");
exactlyThesePartnersAreReturned(result,
"partner(P-10001: LP First GmbH, first contact)+(partnerDetails(Hamburg, RegNo123456789))");
}
@Test
public void partnerTenant_canViewRelatedPartnersButWithoutDetails() {
// given:
context(
"person-FirstGmbH@example.com",
"hs_office.relation#HostsharingeG-with-PARTNER-FirstGmbH:TENANT");
// when:
final var result = partnerRepo.findPartnerByOptionalNameLike(null);
// then:
exactlyThesePartnersAreReturned(result, "partner(P-10001: LP First GmbH, first contact)+null");
}
}
@ -223,7 +243,7 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTestWithClean
class FindByNameLike {
@Test
public void globalAdmin_withoutAssumedRole_canViewAllPartners() {
public void globalAdmin_withoutAssumedRole_canViewAllPartnersWithDetails() {
// given
context("superuser-alex@hostsharing.net");
@ -231,7 +251,8 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTestWithClean
final var result = partnerRepo.findPartnerByOptionalNameLike("third contact");
// then
exactlyThesePartnersAreReturned(result, "partner(P-10003: IF Third OHG, third contact)");
exactlyThesePartnersAreReturned(result,
"partner(P-10003: IF Third OHG, third contact)+(partnerDetails(Hamburg, RegNo123456789))");
}
}
@ -289,19 +310,20 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTestWithClean
}
@Test
public void partnerRelationAgent_canUpdateRelatedPartner() {
public void partnerRelationAgent_canUpdateRelatedPartnerDetails() {
// given
context("superuser-alex@hostsharing.net");
final var givenPartner = givenSomeTemporaryHostsharingPartner(20037, "Erben Bessler", "ninth");
assertThatPartnerIsVisibleForUserWithRole(
givenPartner,
"hs_office.person#ErbenBesslerMelBessler:ADMIN");
givenPartner.getPartnerRel().roleId(AGENT));
assertThatPartnerActuallyInDatabase(givenPartner);
// when
final var result = jpaAttempt.transacted(() -> {
context("superuser-alex@hostsharing.net",
"hs_office.person#ErbenBesslerMelBessler:ADMIN");
context(
"superuser-alex@hostsharing.net",
givenPartner.getPartnerRel().roleId(AGENT));
givenPartner.getDetails().setBirthName("new birthname");
return partnerRepo.save(givenPartner);
});
@ -310,37 +332,17 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTestWithClean
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 HsOfficePartnerRbacEntity saved) {
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(HsOfficePartnerRbacEntity::toString)
.isEqualTo(saved.toString());
}
private void assertThatPartnerIsVisibleForUserWithRole(
final HsOfficePartnerEntity entity,
final HsOfficePartnerRbacEntity entity,
final String assumedRoles) {
jpaAttempt.transacted(() -> {
context("superuser-alex@hostsharing.net", assumedRoles);
@ -349,7 +351,7 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTestWithClean
}
private void assertThatPartnerIsNotVisibleForUserWithRole(
final HsOfficePartnerEntity entity,
final HsOfficePartnerRbacEntity entity,
final String assumedRoles) {
jpaAttempt.transacted(() -> {
context("superuser-alex@hostsharing.net", assumedRoles);
@ -437,7 +439,7 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTestWithClean
select currentTask, targetTable, targetOp, targetdelta->>'partnernumber'
from base.tx_journal_v
where targettable = 'hs_office.partner';
""");
""");
// when
@SuppressWarnings("unchecked") final List<Object[]> customerLogEntries = query.getResultList();
@ -451,19 +453,22 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTestWithClean
"[creating partner test-data , hs_office.partner, INSERT, 10010]");
}
private HsOfficePartnerEntity givenSomeTemporaryHostsharingPartner(
private HsOfficePartnerRbacEntity givenSomeTemporaryHostsharingPartner(
final Integer partnerNumber, final String person, final String contact) {
return jpaAttempt.transacted(() -> {
context("superuser-alex@hostsharing.net");
final var partnerRel = givenSomeTemporaryHostsharingPartnerRel(person, contact);
final var newPartner = HsOfficePartnerEntity.builder()
final var newPartner = HsOfficePartnerRbacEntity.builder()
.partnerNumber(partnerNumber)
.partnerRel(partnerRel)
.details(HsOfficePartnerDetailsEntity.builder().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();
}
@ -482,21 +487,23 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTestWithClean
return partnerRel;
}
void exactlyThesePartnersAreReturned(final List<HsOfficePartnerEntity> actualResult, final String... partnerNames) {
void exactlyThesePartnersAreReturned(final List<HsOfficePartnerRbacEntity> actualResult, final String... partnerNames) {
assertThat(actualResult)
.extracting(partnerEntity -> partnerEntity.toString())
.extracting(partner ->
partner.toString() + "+" +
(partner.getDetails() != null ? ("(" + partner.getDetails() + ")") : "null"))
.containsExactlyInAnyOrder(partnerNames);
}
void allThesePartnersAreReturned(final List<HsOfficePartnerEntity> actualResult, final String... partnerNames) {
void allThesePartnersAreReturned(final List<HsOfficePartnerRbacEntity> actualResult, final String... partnerNames) {
assertThat(actualResult)
.extracting(partnerEntity -> partnerEntity.toString())
.extracting(HsOfficePartnerRbacEntity::toString)
.contains(partnerNames);
}
@AfterEach
void cleanup() {
cleanupAllNew(HsOfficePartnerEntity.class);
cleanupAllNew(HsOfficePartnerRbacEntity.class);
}
private String[] distinct(final String[] strings) {

View File

@ -7,12 +7,12 @@ import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationType;
import static net.hostsharing.hsadminng.hs.office.person.HsOfficePersonType.LEGAL_PERSON;
public class TestHsOfficePartner {
public class HsOfficeTestRealPartner {
public static final HsOfficePartnerEntity TEST_PARTNER = hsOfficePartnerWithLegalPerson("Test Ltd.");
public static final HsOfficePartnerRealEntity TEST_PARTNER = hsOfficePartnerWithLegalPerson("Test Ltd.");
static public HsOfficePartnerEntity hsOfficePartnerWithLegalPerson(final String tradeName) {
return HsOfficePartnerEntity.builder()
static public HsOfficePartnerRealEntity hsOfficePartnerWithLegalPerson(final String tradeName) {
return HsOfficePartnerRealEntity.builder()
.partnerNumber(10001)
.partnerRel(
HsOfficeRelationRealEntity.builder()

View File

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

View File

@ -11,7 +11,7 @@ import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
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.test.context.bean.override.mockito.MockitoBean;
import org.springframework.context.annotation.Import;
import jakarta.persistence.EntityManager;
@ -46,7 +46,7 @@ class HsOfficePersonRbacRepositoryIntegrationTest extends ContextBasedTestWithCl
@Autowired
JpaAttempt jpaAttempt;
@MockBean
@MockitoBean
HttpServletRequest request;
@Nested

View File

@ -10,7 +10,7 @@ import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
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.test.context.bean.override.mockito.MockitoBean;
import org.springframework.context.annotation.Import;
import jakarta.persistence.EntityManager;
@ -44,7 +44,7 @@ class HsOfficePersonRealRepositoryIntegrationTest extends ContextBasedTestWithCl
@Autowired
JpaAttempt jpaAttempt;
@MockBean
@MockitoBean
HttpServletRequest request;
@Autowired
private HsOfficePersonRealRepository hsOfficePersonRealRepository;

View File

@ -9,7 +9,7 @@ import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
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.test.context.bean.override.mockito.MockitoBean;
import org.springframework.context.annotation.Import;
import jakarta.persistence.EntityManager;
@ -35,7 +35,7 @@ class HsOfficeRealRelationRepositoryIntegrationTest extends ContextBasedTestWith
@PersistenceContext
EntityManager em;
@MockBean
@MockitoBean
HttpServletRequest request;
@Nested

View File

@ -13,7 +13,7 @@ import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
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.test.context.bean.override.mockito.MockitoBean;
import org.springframework.context.annotation.Import;
import org.springframework.orm.jpa.JpaSystemException;
@ -58,7 +58,7 @@ class HsOfficeRelationRepositoryIntegrationTest extends ContextBasedTestWithClea
@Autowired
JpaAttempt jpaAttempt;
@MockBean
@MockitoBean
HttpServletRequest request;
@Nested

View File

@ -180,10 +180,9 @@ class HsOfficeSepaMandateControllerAcceptanceTest extends ContextBasedTestWithCl
void globalAdmin_canNotPostNewSepaMandateWhenDebitorUuidIsMissing() {
context.define("superuser-alex@hostsharing.net");
final var givenDebitor = debitorRepo.findDebitorsByOptionalNameLike("Third").get(0);
final var givenBankAccount = bankAccountRepo.findByIbanOrderByIbanAsc("DE02200505501015871393").get(0);
final var location = RestAssured // @formatter:off
RestAssured // @formatter:off
.given()
.header("current-subject", "superuser-alex@hostsharing.net")
.contentType(ContentType.JSON)
@ -227,12 +226,12 @@ class HsOfficeSepaMandateControllerAcceptanceTest extends ContextBasedTestWithCl
.post("http://localhost/api/hs/office/sepamandates")
.then().log().all().assertThat()
.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
}
@Test
void globalAdmin_canNotPostNewSepaMandate_ifPersonDoesNotExist() {
void globalAdmin_canNotPostNewSepaMandate_ifDebitorDoesNotExist() {
context.define("superuser-alex@hostsharing.net");
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")
.then().log().all().assertThat()
.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
}
}
@ -529,7 +528,6 @@ class HsOfficeSepaMandateControllerAcceptanceTest extends ContextBasedTestWithCl
.orElse(givenDebitor.getPartner().getPartnerRel().getHolder().getFamilyName());
final var givenBankAccount = bankAccountRepo.findByOptionalHolderLike(bankAccountHolder).get(0);
final var newSepaMandate = HsOfficeSepaMandateEntity.builder()
.uuid(UUID.randomUUID())
.debitor(givenDebitor)
.bankAccount(givenBankAccount)
.reference("temp ref CAT Z")

View File

@ -13,7 +13,7 @@ import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
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.test.context.bean.override.mockito.MockitoBean;
import org.springframework.context.annotation.Import;
import org.springframework.orm.jpa.JpaSystemException;
@ -55,7 +55,7 @@ class HsOfficeSepaMandateRepositoryIntegrationTest extends ContextBasedTestWithC
@PersistenceContext
EntityManager em;
@MockBean
@MockitoBean
HttpServletRequest request;
@Nested

Some files were not shown because too many files have changed in this diff Show More