Merge remote-tracking branch 'origin/master' into db-migration

This commit is contained in:
Michael Hoennig 2024-01-05 13:46:24 +01:00
commit 9391877a46
60 changed files with 920 additions and 179 deletions

View File

@ -44,7 +44,7 @@ alias podman-stop='systemctl --user disable --now podman.socket && systemctl --u
alias podman-use='export DOCKER_HOST="unix:///run/user/$UID/podman/podman.sock"; export TESTCONTAINERS_RYUK_DISABLED=true' alias podman-use='export DOCKER_HOST="unix:///run/user/$UID/podman/podman.sock"; export TESTCONTAINERS_RYUK_DISABLED=true'
alias gw=gradleWrapper alias gw=gradleWrapper
alias pg-sql-run='docker run --name hsadmin-ng-postgres -e POSTGRES_PASSWORD=password -p 5432:5432 -d postgres:13.7-bullseye' alias pg-sql-run='docker run --name hsadmin-ng-postgres -e POSTGRES_PASSWORD=password -p 5432:5432 -d postgres:15.5-bookworm'
alias pg-sql-stop='docker stop hsadmin-ng-postgres' alias pg-sql-stop='docker stop hsadmin-ng-postgres'
alias pg-sql-start='docker container start hsadmin-ng-postgres' alias pg-sql-start='docker container start hsadmin-ng-postgres'
alias pg-sql-remove='docker rm hsadmin-ng-postgres' alias pg-sql-remove='docker rm hsadmin-ng-postgres'

View File

@ -20,6 +20,7 @@ For architecture consider the files in the `doc` and `adr` folder.
- [Directory and Package Structure](#directory-and-package-structure) - [Directory and Package Structure](#directory-and-package-structure)
- [General Directory Structure](#general-directory-structure) - [General Directory Structure](#general-directory-structure)
- [Source Code Package Structure](#source-code-package-structure) - [Source Code Package Structure](#source-code-package-structure)
- [Run Tests from Command Line](#run-tests-from-command-line)
- [Spotless Code Formatting](#spotless-code-formatting) - [Spotless Code Formatting](#spotless-code-formatting)
- [JaCoCo Test Code Coverage Check](#jacoco-test-code-coverage-check) - [JaCoCo Test Code Coverage Check](#jacoco-test-code-coverage-check)
- [PiTest Mutation Testing](#pitest-mutation-testing) - [PiTest Mutation Testing](#pitest-mutation-testing)
@ -39,6 +40,7 @@ For architecture consider the files in the `doc` and `adr` folder.
- [How to Use a Persistent Database for Integration Tests?](#how-to-use-a-persistent-database-for-integration-tests?) - [How to Use a Persistent Database for Integration Tests?](#how-to-use-a-persistent-database-for-integration-tests?)
- [How to Amend Liquibase SQL Changesets?](#how-to-amend-liquibase-sql-changesets?) - [How to Amend Liquibase SQL Changesets?](#how-to-amend-liquibase-sql-changesets?)
- [How to Re-Generate Spring-Controller-Interfaces from OpenAPI specs?](#how-to-re-generate-spring-controller-interfaces-from-openapi-specs?) - [How to Re-Generate Spring-Controller-Interfaces from OpenAPI specs?](#how-to-re-generate-spring-controller-interfaces-from-openapi-specs?)
- [How to Generate Database Table Diagrams?](#how-to-generate-database-table-diagrams?)
- [Further Documentation](#further-documentation) - [Further Documentation](#further-documentation)
<!-- generated TOC end. --> <!-- generated TOC end. -->
@ -50,11 +52,10 @@ Everything is tested on _Ubuntu Linux 22.04_ and _MacOS Monterey (12.4)_.
To be able to build and run the Java Spring Boot application, you need the following tools: To be able to build and run the Java Spring Boot application, you need the following tools:
- Docker 20.x (on MacOS you also need *Docker Desktop* or similar) - Docker 20.x (on MacOS you also need *Docker Desktop* or similar)
- PostgreSQL Server 13.7-bullseye - PostgreSQL Server 15.5-bookworm
(see instructions below to install and run in Docker) (see instructions below to install and run in Docker)
- Java JDK at least recent enough to run Gradle - Java JDK at least recent enough to run Gradle
(JDK 17.x will be automatically installed by Gradle toolchain support) (JDK 17.x will be automatically installed by Gradle toolchain support)
- Gradle in some not too outdated version (7.4 will be installed via wrapper)
You also might need an IDE (e.g. *IntelliJ IDEA* or *Eclipse* or *VS Code* with *[STS](https://spring.io/tools)* and a GUI Frontend for *PostgreSQL* like *Postbird*. You also might need an IDE (e.g. *IntelliJ IDEA* or *Eclipse* or *VS Code* with *[STS](https://spring.io/tools)* and a GUI Frontend for *PostgreSQL* like *Postbird*.
@ -62,12 +63,16 @@ If you have at least Docker, the Java JDK and Gradle installed in appropriate ve
cd your-hsadmin-ng-directory cd your-hsadmin-ng-directory
gradle wrapper # downloads the configured Gradle version into the project source .aliases # creates some comfortable bash aliases, e.g. 'gw'='./gradlew'
source .aliases # creates some comforable bash aliases, e.g. 'gw'='./gradlew' gw # initially downloads the configured Gradle version into the project
gw test # compiles and runs unit- and integration-tests gw test # compiles and runs unit- and integration-tests
# if the container has not been built yet, run this:
pg-sql-run # downloads + runs PostgreSQL in a Docker container on localhost:5432 pg-sql-run # downloads + runs PostgreSQL in a Docker container on localhost:5432
# if the container has been built already, run this:
pg-sql-start
gw bootRun # compiles and runs the application on localhost:8080 gw bootRun # compiles and runs the application on localhost:8080
# the following command should reply with "pong": # the following command should reply with "pong":
@ -130,14 +135,14 @@ But the easiest way to run PostgreSQL is via Docker.
Initially, pull an image compatible to current PostgreSQL version of Hostsharing: Initially, pull an image compatible to current PostgreSQL version of Hostsharing:
docker pull postgres:13.7-bullseye docker pull postgres:15.5-bookworm
<big>**&#9888;**</big> <big>**&#9888;**</big>
If we switch the version, please also amend the documentation as well as the aliases file. Thanks! If we switch the version, please also amend the documentation as well as the aliases file. Thanks!
Create and run a container with the given PostgreSQL version: Create and run a container with the given PostgreSQL version:
docker run --name hsadmin-ng-postgres -e POSTGRES_PASSWORD=password -p 5432:5432 -d postgres:13.7-bullseye docker run --name hsadmin-ng-postgres -e POSTGRES_PASSWORD=password -p 5432:5432 -d postgres:15.5-bookworm
# or via alias: # or via alias:
pg-sql-run pg-sql-run
@ -196,7 +201,7 @@ To generate the TOC (Table of Contents), a little bash script from a
Given this is in PATH as `md-toc`, use: Given this is in PATH as `md-toc`, use:
```shell ```shell
md-toc <README.md 2 4 | sed -e 's/^ //g' md-toc <README.md 2 4 | cut -c5-'
``` ```
To render the Markdown files, especially to watch embedded PlantUML diagrams, you can use one of the following methods: To render the Markdown files, especially to watch embedded PlantUML diagrams, you can use one of the following methods:
@ -230,12 +235,19 @@ sudo apt install graphviz
##### Ubuntu Linux command line ##### Ubuntu Linux command line
```sh 1. Install Pandoc with some extra libraries:
```shell
sudo apt-get install pandoc texlive-latex-base texlive-fonts-recommended texlive-extra-utils texlive-latex-extra pandoc-plantuml-filter sudo apt-get install pandoc texlive-latex-base texlive-fonts-recommended texlive-extra-utils texlive-latex-extra pandoc-plantuml-filter
``` ```
```sh 2. Install mermaid-filter, e.g. this way:
pandoc --filter pandoc-plantuml rbac.md -o rbac.pdf ```shell
npm install -g mermaid-filter
```
3. Run Pandoc to generate a PDF from a Markdown file with PlantUML and Mermaid diagrams:
```shell
pandoc --filter mermaid-filter --filter pandoc-plantuml rbac.md -o rbac.pdf
``` ```
##### for other IDEs / operating systems ##### for other IDEs / operating systems
@ -244,7 +256,7 @@ If you have figured out how it works, please add instructions above this section
#### Render Markdown Embedded Mermaid Diagrams #### Render Markdown Embedded Mermaid Diagrams
The source of RBAC role diagrams are much easier to read with Mermaid than with PlantUML or GraphViz, that's the main reason Mermaid ist used too. The source of RBAC role diagrams are much easier to read with Mermaid than with PlantUML or GraphViz, that's also the main reason Mermaid is used.
Can you see the following diagram right in your IDE? Can you see the following diagram right in your IDE?
I mean a real graphic diagram, not just some markup code. I mean a real graphic diagram, not just some markup code.
@ -268,8 +280,11 @@ If not, you need to install some tooling.
##### for IntelliJ IDEA (or derived products) ##### for IntelliJ IDEA (or derived products)
You just need the bundled Markdown plugin enabled and install and activate the Mermaid plugin in its [settings](jetbrains://idea/settings?name=Languages+%26+Frameworks--Markdown). 1. Activate the bundled Jebrains Markdown PlantUML Extension via
[File | Settings | Languages & Frameworks | Markdown](jetbrains://idea/settings?name=Languages+%26+Frameworks--Markdown)
2. Install the Jetbrains Mermaid plugin: https://plugins.jetbrains.com/plugin/20146-mermaid, it also works embedded in Markdown files.
Now the above diagram should be rendered.
##### for other IDEs / command-line / operating systems ##### for other IDEs / command-line / operating systems
@ -279,13 +294,23 @@ If you have figured out how it works, please add instructions above this section
#### IntelliJ IDEA #### IntelliJ IDEA
##### Build Settings
Go to [Gradle Settings}(jetbrains://idea/settings?name=Build%2C+Execution%2C+Deployment--Build+Tools--Gradle) and select "Build and run using" and "Run tests using" both to "gradle". Go to [Gradle Settings}(jetbrains://idea/settings?name=Build%2C+Execution%2C+Deployment--Build+Tools--Gradle) and select "Build and run using" and "Run tests using" both to "gradle".
Otherwise, settings from `build.gradle`, like compiler arguments, are not applied when compiling through *IntelliJ IDEA*. Otherwise, settings from `build.gradle`, like compiler arguments, are not applied when compiling through *IntelliJ IDEA*.
##### Annotation Processor
Go to [Annotations Processors](jetbrains://idea/settings?name=Build%2C+Execution%2C+Deployment--Compiler--Annotation+Processors) and activate annotation processing. Go to [Annotations Processors](jetbrains://idea/settings?name=Build%2C+Execution%2C+Deployment--Compiler--Annotation+Processors) and activate annotation processing.
Otherwise, *IntelliJ IDEA* can't see *Lombok* generated classes Otherwise, *IntelliJ IDEA* can't see *Lombok* generated classes
and will show false errors (missing identifiers). and will show false errors (missing identifiers).
##### Suggested Plugins
- [Jetbrains Mermaid Integration](https://plugins.jetbrains.com/plugin/20146-mermaid)
- [Vojtěch Krása PlantUML Integration](https://plugins.jetbrains.com/plugin/7017-plantuml-integration)
### Other Tools ### Other Tools
**jq**: a JSON formatter. **jq**: a JSON formatter.
@ -418,6 +443,21 @@ Underneath of rbac and hs, the structure is business oriented, NOT technical / l
Some of these rules are checked with *ArchUnit* unit tests. Some of these rules are checked with *ArchUnit* unit tests.
### Run Tests from Command Line
Run all tests which have not yet been passed with the current source code:
```shell
gw test
```
Force running all tests:
```shell
gw cleanTest test
```
### Spotless Code Formatting ### Spotless Code Formatting
Code formatting for Java is checked via *spotless*. Code formatting for Java is checked via *spotless*.
@ -576,7 +616,7 @@ Summary for Debian-based Linux systems:
sudo apt-get -y install podman sudo apt-get -y install podman
``` ```
2Then start it like this: Then start it like this:
```shell ```shell
systemctl --user enable --now podman.socket systemctl --user enable --now podman.socket
@ -607,7 +647,7 @@ we need to register a shutdown-hook in the test source code.
2. Now You Can Run the Tests 2. Now You Can Run the Tests
```shell ```shell
gw clean test # gw is from the .aliases file gw test # gw is from the .aliases file
``` ```
#### Use IntelliJ IDEA Run the Tests Against the Podman Daemon #### Use IntelliJ IDEA Run the Tests Against the Podman Daemon

View File

@ -1,15 +1,15 @@
plugins { plugins {
id 'java' id 'java'
id 'org.springframework.boot' version '3.0.0' id 'org.springframework.boot' version '3.1.7'
id 'io.spring.dependency-management' version '1.1.0' id 'io.spring.dependency-management' version '1.1.4'
id 'io.openapiprocessor.openapi-processor' version '2022.2' id 'io.openapiprocessor.openapi-processor' version '2023.2'
id 'com.github.jk1.dependency-license-report' version '2.1' id 'com.github.jk1.dependency-license-report' version '2.5'
id "org.owasp.dependencycheck" version "7.3.0" id "org.owasp.dependencycheck" version "9.0.7"
id "com.diffplug.spotless" version "6.11.0" id "com.diffplug.spotless" version "6.23.3"
id 'jacoco' id 'jacoco'
id 'info.solidsoft.pitest' version '1.9.0' id 'info.solidsoft.pitest' version '1.15.0'
id 'se.patrikerdes.use-latest-versions' version '0.2.18' id 'se.patrikerdes.use-latest-versions' version '0.2.18'
id 'com.github.ben-manes.versions' version '0.43.0' id 'com.github.ben-manes.versions' version '0.50.0'
} }
group = 'net.hostsharing' group = 'net.hostsharing'
@ -17,7 +17,7 @@ version = '0.0.1-SNAPSHOT'
wrapper { wrapper {
distributionType = Wrapper.DistributionType.BIN distributionType = Wrapper.DistributionType.BIN
gradleVersion = '7.5' gradleVersion = '8.5'
} }
configurations { configurations {
@ -42,7 +42,7 @@ repositories {
java { java {
toolchain { toolchain {
languageVersion = JavaLanguageVersion.of(17) languageVersion = JavaLanguageVersion.of(21)
} }
} }
@ -50,30 +50,41 @@ ext {
set('testcontainersVersion', "1.17.3") set('testcontainersVersion', "1.17.3")
} }
// wrapper
dependencies { dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-data-rest' implementation 'org.springframework.boot:spring-boot-starter-data-rest'
implementation 'org.springframework.boot:spring-boot-starter-jdbc' implementation 'org.springframework.boot:spring-boot-starter-jdbc'
implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'com.github.gavlyukovskiy:datasource-proxy-spring-boot-starter:1.8.1' implementation 'com.github.gavlyukovskiy:datasource-proxy-spring-boot-starter:1.9.1'
implementation 'org.springdoc:springdoc-openapi:2.0.0-M7' implementation 'org.springdoc:springdoc-openapi:2.3.0'
implementation 'org.liquibase:liquibase-core' implementation 'org.postgresql:postgresql:42.7.1'
implementation 'com.vladmihalcea:hibernate-types-60:2.20.0' implementation 'org.liquibase:liquibase-core:4.25.1'
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.13.4' implementation 'com.vladmihalcea:hibernate-types-60:2.21.1'
implementation 'org.openapitools:jackson-databind-nullable:0.2.4' implementation 'io.hypersistence:hypersistence-utils-hibernate-62:3.7.0'
implementation 'org.apache.commons:commons-text:1.10.0' implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.16.1'
implementation 'org.modelmapper:modelmapper:3.1.0' implementation 'org.openapitools:jackson-databind-nullable:0.2.6'
implementation 'org.iban4j:iban4j:3.2.3-RELEASE' implementation 'org.apache.commons:commons-text:1.11.0'
implementation 'org.modelmapper:modelmapper:3.2.0'
implementation 'org.iban4j:iban4j:3.2.7-RELEASE'
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.3.0'
// fixes vulnerability CVE-2022-1471
// The dependency usually comes from Spring Boot, just in the wrong version.
// TODO: Remove this explicit dependency once we are on SpringBoot 3.2.x
// as well as the related exclude in settings.gradle
// and the dependency suppression in owasp-dependency-check-suppression.xml.
implementation('org.yaml:snakeyaml') {
version {
strictly('2.2')
}
}
compileOnly 'org.projectlombok:lombok' compileOnly 'org.projectlombok:lombok'
testCompileOnly 'org.projectlombok:lombok' testCompileOnly 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools' developmentOnly 'org.springframework.boot:spring-boot-devtools'
runtimeOnly 'org.postgresql:postgresql'
annotationProcessor 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok'
testAnnotationProcessor 'org.projectlombok:lombok' testAnnotationProcessor 'org.projectlombok:lombok'
@ -81,11 +92,12 @@ dependencies {
testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.testcontainers:testcontainers' testImplementation 'org.testcontainers:testcontainers'
testImplementation 'org.testcontainers:junit-jupiter' testImplementation 'org.testcontainers:junit-jupiter'
testImplementation 'org.junit.jupiter:junit-jupiter'
testImplementation 'org.testcontainers:postgresql' testImplementation 'org.testcontainers:postgresql'
testImplementation 'com.tngtech.archunit:archunit-junit5:1.0.0' testImplementation 'com.tngtech.archunit:archunit-junit5:1.2.1'
testImplementation 'io.rest-assured:spring-mock-mvc' testImplementation 'io.rest-assured:spring-mock-mvc'
testImplementation 'org.hamcrest:hamcrest-core:2.2' testImplementation 'org.hamcrest:hamcrest-core:2.2'
testImplementation 'org.pitest:pitest-junit5-plugin:1.1.0' testImplementation 'org.pitest:pitest-junit5-plugin:1.2.1'
} }
dependencyManagement { dependencyManagement {
@ -170,7 +182,7 @@ openApiGenerate.dependsOn processSpring
// Spotless Code Formatting // Spotless Code Formatting
spotless { spotless {
java { java {
// removeUnusedImports() TODO: reactivate once it can deal with multi-line-strings removeUnusedImports()
indentWithSpaces(4) indentWithSpaces(4)
endWithNewline() endWithNewline()
toggleOffOn() toggleOffOn()
@ -182,14 +194,24 @@ spotless {
} }
} }
project.tasks.check.dependsOn(spotlessCheck) project.tasks.check.dependsOn(spotlessCheck)
// HACK: no idea why spotless uses the output of these tasks, but we get warnings without those
project.tasks.spotlessJava.dependsOn(
tasks.generateLicenseReport,
tasks.pitest,
tasks.jacocoTestReport,
tasks.processResources,
tasks.processTestResources)
// OWASP Dependency Security Test // OWASP Dependency Security Test
dependencyCheck { dependencyCheck {
cveValidForHours=4 nvd {
apiKey = project.property('OWASP_API_KEY') // set it in ~/.gradle/gradle.properties
delay = 16000
}
format = 'ALL' format = 'ALL'
suppressionFile = 'etc/owasp-dependency-check-suppression.xml' suppressionFile = 'etc/owasp-dependency-check-suppression.xml'
failOnError = true failOnError = true
failBuildOnCVSS = 7 failBuildOnCVSS = 5
} }
project.tasks.check.dependsOn(dependencyCheckAnalyze) project.tasks.check.dependsOn(dependencyCheckAnalyze)
project.tasks.dependencyCheckAnalyze.doFirst { // Why not doLast? See README.md! project.tasks.dependencyCheckAnalyze.doFirst { // Why not doLast? See README.md!
@ -206,7 +228,7 @@ project.tasks.check.dependsOn(checkLicense)
// JaCoCo Test Code Coverage // JaCoCo Test Code Coverage
jacoco { jacoco {
toolVersion = "0.8.8" toolVersion = "0.8.10"
} }
test { test {
finalizedBy jacocoTestReport // generate report after tests finalizedBy jacocoTestReport // generate report after tests
@ -285,7 +307,7 @@ pitest {
targetTests = ['net.hostsharing.hsadminng.**.*UnitTest', 'net.hostsharing.hsadminng.**.*RestTest'] targetTests = ['net.hostsharing.hsadminng.**.*UnitTest', 'net.hostsharing.hsadminng.**.*RestTest']
excludedTestClasses = ['**AcceptanceTest*', '**IntegrationTest*'] excludedTestClasses = ['**AcceptanceTest*', '**IntegrationTest*']
pitestVersion = '1.9.9' pitestVersion = '1.15.3'
junit5PluginVersion = '1.1.0' junit5PluginVersion = '1.1.0'
threads = 4 threads = 4

View File

@ -8,6 +8,7 @@
{ "moduleLicense": "BSD License" }, { "moduleLicense": "BSD License" },
{ "moduleLicense": "BSD-2-Clause" }, { "moduleLicense": "BSD-2-Clause" },
{ "moduleLicense": "BSD-3-Clause" },
{ "moduleLicense": "The BSD License" }, { "moduleLicense": "The BSD License" },
{ "moduleLicense": "CDDL 1.1" }, { "moduleLicense": "CDDL 1.1" },

View File

@ -14,4 +14,52 @@
<packageUrl regex="true">^pkg:maven/com\.fasterxml\.jackson\.core/jackson\-databind@.*$</packageUrl> <packageUrl regex="true">^pkg:maven/com\.fasterxml\.jackson\.core/jackson\-databind@.*$</packageUrl>
<cve>CVE-2022-42003</cve> <cve>CVE-2022-42003</cve>
</suppress> </suppress>
<suppress>
<notes><![CDATA[
We don't parse external XML.
]]></notes>
<packageUrl regex="true">^pkg:maven/org\.eclipse\.angus/angus\-activation@.*$</packageUrl>
<cpe>cpe:/a:eclipse:eclipse_ide</cpe>
</suppress>
<suppress>
<notes><![CDATA[
We don't parse external XML.
]]></notes>
<packageUrl regex="true">^pkg:maven/jakarta\.activation/jakarta\.activation\-api@.*$</packageUrl>
<cpe>cpe:/a:eclipse:eclipse_ide</cpe>
</suppress>
<suppress>
<notes><![CDATA[
Cyclic references are not possible if file comes in JSON text format.
]]></notes>
<packageUrl regex="true">^pkg:maven/com\.fasterxml\.jackson\.core/jackson\-databind@.*$</packageUrl>
<cpe>cpe:/a:fasterxml:jackson-databind</cpe>
</suppress>
<suppress>
<notes><![CDATA[
As far as I see Criteria.parse(...) cannot be reached with external data.
]]></notes>
<packageUrl regex="true">^pkg:maven/com\.jayway\.jsonpath/json\-path@.*$</packageUrl>
<vulnerabilityName>CVE-2023-51074</vulnerabilityName>
</suppress>
<suppress>
<notes><![CDATA[
Internal tooling, not exposed to the Internet.
]]></notes>
<packageUrl regex="true">^pkg:maven/org\.pitest/pitest\-command\-line@.*$</packageUrl>
<cpe>cpe:/a:line:line</cpe>
</suppress>
<suppress>
<notes><![CDATA[
Spring Boot 3.1.x has a transient dependency to snakeyaml 1.3
which contains this vulnerability.
We've explicitly bumped to 2.2, but the vulnerability checker does not seem to notice that.
TODO: Remove this suppression once we are on SpringBoot 3.2,
as well as the explicit version bump and the transient dependency exclude.
]]></notes>
<packageUrl regex="true">^pkg:maven/org\.yaml/snakeyaml@.*$</packageUrl>
<cve>CVE-2022-1471</cve>
</suppress>
</suppressions> </suppressions>

Binary file not shown.

View File

@ -1,5 +1,7 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

41
gradlew vendored
View File

@ -55,7 +55,7 @@
# Darwin, MinGW, and NonStop. # Darwin, MinGW, and NonStop.
# #
# (3) This script is generated from the Groovy template # (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project. # within the Gradle project.
# #
# You can find Gradle at https://github.com/gradle/gradle/. # You can find Gradle at https://github.com/gradle/gradle/.
@ -80,13 +80,11 @@ do
esac esac
done done
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit # This is normally unused
# shellcheck disable=SC2034
APP_NAME="Gradle"
APP_BASE_NAME=${0##*/} APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value. # Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum MAX_FD=maximum
@ -133,22 +131,29 @@ location of your Java installation."
fi fi
else else
JAVACMD=java JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the Please set the JAVA_HOME variable in your environment to match the
location of your Java installation." location of your Java installation."
fi
fi fi
# Increase the maximum file descriptors if we can. # Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #( case $MAX_FD in #(
max*) max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) || MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit" warn "Could not query maximum file descriptor limit"
esac esac
case $MAX_FD in #( case $MAX_FD in #(
'' | soft) :;; #( '' | soft) :;; #(
*) *)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" || ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD" warn "Could not set maximum file descriptor limit to $MAX_FD"
esac esac
@ -193,11 +198,15 @@ if "$cygwin" || "$msys" ; then
done done
fi fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
# shell script including quotes and variable substitutions, so put them in DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded. # Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \ set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \ "-Dorg.gradle.appname=$APP_BASE_NAME" \
@ -205,6 +214,12 @@ set -- \
org.gradle.wrapper.GradleWrapperMain \ org.gradle.wrapper.GradleWrapperMain \
"$@" "$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args. # Use "xargs" to parse quoted args.
# #
# With -n1 it outputs one arg per line, with the quotes and backslashes removed. # With -n1 it outputs one arg per line, with the quotes and backslashes removed.

15
gradlew.bat vendored
View File

@ -14,7 +14,7 @@
@rem limitations under the License. @rem limitations under the License.
@rem @rem
@if "%DEBUG%" == "" @echo off @if "%DEBUG%"=="" @echo off
@rem ########################################################################## @rem ##########################################################################
@rem @rem
@rem Gradle startup script for Windows @rem Gradle startup script for Windows
@ -25,7 +25,8 @@
if "%OS%"=="Windows_NT" setlocal if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0 set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=. if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0 set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME% set APP_HOME=%DIRNAME%
@ -40,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1 %JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute if %ERRORLEVEL% equ 0 goto execute
echo. echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@ -75,13 +76,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
:end :end
@rem End local scope for the variables with windows NT shell @rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd if %ERRORLEVEL% equ 0 goto mainEnd
:fail :fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code! rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 set EXIT_CODE=%ERRORLEVEL%
exit /b 1 if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd :mainEnd
if "%OS%"=="Windows_NT" endlocal if "%OS%"=="Windows_NT" endlocal

View File

@ -7,4 +7,28 @@ pluginManagement {
} }
} }
dependencyResolutionManagement {
components {
all {
allVariants {
withDependencies {
removeAll {
// Spring Boot 3.1.x has a transient dependency to snakeyaml 1.3
// which contains a severe vulnerability.
// Here we remove this transient dependency and in build.gradle
// we add an explicit dependency to snakeyaml 2.2,
// which does not have this vulnerability anymore.
//
// TODO: Check Once we are on SpringBoot 3.2.x, check if this exclude
// is still neccessary. If not:
// Remove it // as well as the related explicit dependency in build.gradle
// and the dependency suppression in owasp-dependency-check-suppression.xml.
it.module in [ 'snakeyaml' ]
}
}
}
}
}
}
rootProject.name = 'hsadmin-ng' rootProject.name = 'hsadmin-ng'

View File

@ -8,7 +8,7 @@ import static org.hibernate.dialect.DatabaseVersion.make;
public class PostgresCustomDialect extends PostgreSQLDialect { public class PostgresCustomDialect extends PostgreSQLDialect {
public PostgresCustomDialect() { public PostgresCustomDialect() {
super(make(13, 7)); super(make(15, 5));
} }
} }

View File

@ -15,9 +15,11 @@ import java.util.Collections;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static java.util.function.Predicate.not; import static java.util.function.Predicate.not;
import static net.hostsharing.hsadminng.mapper.PostgresArray.fromPostgresArray;
import static org.springframework.transaction.annotation.Propagation.MANDATORY; import static org.springframework.transaction.annotation.Propagation.MANDATORY;
@Service @Service
@ -81,11 +83,14 @@ public class Context {
} }
public String[] getAssumedRoles() { public String[] getAssumedRoles() {
return (String[]) em.createNativeQuery("select assumedRoles() as roles", String[].class).getSingleResult(); final byte[] result = (byte[]) em.createNativeQuery("select assumedRoles() as roles", String[].class).getSingleResult();
return fromPostgresArray(result, String.class, Function.identity());
} }
public UUID[] currentSubjectsUuids() { public UUID[] currentSubjectsUuids() {
return (UUID[]) em.createNativeQuery("select currentSubjectsUuids() as uuids", UUID[].class).getSingleResult(); final byte[] result = (byte[]) em.createNativeQuery("select currentSubjectsUuids() as uuids", UUID[].class)
.getSingleResult();
return fromPostgresArray(result, UUID.class, UUID::fromString);
} }
public static String getCallerMethodNameFromStackFrame(final int skipFrames) { public static String getCallerMethodNameFromStackFrame(final int skipFrames) {

View File

@ -14,7 +14,7 @@ public interface HsOfficeContactRepository extends Repository<HsOfficeContactEnt
@Query(""" @Query("""
SELECT c FROM HsOfficeContactEntity c SELECT c FROM HsOfficeContactEntity c
WHERE :label is null WHERE :label is null
OR c.label like concat(:label, '%') OR c.label like concat(cast(:label as text), '%')
""") """)
List<HsOfficeContactEntity> findContactByOptionalLabelLike(String label); List<HsOfficeContactEntity> findContactByOptionalLabelLike(String label);

View File

@ -1,13 +1,11 @@
package net.hostsharing.hsadminng.hs.office.coopassets; package net.hostsharing.hsadminng.hs.office.coopassets;
import com.vladmihalcea.hibernate.type.basic.PostgreSQLEnumType;
import lombok.*; import lombok.*;
import net.hostsharing.hsadminng.errors.DisplayName; import net.hostsharing.hsadminng.errors.DisplayName;
import net.hostsharing.hsadminng.hs.office.membership.HsOfficeMembershipEntity; import net.hostsharing.hsadminng.hs.office.membership.HsOfficeMembershipEntity;
import net.hostsharing.hsadminng.stringify.Stringify; import net.hostsharing.hsadminng.stringify.Stringify;
import net.hostsharing.hsadminng.stringify.Stringifyable; import net.hostsharing.hsadminng.stringify.Stringifyable;
import org.hibernate.annotations.GenericGenerator; import org.hibernate.annotations.GenericGenerator;
import org.hibernate.annotations.Type;
import jakarta.persistence.*; import jakarta.persistence.*;
import java.math.BigDecimal; import java.math.BigDecimal;
@ -47,7 +45,6 @@ public class HsOfficeCoopAssetsTransactionEntity implements Stringifyable {
@Column(name = "transactiontype") @Column(name = "transactiontype")
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
@Type(PostgreSQLEnumType.class)
private HsOfficeCoopAssetsTransactionType transactionType; private HsOfficeCoopAssetsTransactionType transactionType;
@Column(name = "valuedate") @Column(name = "valuedate")

View File

@ -1,6 +1,5 @@
package net.hostsharing.hsadminng.hs.office.coopassets; package net.hostsharing.hsadminng.hs.office.coopassets;
import net.hostsharing.hsadminng.hs.office.coopshares.HsOfficeCoopSharesTransactionEntity;
import org.springframework.data.jpa.repository.Query; import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.Repository; import org.springframework.data.repository.Repository;

View File

@ -1,12 +1,10 @@
package net.hostsharing.hsadminng.hs.office.coopshares; package net.hostsharing.hsadminng.hs.office.coopshares;
import com.vladmihalcea.hibernate.type.basic.PostgreSQLEnumType;
import lombok.*; import lombok.*;
import net.hostsharing.hsadminng.errors.DisplayName; import net.hostsharing.hsadminng.errors.DisplayName;
import net.hostsharing.hsadminng.hs.office.membership.HsOfficeMembershipEntity; import net.hostsharing.hsadminng.hs.office.membership.HsOfficeMembershipEntity;
import net.hostsharing.hsadminng.stringify.Stringify; import net.hostsharing.hsadminng.stringify.Stringify;
import net.hostsharing.hsadminng.stringify.Stringifyable; import net.hostsharing.hsadminng.stringify.Stringifyable;
import org.hibernate.annotations.Type;
import jakarta.persistence.*; import jakarta.persistence.*;
import java.time.LocalDate; import java.time.LocalDate;
@ -43,7 +41,6 @@ public class HsOfficeCoopSharesTransactionEntity implements Stringifyable {
@Column(name = "transactiontype") @Column(name = "transactiontype")
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
@Type(PostgreSQLEnumType.class)
private HsOfficeCoopSharesTransactionType transactionType; private HsOfficeCoopSharesTransactionType transactionType;
@Column(name = "valuedate") @Column(name = "valuedate")

View File

@ -19,15 +19,15 @@ public interface HsOfficeDebitorRepository extends Repository<HsOfficeDebitorEnt
@Query(""" @Query("""
SELECT debitor FROM HsOfficeDebitorEntity debitor SELECT debitor FROM HsOfficeDebitorEntity debitor
JOIN HsOfficePartnerEntity partner ON partner.uuid = debitor.partner JOIN HsOfficePartnerEntity partner ON partner.uuid = debitor.partner.uuid
JOIN HsOfficePersonEntity person ON person.uuid = partner.person JOIN HsOfficePersonEntity person ON person.uuid = partner.person.uuid
JOIN HsOfficeContactEntity contact ON contact.uuid = debitor.billingContact JOIN HsOfficeContactEntity contact ON contact.uuid = debitor.billingContact.uuid
WHERE :name is null WHERE :name is null
OR partner.details.birthName like concat(:name, '%') OR partner.details.birthName like concat(cast(:name as text), '%')
OR person.tradeName like concat(:name, '%') OR person.tradeName like concat(cast(:name as text), '%')
OR person.familyName like concat(:name, '%') OR person.familyName like concat(cast(:name as text), '%')
OR person.givenName like concat(:name, '%') OR person.givenName like concat(cast(:name as text), '%')
OR contact.label like concat(:name, '%') OR contact.label like concat(cast(:name as text), '%')
""") """)
List<HsOfficeDebitorEntity> findDebitorByOptionalNameLike(String name); List<HsOfficeDebitorEntity> findDebitorByOptionalNameLike(String name);

View File

@ -1,6 +1,5 @@
package net.hostsharing.hsadminng.hs.office.membership; package net.hostsharing.hsadminng.hs.office.membership;
import com.vladmihalcea.hibernate.type.basic.PostgreSQLEnumType;
import com.vladmihalcea.hibernate.type.range.PostgreSQLRangeType; import com.vladmihalcea.hibernate.type.range.PostgreSQLRangeType;
import com.vladmihalcea.hibernate.type.range.Range; import com.vladmihalcea.hibernate.type.range.Range;
import lombok.*; import lombok.*;
@ -61,7 +60,6 @@ public class HsOfficeMembershipEntity implements Stringifyable {
@Column(name = "reasonfortermination") @Column(name = "reasonfortermination")
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
@Type(PostgreSQLEnumType.class)
private HsOfficeReasonForTermination reasonForTermination; private HsOfficeReasonForTermination reasonForTermination;
public void setValidFrom(final LocalDate validFrom) { public void setValidFrom(final LocalDate validFrom) {

View File

@ -13,14 +13,14 @@ public interface HsOfficePartnerRepository extends Repository<HsOfficePartnerEnt
@Query(""" @Query("""
SELECT partner FROM HsOfficePartnerEntity partner SELECT partner FROM HsOfficePartnerEntity partner
JOIN HsOfficeContactEntity contact ON contact.uuid = partner.contact JOIN HsOfficeContactEntity contact ON contact.uuid = partner.contact.uuid
JOIN HsOfficePersonEntity person ON person.uuid = partner.person JOIN HsOfficePersonEntity person ON person.uuid = partner.person.uuid
WHERE :name is null WHERE :name is null
OR partner.details.birthName like concat(:name, '%') OR partner.details.birthName like concat(cast(:name as text), '%')
OR contact.label like concat(:name, '%') OR contact.label like concat(cast(:name as text), '%')
OR person.tradeName like concat(:name, '%') OR person.tradeName like concat(cast(:name as text), '%')
OR person.givenName like concat(:name, '%') OR person.givenName like concat(cast(:name as text), '%')
OR person.familyName like concat(:name, '%') OR person.familyName like concat(cast(:name as text), '%')
""") """)
List<HsOfficePartnerEntity> findPartnerByOptionalNameLike(String name); List<HsOfficePartnerEntity> findPartnerByOptionalNameLike(String name);

View File

@ -1,13 +1,11 @@
package net.hostsharing.hsadminng.hs.office.person; package net.hostsharing.hsadminng.hs.office.person;
import com.vladmihalcea.hibernate.type.basic.PostgreSQLEnumType;
import lombok.*; import lombok.*;
import lombok.experimental.FieldNameConstants; import lombok.experimental.FieldNameConstants;
import net.hostsharing.hsadminng.errors.DisplayName; import net.hostsharing.hsadminng.errors.DisplayName;
import net.hostsharing.hsadminng.stringify.Stringify; import net.hostsharing.hsadminng.stringify.Stringify;
import net.hostsharing.hsadminng.stringify.Stringifyable; import net.hostsharing.hsadminng.stringify.Stringifyable;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.hibernate.annotations.Type;
import jakarta.persistence.*; import jakarta.persistence.*;
import java.util.UUID; import java.util.UUID;
@ -37,7 +35,6 @@ public class HsOfficePersonEntity implements Stringifyable {
@Column(name = "persontype") @Column(name = "persontype")
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
@Type(PostgreSQLEnumType.class)
private HsOfficePersonType personType; private HsOfficePersonType personType;
@Column(name = "tradename") @Column(name = "tradename")

View File

@ -14,9 +14,9 @@ public interface HsOfficePersonRepository extends Repository<HsOfficePersonEntit
@Query(""" @Query("""
SELECT p FROM HsOfficePersonEntity p SELECT p FROM HsOfficePersonEntity p
WHERE :name is null WHERE :name is null
OR p.tradeName like concat(:name, '%') OR p.tradeName like concat(cast(:name as text), '%')
OR p.givenName like concat(:name, '%') OR p.givenName like concat(cast(:name as text), '%')
OR p.familyName like concat(:name, '%') OR p.familyName like concat(cast(:name as text), '%')
""") """)
List<HsOfficePersonEntity> findPersonByOptionalNameLike(String name); List<HsOfficePersonEntity> findPersonByOptionalNameLike(String name);

View File

@ -51,7 +51,7 @@ public class HsOfficeRelationshipController implements HsOfficeRelationshipsApi
final HsOfficeRelationshipTypeResource relationshipType) { final HsOfficeRelationshipTypeResource relationshipType) {
context.define(currentUser, assumedRoles); context.define(currentUser, assumedRoles);
final var entities = relationshipRepo.findRelationshipRelatedToPersonUuid(personUuid, final var entities = relationshipRepo.findRelationshipRelatedToPersonUuidAndRelationshipType(personUuid,
mapper.map(relationshipType, HsOfficeRelationshipType.class)); mapper.map(relationshipType, HsOfficeRelationshipType.class));
final var resources = mapper.mapList(entities, HsOfficeRelationshipResource.class, final var resources = mapper.mapList(entities, HsOfficeRelationshipResource.class,

View File

@ -1,12 +1,10 @@
package net.hostsharing.hsadminng.hs.office.relationship; package net.hostsharing.hsadminng.hs.office.relationship;
import com.vladmihalcea.hibernate.type.basic.PostgreSQLEnumType;
import lombok.*; import lombok.*;
import lombok.experimental.FieldNameConstants; import lombok.experimental.FieldNameConstants;
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity; import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity; import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity;
import net.hostsharing.hsadminng.stringify.Stringify; import net.hostsharing.hsadminng.stringify.Stringify;
import org.hibernate.annotations.Type;
import jakarta.persistence.*; import jakarta.persistence.*;
import java.util.UUID; import java.util.UUID;
@ -47,7 +45,6 @@ public class HsOfficeRelationshipEntity {
@Column(name = "reltype") @Column(name = "reltype")
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
@Type(PostgreSQLEnumType.class)
private HsOfficeRelationshipType relType; private HsOfficeRelationshipType relType;
@Override @Override

View File

@ -12,8 +12,8 @@ public interface HsOfficeRelationshipRepository extends Repository<HsOfficeRelat
Optional<HsOfficeRelationshipEntity> findByUuid(UUID id); Optional<HsOfficeRelationshipEntity> findByUuid(UUID id);
default List<HsOfficeRelationshipEntity> findRelationshipRelatedToPersonUuid(@NotNull UUID personUuid, HsOfficeRelationshipType relationshipType) { default List<HsOfficeRelationshipEntity> findRelationshipRelatedToPersonUuidAndRelationshipType(@NotNull UUID personUuid, HsOfficeRelationshipType relationshipType) {
return findRelationshipRelatedToPersonUuid(personUuid, relationshipType.toString()); return findRelationshipRelatedToPersonUuidAndRelationshipTypeString(personUuid, relationshipType.toString());
} }
@Query(value = """ @Query(value = """
@ -27,7 +27,7 @@ public interface HsOfficeRelationshipRepository extends Repository<HsOfficeRelat
WHERE (:relationshipType IS NULL OR p.relType = cast(:relationshipType AS HsOfficeRelationshipType)) WHERE (:relationshipType IS NULL OR p.relType = cast(:relationshipType AS HsOfficeRelationshipType))
AND ( p.relAnchorUuid = :personUuid OR p.relHolderUuid = :personUuid) AND ( p.relAnchorUuid = :personUuid OR p.relHolderUuid = :personUuid)
""", nativeQuery = true) """, nativeQuery = true)
List<HsOfficeRelationshipEntity> findRelationshipRelatedToPersonUuid(@NotNull UUID personUuid, String relationshipType); List<HsOfficeRelationshipEntity> findRelationshipRelatedToPersonUuidAndRelationshipTypeString(@NotNull UUID personUuid, String relationshipType);
HsOfficeRelationshipEntity save(final HsOfficeRelationshipEntity entity); HsOfficeRelationshipEntity save(final HsOfficeRelationshipEntity entity);

View File

@ -14,7 +14,7 @@ public interface HsOfficeSepaMandateRepository extends Repository<HsOfficeSepaMa
@Query(""" @Query("""
SELECT mandate FROM HsOfficeSepaMandateEntity mandate SELECT mandate FROM HsOfficeSepaMandateEntity mandate
WHERE :iban is null WHERE :iban is null
OR mandate.bankAccount.iban like concat(:iban, '%') OR mandate.bankAccount.iban like concat(cast(:iban as text), '%')
ORDER BY mandate.bankAccount.iban ORDER BY mandate.bankAccount.iban
""") """)
List<HsOfficeSepaMandateEntity> findSepaMandateByOptionalIban(String iban); List<HsOfficeSepaMandateEntity> findSepaMandateByOptionalIban(String iban);

View File

@ -0,0 +1,58 @@
package net.hostsharing.hsadminng.mapper;
import lombok.experimental.UtilityClass;
import org.postgresql.util.PGtokenizer;
import java.lang.reflect.Array;
import java.nio.charset.StandardCharsets;
import java.util.function.Function;
@UtilityClass
public class PostgresArray {
/**
* Converts a byte[], as returned for a Postgres-array by native queries, to a Java array.
*
* <p>This example code worked with Hibernate 5 (Spring Boot 3.0.x):
* <pre><code>
* return (UUID[]) em.createNativeQuery("select currentSubjectsUuids() as uuids", UUID[].class).getSingleResult();
* </code></pre>
* </p>
*
* <p>With Hibernate 6 (Spring Boot 3.1.x), this utility method can be used like such:
* <pre><code>
* final byte[] result = (byte[]) em.createNativeQuery("select * from currentSubjectsUuids() as uuids", UUID[].class)
* .getSingleResult();
* return fromPostgresArray(result, UUID.class, UUID::fromString);
* </code></pre>
* </p>
*
* @param pgArray the byte[] returned by a native query containing as rendered for a Postgres array
* @param elementClass the class of a single element of the Java array to be returned
* @param itemParser converts a string element to the specified elementClass
* @return a Java array containing the data from pgArray
* @param <T> type of a single element of the Java array
*/
public static <T> T[] fromPostgresArray(final byte[] pgArray, final Class<T> elementClass, final Function<String, T> itemParser) {
final var pgArrayLiteral = new String(pgArray, StandardCharsets.UTF_8);
if (pgArrayLiteral.length() == 2) {
return newGenericArray(elementClass, 0);
}
final PGtokenizer tokenizer = new PGtokenizer(pgArrayLiteral.substring(1, pgArrayLiteral.length()-1), ',');
tokenizer.remove("\"", "\"");
final T[] array = newGenericArray(elementClass, tokenizer.getSize()); // Create a new array of the specified type and length
for ( int n = 0; n < tokenizer.getSize(); ++n ) {
final String token = tokenizer.getToken(n);
if ( !"NULL".equals(token) ) {
array[n] = itemParser.apply(token.trim().replace("\\\"", "\""));
}
}
return array;
}
@SuppressWarnings("unchecked")
private static <T> T[] newGenericArray(final Class<T> elementClass, final int length) {
return (T[]) Array.newInstance(elementClass, length);
}
}

View File

@ -11,7 +11,7 @@ public interface RbacUserRepository extends Repository<RbacUserEntity, UUID> {
@Query(""" @Query("""
select u from RbacUserEntity u select u from RbacUserEntity u
where :userName is null or u.name like concat(:userName, '%') where :userName is null or u.name like concat(cast(:userName as text), '%')
order by u.name order by u.name
""") """)
List<RbacUserEntity> findByOptionalNameLike(String userName); List<RbacUserEntity> findByOptionalNameLike(String userName);

View File

@ -11,7 +11,6 @@ import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder; import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder;
import java.util.List; import java.util.List;
import java.util.UUID;
@RestController @RestController
public class TestCustomerController implements TestCustomersApi { public class TestCustomerController implements TestCustomersApi {

View File

@ -12,7 +12,7 @@ public interface TestCustomerRepository extends Repository<TestCustomerEntity, U
Optional<TestCustomerEntity> findByUuid(UUID id); Optional<TestCustomerEntity> findByUuid(UUID id);
@Query("SELECT c FROM TestCustomerEntity c WHERE :prefix is null or c.prefix like concat(:prefix, '%')") @Query("SELECT c FROM TestCustomerEntity c WHERE :prefix is null or c.prefix like concat(cast(:prefix as text), '%')")
List<TestCustomerEntity> findCustomerByOptionalPrefixLike(String prefix); List<TestCustomerEntity> findCustomerByOptionalPrefixLike(String prefix);
TestCustomerEntity save(final TestCustomerEntity entity); TestCustomerEntity save(final TestCustomerEntity entity);

View File

@ -8,7 +8,7 @@ import java.util.UUID;
public interface TestPackageRepository extends Repository<TestPackageEntity, UUID> { public interface TestPackageRepository extends Repository<TestPackageEntity, UUID> {
@Query("SELECT p FROM TestPackageEntity p WHERE :name is null or p.name like concat(:name, '%')") @Query("SELECT p FROM TestPackageEntity p WHERE :name is null or p.name like concat(cast(:name as text), '%')")
List<TestPackageEntity> findAllByOptionalNameLike(final String name); List<TestPackageEntity> findAllByOptionalNameLike(final String name);
TestPackageEntity findByUuid(UUID packageUuid); TestPackageEntity findByUuid(UUID packageUuid);

View File

@ -0,0 +1,96 @@
--liquibase formatted sql
-- TODO: These changesets are just for the external remote views to simulate the legacy tables.
-- Once we don't need the external remote views anymore, create revert changesets.
-- ============================================================================
--changeset hs-office-contact-MIGRATION-mapping:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
CREATE TABLE hs_office_contact_legacy_id
(
uuid uuid NOT NULL REFERENCES hs_office_contact(uuid),
contact_id integer NOT NULL
);
--//
-- ============================================================================
--changeset hs-office-contact-MIGRATION-sequence:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
CREATE SEQUENCE IF NOT EXISTS hs_office_contact_legacy_id_seq
AS integer
START 1000000000
OWNED BY hs_office_contact_legacy_id.contact_id;
--//
-- ============================================================================
--changeset hs-office-contact-MIGRATION-default:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
ALTER TABLE hs_office_contact_legacy_id
ALTER COLUMN contact_id
SET DEFAULT nextVal('hs_office_contact_legacy_id_seq');
--/
-- ============================================================================
--changeset hs-office-contact-MIGRATION-insert:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
CALL defineContext('schema-migration');
INSERT INTO hs_office_contact_legacy_id(uuid, contact_id)
SELECT uuid, nextVal('hs_office_contact_legacy_id_seq') FROM hs_office_contact;
--/
-- ============================================================================
--changeset hs-office-contact-MIGRATION-insert-trigger:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
create or replace function insertContactLegacyIdMapping()
returns trigger
language plpgsql
strict as $$
begin
if TG_OP <> 'INSERT' then
raise exception 'invalid usage of trigger';
end if;
INSERT INTO hs_office_contact_legacy_id VALUES
(NEW.uuid, nextVal('hs_office_contact_legacy_id_seq'));
return NEW;
end; $$;
create trigger createContactLegacyIdMapping
after insert on hs_office_contact
for each row
execute procedure insertContactLegacyIdMapping();
--/
-- ============================================================================
--changeset hs-office-contact-MIGRATION-delete-trigger:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
create or replace function deleteContactLegacyIdMapping()
returns trigger
language plpgsql
strict as $$
begin
if TG_OP <> 'DELETE' then
raise exception 'invalid usage of trigger';
end if;
DELETE FROM hs_office_contact_legacy_id
WHERE uuid = OLD.uuid;
return OLD;
end; $$;
create trigger removeContactLegacyIdMapping
before delete on hs_office_contact
for each row
execute procedure deleteContactLegacyIdMapping();
--/

View File

@ -0,0 +1,95 @@
--liquibase formatted sql
-- TODO: These changesets are just for the external remote views to simulate the legacy tables.
-- Once we don't need the external remote views anymore, create revert changesets.
-- ============================================================================
--changeset hs-office-partner-MIGRATION-mapping:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
CREATE TABLE hs_office_partner_legacy_id
(
uuid uuid NOT NULL REFERENCES hs_office_partner(uuid),
bp_id integer NOT NULL
);
--//
-- ============================================================================
--changeset hs-office-partner-MIGRATION-sequence:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
CREATE SEQUENCE IF NOT EXISTS hs_office_partner_legacy_id_seq
AS integer
START 1000000000
OWNED BY hs_office_partner_legacy_id.bp_id;
--//
-- ============================================================================
--changeset hs-office-partner-MIGRATION-default:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
ALTER TABLE hs_office_partner_legacy_id
ALTER COLUMN bp_id
SET DEFAULT nextVal('hs_office_partner_legacy_id_seq');
--/
-- ============================================================================
--changeset hs-office-partner-MIGRATION-insert:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
CALL defineContext('schema-migration');
INSERT INTO hs_office_partner_legacy_id(uuid, bp_id)
SELECT uuid, nextVal('hs_office_partner_legacy_id_seq') FROM hs_office_partner;
--/
-- ============================================================================
--changeset hs-office-partner-MIGRATION-insert-trigger:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
create or replace function insertPartnerLegacyIdMapping()
returns trigger
language plpgsql
strict as $$
begin
if TG_OP <> 'INSERT' then
raise exception 'invalid usage of trigger';
end if;
INSERT INTO hs_office_partner_legacy_id VALUES
(NEW.uuid, nextVal('hs_office_partner_legacy_id_seq'));
return NEW;
end; $$;
create trigger createPartnerLegacyIdMapping
after insert on hs_office_partner
for each row
execute procedure insertPartnerLegacyIdMapping();
--/
-- ============================================================================
--changeset hs-office-partner-MIGRATION-delete-trigger:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
create or replace function deletePartnerLegacyIdMapping()
returns trigger
language plpgsql
strict as $$
begin
if TG_OP <> 'DELETE' then
raise exception 'invalid usage of trigger';
end if;
DELETE FROM hs_office_partner_legacy_id
WHERE uuid = OLD.uuid;
return OLD;
end; $$;
create trigger removePartnerLegacyIdMapping
before delete on hs_office_partner
for each row
execute procedure deletePartnerLegacyIdMapping();
--/

View File

@ -0,0 +1,97 @@
--liquibase formatted sql
-- TODO: These changesets are just for the external remote views to simulate the legacy tables.
-- Once we don't need the external remote views anymore, create revert changesets.
-- ============================================================================
--changeset hs-office-sepamandate-MIGRATION-mapping:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
CREATE TABLE hs_office_sepamandate_legacy_id
(
uuid uuid NOT NULL REFERENCES hs_office_sepamandate(uuid),
sepa_mandat_id integer NOT NULL
);
--//
-- ============================================================================
--changeset hs-office-sepamandate-MIGRATION-sequence:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
CREATE SEQUENCE IF NOT EXISTS hs_office_sepamandate_legacy_id_seq
AS integer
START 1000000000
OWNED BY hs_office_sepamandate_legacy_id.sepa_mandat_id;
--//
-- ============================================================================
--changeset hs-office-sepamandate-MIGRATION-default:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
ALTER TABLE hs_office_sepamandate_legacy_id
ALTER COLUMN sepa_mandat_id
SET DEFAULT nextVal('hs_office_sepamandate_legacy_id_seq');
--/
-- ============================================================================
--changeset hs-office-sepamandate-MIGRATION-insert:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
CALL defineContext('schema-migration');
INSERT INTO hs_office_sepamandate_legacy_id(uuid, sepa_mandat_id)
SELECT uuid, nextVal('hs_office_sepamandate_legacy_id_seq') FROM hs_office_sepamandate;
--/
-- ============================================================================
--changeset hs-office-sepamandate-MIGRATION-insert-trigger:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
create or replace function insertSepaMandateLegacyIdMapping()
returns trigger
language plpgsql
strict as $$
begin
if TG_OP <> 'INSERT' then
raise exception 'invalid usage of trigger';
end if;
INSERT INTO hs_office_sepamandate_legacy_id VALUES
(NEW.uuid, nextVal('hs_office_sepamandate_legacy_id_seq'));
return NEW;
end; $$;
create trigger createSepaMandateLegacyIdMapping
after insert on hs_office_sepamandate
for each row
execute procedure insertSepaMandateLegacyIdMapping();
--/
-- ============================================================================
--changeset hs-office-sepamandate-MIGRATION-delete-trigger:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
create or replace function deleteSepaMandateLegacyIdMapping()
returns trigger
language plpgsql
strict as $$
begin
if TG_OP <> 'DELETE' then
raise exception 'invalid usage of trigger';
end if;
DELETE FROM hs_office_sepamandate_legacy_id
WHERE uuid = OLD.uuid;
return OLD;
end; $$;
create trigger removeSepaMandateLegacyIdMapping
before delete on hs_office_sepamandate
for each row
execute procedure deleteSepaMandateLegacyIdMapping();
--/

View File

@ -0,0 +1,96 @@
--liquibase formatted sql
-- TODO: These changesets are just for the external remote views to simulate the legacy tables.
-- Once we don't need the external remote views anymore, create revert changesets.
-- ============================================================================
--changeset hs-office-coopshares-MIGRATION-mapping:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
CREATE TABLE hs_office_coopsharestransaction_legacy_id
(
uuid uuid NOT NULL REFERENCES hs_office_coopsharestransaction(uuid),
member_share_id integer NOT NULL
);
--//
-- ============================================================================
--changeset hs-office-coopshares-MIGRATION-sequence:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
CREATE SEQUENCE IF NOT EXISTS hs_office_coopsharestransaction_legacy_id_seq
AS integer
START 1000000000
OWNED BY hs_office_coopsharestransaction_legacy_id.member_share_id;
--//
-- ============================================================================
--changeset hs-office-coopshares-MIGRATION-default:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
ALTER TABLE hs_office_coopsharestransaction_legacy_id
ALTER COLUMN member_share_id
SET DEFAULT nextVal('hs_office_coopsharestransaction_legacy_id_seq');
--/
-- ============================================================================
--changeset hs-office-coopshares-MIGRATION-insert:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
CALL defineContext('schema-migration');
INSERT INTO hs_office_coopsharestransaction_legacy_id(uuid, member_share_id)
SELECT uuid, nextVal('hs_office_coopsharestransaction_legacy_id_seq') FROM hs_office_coopsharestransaction;
--/
-- ============================================================================
--changeset hs-office-coopShares-MIGRATION-insert-trigger:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
create or replace function insertCoopSharesLegacyIdMapping()
returns trigger
language plpgsql
strict as $$
begin
if TG_OP <> 'INSERT' then
raise exception 'invalid usage of trigger';
end if;
INSERT INTO hs_office_coopsharestransaction_legacy_id VALUES
(NEW.uuid, nextVal('hs_office_coopsharestransaction_legacy_id_seq'));
return NEW;
end; $$;
create trigger createCoopSharesLegacyIdMapping
after insert on hs_office_coopsharestransaction
for each row
execute procedure insertCoopSharesLegacyIdMapping();
--/
-- ============================================================================
--changeset hs-office-coopShares-MIGRATION-delete-trigger:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
create or replace function deleteCoopSharesLegacyIdMapping()
returns trigger
language plpgsql
strict as $$
begin
if TG_OP <> 'DELETE' then
raise exception 'invalid usage of trigger';
end if;
DELETE FROM hs_office_coopsharestransaction_legacy_id
WHERE uuid = OLD.uuid;
return OLD;
end; $$;
create trigger removeCoopSharesLegacyIdMapping
before delete on hs_office_coopsharestransaction
for each row
execute procedure deleteCoopSharesLegacyIdMapping();
--/

View File

@ -0,0 +1,96 @@
--liquibase formatted sql
-- TODO: These changesets are just for the external remote views to simulate the legacy tables.
-- Once we don't need the external remote views anymore, create revert changesets.
-- ============================================================================
--changeset hs-office-coopassets-MIGRATION-mapping:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
CREATE TABLE hs_office_coopassetstransaction_legacy_id
(
uuid uuid NOT NULL REFERENCES hs_office_coopassetstransaction(uuid),
member_asset_id integer NOT NULL
);
--//
-- ============================================================================
--changeset hs-office-coopassets-MIGRATION-sequence:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
CREATE SEQUENCE IF NOT EXISTS hs_office_coopassetstransaction_legacy_id_seq
AS integer
START 1000000000
OWNED BY hs_office_coopassetstransaction_legacy_id.member_asset_id;
--//
-- ============================================================================
--changeset hs-office-coopassets-MIGRATION-default:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
ALTER TABLE hs_office_coopassetstransaction_legacy_id
ALTER COLUMN member_asset_id
SET DEFAULT nextVal('hs_office_coopassetstransaction_legacy_id_seq');
--/
-- ============================================================================
--changeset hs-office-coopassets-MIGRATION-insert:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
CALL defineContext('schema-migration');
INSERT INTO hs_office_coopassetstransaction_legacy_id(uuid, member_asset_id)
SELECT uuid, nextVal('hs_office_coopassetstransaction_legacy_id_seq') FROM hs_office_coopassetstransaction;
--/
-- ============================================================================
--changeset hs-office-coopAssets-MIGRATION-insert-trigger:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
create or replace function insertCoopAssetsLegacyIdMapping()
returns trigger
language plpgsql
strict as $$
begin
if TG_OP <> 'INSERT' then
raise exception 'invalid usage of trigger';
end if;
INSERT INTO hs_office_coopassetstransaction_legacy_id VALUES
(NEW.uuid, nextVal('hs_office_coopassetstransaction_legacy_id_seq'));
return NEW;
end; $$;
create trigger createCoopAssetsLegacyIdMapping
after insert on hs_office_coopassetstransaction
for each row
execute procedure insertCoopAssetsLegacyIdMapping();
--/
-- ============================================================================
--changeset hs-office-coopAssets-MIGRATION-delete-trigger:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
create or replace function deleteCoopAssetsLegacyIdMapping()
returns trigger
language plpgsql
strict as $$
begin
if TG_OP <> 'DELETE' then
raise exception 'invalid usage of trigger';
end if;
DELETE FROM hs_office_coopassetstransaction_legacy_id
WHERE uuid = OLD.uuid;
return OLD;
end; $$;
create trigger removeCoopAssetsLegacyIdMapping
before delete on hs_office_coopassetstransaction
for each row
execute procedure deleteCoopAssetsLegacyIdMapping();
--/

View File

@ -53,6 +53,8 @@ databaseChangeLog:
file: db/changelog/200-hs-office-contact.sql file: db/changelog/200-hs-office-contact.sql
- include: - include:
file: db/changelog/203-hs-office-contact-rbac.sql file: db/changelog/203-hs-office-contact-rbac.sql
- include:
file: db/changelog/206-hs-office-contact-migration.sql
- include: - include:
file: db/changelog/208-hs-office-contact-test-data.sql file: db/changelog/208-hs-office-contact-test-data.sql
- include: - include:
@ -67,6 +69,8 @@ databaseChangeLog:
file: db/changelog/223-hs-office-partner-rbac.sql file: db/changelog/223-hs-office-partner-rbac.sql
- include: - include:
file: db/changelog/224-hs-office-partner-details-rbac.sql file: db/changelog/224-hs-office-partner-details-rbac.sql
- include:
file: db/changelog/226-hs-office-partner-migration.sql
- include: - include:
file: db/changelog/228-hs-office-partner-test-data.sql file: db/changelog/228-hs-office-partner-test-data.sql
- include: - include:
@ -80,7 +84,7 @@ databaseChangeLog:
- include: - include:
file: db/changelog/243-hs-office-bankaccount-rbac.sql file: db/changelog/243-hs-office-bankaccount-rbac.sql
- include: - include:
file: db/changelog/248-hs-office-bankaccount-test-data.sql file: db/changelog/248-hs-office-bankaccount-test-data.sql
- include: - include:
file: db/changelog/270-hs-office-debitor.sql file: db/changelog/270-hs-office-debitor.sql
- include: - include:
@ -91,6 +95,8 @@ databaseChangeLog:
file: db/changelog/250-hs-office-sepamandate.sql file: db/changelog/250-hs-office-sepamandate.sql
- include: - include:
file: db/changelog/253-hs-office-sepamandate-rbac.sql file: db/changelog/253-hs-office-sepamandate-rbac.sql
- include:
file: db/changelog/256-hs-office-sepamandate-migration.sql
- include: - include:
file: db/changelog/258-hs-office-sepamandate-test-data.sql file: db/changelog/258-hs-office-sepamandate-test-data.sql
- include: - include:
@ -103,11 +109,15 @@ databaseChangeLog:
file: db/changelog/310-hs-office-coopshares.sql file: db/changelog/310-hs-office-coopshares.sql
- include: - include:
file: db/changelog/313-hs-office-coopshares-rbac.sql file: db/changelog/313-hs-office-coopshares-rbac.sql
- include:
file: db/changelog/316-hs-office-coopshares-migration.sql
- include: - include:
file: db/changelog/318-hs-office-coopshares-test-data.sql file: db/changelog/318-hs-office-coopshares-test-data.sql
- include: - include:
file: db/changelog/320-hs-office-coopassets.sql file: db/changelog/320-hs-office-coopassets.sql
- include: - include:
file: db/changelog/323-hs-office-coopassets-rbac.sql file: db/changelog/323-hs-office-coopassets-rbac.sql
- include:
file: db/changelog/326-hs-office-coopassets-migration.sql
- include: - include:
file: db/changelog/328-hs-office-coopassets-test-data.sql file: db/changelog/328-hs-office-coopassets-test-data.sql

View File

@ -83,7 +83,7 @@ class HsOfficeBankAccountControllerRestTest {
enum InvalidBicTestCase { enum InvalidBicTestCase {
TOO_SHORT("BEVODEB", "Bic length must be 8 or 11"), TOO_SHORT("BEVODEB", "Bic length must be 8 or 11"),
TOO_LONG("BEVODEBBX", "Bic length must be 8 or 11"), TOO_LONG("BEVODEBBX", "Bic length must be 8 or 11"),
INVALID_CHARACTER("BEV-ODEB", "Bank code must contain only letters."); INVALID_CHARACTER("BEV-ODEB", "Bank code must contain only alphanumeric.");
private final String givenBic; private final String givenBic;
private final String expectedErrorMessage; private final String expectedErrorMessage;

View File

@ -1,6 +1,5 @@
package net.hostsharing.hsadminng.hs.office.bankaccount; package net.hostsharing.hsadminng.hs.office.bankaccount;
import java.util.UUID;
public class TestHsOfficeBankAccount { public class TestHsOfficeBankAccount {

View File

@ -28,7 +28,6 @@ import static net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantEntity.grantD
import static net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleEntity.roleNamesOf; import static net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleEntity.roleNamesOf;
import static net.hostsharing.test.JpaAttempt.attempt; import static net.hostsharing.test.JpaAttempt.attempt;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assumptions.assumeThat;
@DataJpaTest @DataJpaTest
@Import( { Context.class, JpaAttempt.class }) @Import( { Context.class, JpaAttempt.class })
@ -237,10 +236,6 @@ class HsOfficeContactRepositoryIntegrationTest extends ContextBasedTest {
final var initialRoleNames = roleNamesOf(rawRoleRepo.findAll()); final var initialRoleNames = roleNamesOf(rawRoleRepo.findAll());
final var initialGrantNames = grantDisplaysOf(rawGrantRepo.findAll()); final var initialGrantNames = grantDisplaysOf(rawGrantRepo.findAll());
final var givenContact = givenSomeTemporaryContact("selfregistered-user-drew@hostsharing.org"); final var givenContact = givenSomeTemporaryContact("selfregistered-user-drew@hostsharing.org");
assumeThat(rawRoleRepo.findAll().size()).as("unexpected number of roles created")
.isEqualTo(initialRoleNames.size() + 3);
assumeThat(rawGrantRepo.findAll().size()).as("unexpected number of grants created")
.isEqualTo(initialGrantNames.size() + 7);
// when // when
final var result = jpaAttempt.transacted(() -> { final var result = jpaAttempt.transacted(() -> {

View File

@ -1,6 +1,5 @@
package net.hostsharing.hsadminng.hs.office.contact; package net.hostsharing.hsadminng.hs.office.contact;
import java.util.UUID;
public class TestHsOfficeContact { public class TestHsOfficeContact {

View File

@ -26,7 +26,6 @@ import java.util.UUID;
import static net.hostsharing.test.IsValidUuidMatcher.isUuidValid; import static net.hostsharing.test.IsValidUuidMatcher.isUuidValid;
import static net.hostsharing.test.JsonMatcher.lenientlyEquals; import static net.hostsharing.test.JsonMatcher.lenientlyEquals;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assumptions.assumeThat;
import static org.hamcrest.Matchers.*; import static org.hamcrest.Matchers.*;
@SpringBootTest( @SpringBootTest(
@ -481,7 +480,7 @@ class HsOfficeDebitorControllerAcceptanceTest {
void contactAdminUser_canNotDeleteRelatedDebitor() { void contactAdminUser_canNotDeleteRelatedDebitor() {
context.define("superuser-alex@hostsharing.net"); context.define("superuser-alex@hostsharing.net");
final var givenDebitor = givenSomeTemporaryDebitor(); final var givenDebitor = givenSomeTemporaryDebitor();
assumeThat(givenDebitor.getBillingContact().getLabel()).isEqualTo("forth contact"); assertThat(givenDebitor.getBillingContact().getLabel()).isEqualTo("forth contact");
RestAssured // @formatter:off RestAssured // @formatter:off
.given() .given()
@ -501,7 +500,7 @@ class HsOfficeDebitorControllerAcceptanceTest {
void normalUser_canNotDeleteUnrelatedDebitor() { void normalUser_canNotDeleteUnrelatedDebitor() {
context.define("superuser-alex@hostsharing.net"); context.define("superuser-alex@hostsharing.net");
final var givenDebitor = givenSomeTemporaryDebitor(); final var givenDebitor = givenSomeTemporaryDebitor();
assumeThat(givenDebitor.getBillingContact().getLabel()).isEqualTo("forth contact"); assertThat(givenDebitor.getBillingContact().getLabel()).isEqualTo("forth contact");
RestAssured // @formatter:off RestAssured // @formatter:off
.given() .given()

View File

@ -2,7 +2,6 @@ package net.hostsharing.hsadminng.hs.office.debitor;
import lombok.experimental.UtilityClass; import lombok.experimental.UtilityClass;
import java.util.UUID;
import static net.hostsharing.hsadminng.hs.office.contact.TestHsOfficeContact.TEST_CONTACT; import static net.hostsharing.hsadminng.hs.office.contact.TestHsOfficeContact.TEST_CONTACT;
import static net.hostsharing.hsadminng.hs.office.partner.TestHsOfficePartner.TEST_PARTNER; import static net.hostsharing.hsadminng.hs.office.partner.TestHsOfficePartner.TEST_PARTNER;

View File

@ -32,7 +32,6 @@ import static net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantEntity.grantD
import static net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleEntity.roleNamesOf; import static net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleEntity.roleNamesOf;
import static net.hostsharing.test.JpaAttempt.attempt; import static net.hostsharing.test.JpaAttempt.attempt;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assumptions.assumeThat;
@DataJpaTest @DataJpaTest
@Import( { Context.class, JpaAttempt.class }) @Import( { Context.class, JpaAttempt.class })
@ -327,7 +326,7 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTest {
// when // when
final var result = jpaAttempt.transacted(() -> { final var result = jpaAttempt.transacted(() -> {
context("superuser-alex@hostsharing.net", "hs_office_debitor#10003ThirdOHG-thirdcontact.admin"); context("superuser-alex@hostsharing.net", "hs_office_debitor#10003ThirdOHG-thirdcontact.admin");
assumeThat(membershipRepo.findByUuid(givenMembership.getUuid())).isPresent(); assertThat(membershipRepo.findByUuid(givenMembership.getUuid())).isPresent();
membershipRepo.deleteByUuid(givenMembership.getUuid()); membershipRepo.deleteByUuid(givenMembership.getUuid());
}); });

View File

@ -3,7 +3,6 @@ package net.hostsharing.hsadminng.hs.office.membership;
import com.vladmihalcea.hibernate.type.range.Range; import com.vladmihalcea.hibernate.type.range.Range;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.UUID;
import static net.hostsharing.hsadminng.hs.office.partner.TestHsOfficePartner.TEST_PARTNER; import static net.hostsharing.hsadminng.hs.office.partner.TestHsOfficePartner.TEST_PARTNER;

View File

@ -24,7 +24,6 @@ import java.util.UUID;
import static net.hostsharing.test.IsValidUuidMatcher.isUuidValid; import static net.hostsharing.test.IsValidUuidMatcher.isUuidValid;
import static net.hostsharing.test.JsonMatcher.lenientlyEquals; import static net.hostsharing.test.JsonMatcher.lenientlyEquals;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assumptions.assumeThat;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.startsWith; import static org.hamcrest.Matchers.startsWith;
@ -408,7 +407,7 @@ class HsOfficePartnerControllerAcceptanceTest {
void contactAdminUser_canNotDeleteRelatedPartner() { void contactAdminUser_canNotDeleteRelatedPartner() {
context.define("superuser-alex@hostsharing.net"); context.define("superuser-alex@hostsharing.net");
final var givenPartner = givenSomeTemporaryPartnerBessler(); final var givenPartner = givenSomeTemporaryPartnerBessler();
assumeThat(givenPartner.getContact().getLabel()).isEqualTo("forth contact"); assertThat(givenPartner.getContact().getLabel()).isEqualTo("forth contact");
RestAssured // @formatter:off RestAssured // @formatter:off
.given() .given()
@ -428,7 +427,7 @@ class HsOfficePartnerControllerAcceptanceTest {
void normalUser_canNotDeleteUnrelatedPartner() { void normalUser_canNotDeleteUnrelatedPartner() {
context.define("superuser-alex@hostsharing.net"); context.define("superuser-alex@hostsharing.net");
final var givenPartner = givenSomeTemporaryPartnerBessler(); final var givenPartner = givenSomeTemporaryPartnerBessler();
assumeThat(givenPartner.getContact().getLabel()).isEqualTo("forth contact"); assertThat(givenPartner.getContact().getLabel()).isEqualTo("forth contact");
RestAssured // @formatter:off RestAssured // @formatter:off
.given() .given()

View File

@ -29,7 +29,6 @@ import static net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantEntity.grantD
import static net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleEntity.roleNamesOf; import static net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleEntity.roleNamesOf;
import static net.hostsharing.test.JpaAttempt.attempt; import static net.hostsharing.test.JpaAttempt.attempt;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assumptions.assumeThat;
@DataJpaTest @DataJpaTest
@Import( { Context.class, JpaAttempt.class }) @Import( { Context.class, JpaAttempt.class })
@ -330,7 +329,7 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTest {
// when // when
final var result = jpaAttempt.transacted(() -> { final var result = jpaAttempt.transacted(() -> {
context("person-ErbenBesslerMelBessler@example.com"); context("person-ErbenBesslerMelBessler@example.com");
assumeThat(partnerRepo.findByUuid(givenPartner.getUuid())).isPresent(); assertThat(partnerRepo.findByUuid(givenPartner.getUuid())).isPresent();
partnerRepo.deleteByUuid(givenPartner.getUuid()); partnerRepo.deleteByUuid(givenPartner.getUuid());
}); });
@ -352,10 +351,6 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTest {
final var initialRoleNames = Array.from(roleNamesOf(rawRoleRepo.findAll())); final var initialRoleNames = Array.from(roleNamesOf(rawRoleRepo.findAll()));
final var initialGrantNames = Array.from(grantDisplaysOf(rawGrantRepo.findAll())); final var initialGrantNames = Array.from(grantDisplaysOf(rawGrantRepo.findAll()));
final var givenPartner = givenSomeTemporaryPartnerBessler("twelfth"); final var givenPartner = givenSomeTemporaryPartnerBessler("twelfth");
assumeThat(rawRoleRepo.findAll().size()).as("unexpected number of roles created")
.isEqualTo(initialRoleNames.length + 3);
assumeThat(rawGrantRepo.findAll().size()).as("unexpected number of grants created")
.isEqualTo(initialGrantNames.length + 10);
// when // when
final var result = jpaAttempt.transacted(() -> { final var result = jpaAttempt.transacted(() -> {

View File

@ -3,7 +3,6 @@ package net.hostsharing.hsadminng.hs.office.partner;
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity; import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity; import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity;
import java.util.UUID;
import static net.hostsharing.hsadminng.hs.office.person.HsOfficePersonType.LEGAL; import static net.hostsharing.hsadminng.hs.office.person.HsOfficePersonType.LEGAL;

View File

@ -27,7 +27,6 @@ import static net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantEntity.grantD
import static net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleEntity.roleNamesOf; import static net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleEntity.roleNamesOf;
import static net.hostsharing.test.JpaAttempt.attempt; import static net.hostsharing.test.JpaAttempt.attempt;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assumptions.assumeThat;
@DataJpaTest @DataJpaTest
@Import( { Context.class, JpaAttempt.class }) @Import( { Context.class, JpaAttempt.class })
@ -244,10 +243,6 @@ class HsOfficePersonRepositoryIntegrationTest extends ContextBasedTest {
final var initialRoleNames = roleNamesOf(rawRoleRepo.findAll()); final var initialRoleNames = roleNamesOf(rawRoleRepo.findAll());
final var initialGrantNames = grantDisplaysOf(rawGrantRepo.findAll()); final var initialGrantNames = grantDisplaysOf(rawGrantRepo.findAll());
final var givenPerson = givenSomeTemporaryPerson("selfregistered-user-drew@hostsharing.org"); final var givenPerson = givenSomeTemporaryPerson("selfregistered-user-drew@hostsharing.org");
assumeThat(rawRoleRepo.findAll().size()).as("unexpected number of roles created")
.isEqualTo(initialRoleNames.size() + 3);
assumeThat(rawGrantRepo.findAll().size()).as("unexpected number of grants created")
.isEqualTo(initialGrantNames.size() + 7);
// when // when
final var result = jpaAttempt.transacted(() -> { final var result = jpaAttempt.transacted(() -> {

View File

@ -1,6 +1,5 @@
package net.hostsharing.hsadminng.hs.office.person; package net.hostsharing.hsadminng.hs.office.person;
import java.util.UUID;
public class TestHsOfficePerson { public class TestHsOfficePerson {

View File

@ -25,7 +25,6 @@ import java.util.UUID;
import static net.hostsharing.test.IsValidUuidMatcher.isUuidValid; import static net.hostsharing.test.IsValidUuidMatcher.isUuidValid;
import static net.hostsharing.test.JsonMatcher.lenientlyEquals; import static net.hostsharing.test.JsonMatcher.lenientlyEquals;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assumptions.assumeThat;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.startsWith; import static org.hamcrest.Matchers.startsWith;

View File

@ -1,6 +1,5 @@
package net.hostsharing.hsadminng.hs.office.sepamandate; package net.hostsharing.hsadminng.hs.office.sepamandate;
import com.vladmihalcea.hibernate.type.range.Range;
import net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountEntity; import net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountEntity;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;

View File

@ -31,7 +31,6 @@ import static net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantEntity.grantD
import static net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleEntity.roleNamesOf; import static net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleEntity.roleNamesOf;
import static net.hostsharing.test.JpaAttempt.attempt; import static net.hostsharing.test.JpaAttempt.attempt;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assumptions.assumeThat;
@DataJpaTest @DataJpaTest
@Import({ Context.class, JpaAttempt.class }) @Import({ Context.class, JpaAttempt.class })
@ -346,7 +345,7 @@ class HsOfficeSepaMandateRepositoryIntegrationTest extends ContextBasedTest {
// when // when
final var result = jpaAttempt.transacted(() -> { final var result = jpaAttempt.transacted(() -> {
context("bankaccount-admin@ThirdOHG.example.com"); context("bankaccount-admin@ThirdOHG.example.com");
assumeThat(sepaMandateRepo.findByUuid(givenSepaMandate.getUuid())).isPresent(); assertThat(sepaMandateRepo.findByUuid(givenSepaMandate.getUuid())).isPresent();
sepaMandateRepo.deleteByUuid(givenSepaMandate.getUuid()); sepaMandateRepo.deleteByUuid(givenSepaMandate.getUuid());
}); });

View File

@ -0,0 +1,88 @@
package net.hostsharing.hsadminng.mapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import jakarta.persistence.EntityManager;
import java.util.UUID;
import java.util.function.Function;
import static org.assertj.core.api.Assertions.assertThat;
@DataJpaTest
class PostgresArrayIntegrationTest {
@Autowired
EntityManager em;
@Test
void shouldCreateEmptyArray() {
em.createNativeQuery("""
create or replace function returnEmptyArray()
returns text[]
stable leakproof
language plpgsql as $$
declare
emptyArray text[] = '{}';
begin
return emptyArray;
end; $$;
""").executeUpdate();
final byte[] pgArray = (byte[]) em.createNativeQuery("SELECT returnEmptyArray()", String[].class).getSingleResult();
final String[] result = PostgresArray.fromPostgresArray(pgArray, String.class, Function.identity());
assertThat(result).isEmpty();
}
@Test
void shouldCreateStringArray() {
em.createNativeQuery("""
create or replace function returnStringArray()
returns varchar(63)[]
stable leakproof
language plpgsql as $$
declare
text1 text = 'one';
text2 text = 'two, three';
text3 text = 'four; five';
text4 text = 'say "Hello" to me';
begin
return array[text1, text2, text3, null, text4];
end; $$;
""").executeUpdate();
final byte[] pgArray = (byte[]) em.createNativeQuery("SELECT returnStringArray()", String[].class).getSingleResult();
final String[] result = PostgresArray.fromPostgresArray(pgArray, String.class, Function.identity());
assertThat(result).containsExactly("one", "two, three", "four; five", null, "say \"Hello\" to me");
}
@Test
void shouldCreateUUidArray() {
em.createNativeQuery("""
create or replace function returnUuidArray()
returns uuid[]
stable leakproof
language plpgsql as $$
declare
uuid1 UUID = 'f47ac10b-58cc-4372-a567-0e02b2c3d479';
uuid2 UUID = '6ba7b810-9dad-11d1-80b4-00c04fd430c8';
uuid3 UUID = '01234567-89ab-cdef-0123-456789abcdef';
begin
return ARRAY[uuid1, uuid2, null, uuid3];
end; $$;
""").executeUpdate();
final byte[] pgArray = (byte[]) em.createNativeQuery("SELECT returnUuidArray()", UUID[].class).getSingleResult();
final UUID[] result = PostgresArray.fromPostgresArray(pgArray, UUID.class, UUID::fromString);
assertThat(result).containsExactly(
UUID.fromString("f47ac10b-58cc-4372-a567-0e02b2c3d479"),
UUID.fromString("6ba7b810-9dad-11d1-80b4-00c04fd430c8"),
null,
UUID.fromString("01234567-89ab-cdef-0123-456789abcdef"));
}
}

View File

@ -26,7 +26,6 @@ import java.util.List;
import java.util.UUID; import java.util.UUID;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assumptions.assumeThat;
import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.Matchers.*; import static org.hamcrest.Matchers.*;
@ -343,7 +342,7 @@ class RbacGrantControllerAcceptanceTest extends ContextBasedTest {
} }
private void assumeCreated(final ValidatableResponse response) { private void assumeCreated(final ValidatableResponse response) {
assumeThat(response.extract().response().statusCode()).isEqualTo(201); assertThat(response.extract().response().statusCode()).isEqualTo(201);
} }
class Subject { class Subject {
@ -479,7 +478,7 @@ class RbacGrantControllerAcceptanceTest extends ContextBasedTest {
} }
private void assumeGrantExists(final Subject grantingSubject, final String expectedGrant) { private void assumeGrantExists(final Subject grantingSubject, final String expectedGrant) {
assumeThat(findAllGrantsOf(grantingSubject)) assertThat(findAllGrantsOf(grantingSubject))
.extracting(RbacGrantEntity::toDisplay) .extracting(RbacGrantEntity::toDisplay)
.contains(expectedGrant); .contains(expectedGrant);
} }

View File

@ -25,7 +25,6 @@ import java.util.UUID;
import static net.hostsharing.test.JpaAttempt.attempt; import static net.hostsharing.test.JpaAttempt.attempt;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assumptions.assumeThat;
@DataJpaTest @DataJpaTest
@Import( { Context.class, JpaAttempt.class }) @Import( { Context.class, JpaAttempt.class })
@ -186,9 +185,8 @@ class RbacGrantRepositoryIntegrationTest extends ContextBasedTest {
// when // when
context("customer-admin@xxx.example.com", "test_customer#xxx.admin"); context("customer-admin@xxx.example.com", "test_customer#xxx.admin");
final var revokeAttempt = attempt(em, () -> { final var revokeAttempt = attempt(em, () ->
rbacGrantRepository.deleteByRbacGrantId(grant.getRbacGrantId()); rbacGrantRepository.deleteByRbacGrantId(grant.getRbacGrantId()));
});
// then // then
context("customer-admin@xxx.example.com", "test_customer#xxx.admin"); context("customer-admin@xxx.example.com", "test_customer#xxx.admin");
@ -208,9 +206,8 @@ class RbacGrantRepositoryIntegrationTest extends ContextBasedTest {
// when // when
context("pac-admin-xxx00@xxx.example.com", "test_package#xxx00.admin"); context("pac-admin-xxx00@xxx.example.com", "test_package#xxx00.admin");
final var revokeAttempt = attempt(em, () -> { final var revokeAttempt = attempt(em, () ->
rbacGrantRepository.deleteByRbacGrantId(grant.getRbacGrantId()); rbacGrantRepository.deleteByRbacGrantId(grant.getRbacGrantId()));
});
// then // then
assertThat(revokeAttempt.caughtExceptionsRootCause()).isNull(); assertThat(revokeAttempt.caughtExceptionsRootCause()).isNull();
@ -230,9 +227,8 @@ class RbacGrantRepositoryIntegrationTest extends ContextBasedTest {
// when // when
context("pac-admin-xxx00@xxx.example.com", "test_package#xxx00.admin"); context("pac-admin-xxx00@xxx.example.com", "test_package#xxx00.admin");
final var revokeAttempt = attempt(em, () -> { final var revokeAttempt = attempt(em, () ->
rbacGrantRepository.deleteByRbacGrantId(grant.getRbacGrantId()); rbacGrantRepository.deleteByRbacGrantId(grant.getRbacGrantId()));
});
// then // then
revokeAttempt.assertExceptionWithRootCauseMessage( revokeAttempt.assertExceptionWithRootCauseMessage(
@ -255,10 +251,10 @@ class RbacGrantRepositoryIntegrationTest extends ContextBasedTest {
rbacGrantRepository.save(grant) rbacGrantRepository.save(grant)
); );
assumeThat(grantAttempt.caughtException()).isNull(); assertThat(grantAttempt.caughtException()).isNull();
assumeThat(rawRbacGrantRepository.findAll()) assertThat(rawRbacGrantRepository.findAll())
.extracting(RawRbacGrantEntity::toDisplay) .extracting(RawRbacGrantEntity::toDisplay)
.contains("{ grant role %s to user %s by role %s and assume }".formatted( .contains("{ grant role %s to user %s by %s and assume }".formatted(
with.grantedRole, with.granteeUserName, with.assumedRole with.grantedRole, with.granteeUserName, with.assumedRole
)); ));

View File

@ -61,11 +61,6 @@ class RbacUserRepositoryIntegrationTest extends ContextBasedTest {
assertThat(result.returnedValue()).isNotNull() assertThat(result.returnedValue()).isNotNull()
.extracting(RbacUserEntity::getUuid).isEqualTo(givenUuid); .extracting(RbacUserEntity::getUuid).isEqualTo(givenUuid);
assertThat(rbacUserRepository.findByName(result.returnedValue().getName())).isNotNull(); assertThat(rbacUserRepository.findByName(result.returnedValue().getName())).isNotNull();
// jpaAttempt.transacted(() -> {
// context(givenUser.getName());
// assertThat(em.find(RbacUserEntity.class, givenUser.getUuid()))
// .isNotNull().extracting(RbacUserEntity::getName).isEqualTo(givenUser.getName());
// }).assertSuccessful();
} }
} }
@ -87,9 +82,6 @@ class RbacUserRepositoryIntegrationTest extends ContextBasedTest {
// then the user is deleted // then the user is deleted
result.assertSuccessful(); result.assertSuccessful();
assertThat(rbacUserRepository.findByName(givenUser.getName())).isNull(); assertThat(rbacUserRepository.findByName(givenUser.getName())).isNull();
// jpaAttempt.transacted(() -> {
// assertThat(rbacUserRepository.findByName(givenUser.getName())).isNull();
// }).assertSuccessful();
} }
} }

View File

@ -1,6 +1,5 @@
package net.hostsharing.hsadminng.test.cust; package net.hostsharing.hsadminng.test.cust;
import static java.util.UUID.randomUUID;
public class TestCustomer { public class TestCustomer {

View File

@ -16,7 +16,7 @@ import org.springframework.transaction.annotation.Transactional;
import java.util.UUID; import java.util.UUID;
import static java.lang.String.format; import static java.lang.String.format;
import static org.assertj.core.api.Assumptions.assumeThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
@ -85,7 +85,8 @@ class TestPackageControllerAcceptanceTest {
@Test @Test
void withDescriptionUpdatesDescription() { void withDescriptionUpdatesDescription() {
assumeThat(getDescriptionOfPackage("xxx00")) assertThat(getDescriptionOfPackage("xxx00"))
.as("precondition failed")
.isEqualTo("Here you can add your own description of package xxx00."); .isEqualTo("Here you can add your own description of package xxx00.");
final var randomDescription = RandomStringUtils.randomAlphanumeric(80); final var randomDescription = RandomStringUtils.randomAlphanumeric(80);
@ -117,7 +118,8 @@ class TestPackageControllerAcceptanceTest {
@Test @Test
void withNullDescriptionUpdatesDescriptionToNull() { void withNullDescriptionUpdatesDescriptionToNull() {
assumeThat(getDescriptionOfPackage("xxx01")) assertThat(getDescriptionOfPackage("xxx01"))
.as("precondition failed")
.isEqualTo("Here you can add your own description of package xxx01."); .isEqualTo("Here you can add your own description of package xxx01.");
// @formatter:off // @formatter:off
@ -146,7 +148,8 @@ class TestPackageControllerAcceptanceTest {
@Test @Test
void withoutDescriptionDoesNothing() { void withoutDescriptionDoesNothing() {
assumeThat(getDescriptionOfPackage("xxx02")) assertThat(getDescriptionOfPackage("xxx02"))
.as("precondition failed")
.isEqualTo("Here you can add your own description of package xxx02."); .isEqualTo("Here you can add your own description of package xxx02.");
// @formatter:off // @formatter:off

View File

@ -12,7 +12,6 @@ import java.util.Optional;
import java.util.function.Supplier; import java.util.function.Supplier;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assumptions.assumeThat;
/** /**
* Wraps the 'when' part of a DataJpaTest to improve readability of tests. * Wraps the 'when' part of a DataJpaTest to improve readability of tests.
@ -138,7 +137,7 @@ public class JpaAttempt {
} }
public JpaResult<T> assumeSuccessful() { public JpaResult<T> assumeSuccessful() {
assumeThat(exception).as(firstRootCauseMessageLineOf(exception)).isNull(); assertThat(exception).as(firstRootCauseMessageLineOf(exception)).isNull();
return this; return this;
} }

View File

@ -4,7 +4,7 @@ spring:
platform: postgres platform: postgres
datasource: datasource:
url: jdbc:tc:postgresql:13.7-bullseye:///spring_boot_testcontainers url: jdbc:tc:postgresql:15.5-bookworm:///spring_boot_testcontainers
url-local: jdbc:postgresql://localhost:5432/postgres url-local: jdbc:postgresql://localhost:5432/postgres
username: postgres username: postgres
password: password password: password