diff --git a/.github/changelog.yaml b/.github/changelog.yaml
new file mode 100644
index 0000000..0a5526e
--- /dev/null
+++ b/.github/changelog.yaml
@@ -0,0 +1,13 @@
+sections:
+ - title: Major changes
+ labels:
+ - "release/super-feature"
+ - title: Complete changelog
+ labels:
+ - "bug"
+ - "enhancement"
+ - "dependencies"
+template: |
+ {{ range $section := .Sections }}{{ if $section.Items }}### {{ $section.GetTitle }}{{ range $item := $section.Items }}
+ * [#{{ $item.GetID }}]({{ $item.GetURL }}) - {{ $item.GetTitle }}{{ end }}{{ end }}
+ {{ end }}
\ No newline at end of file
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..64412da
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,8 @@
+version: 2
+updates:
+ - package-ecosystem: maven
+ directory: "/"
+ schedule:
+ interval: daily
+ labels:
+ - dependencies
\ No newline at end of file
diff --git a/.github/workflows/build-branch.yml b/.github/workflows/build-branch.yml
new file mode 100644
index 0000000..7270213
--- /dev/null
+++ b/.github/workflows/build-branch.yml
@@ -0,0 +1,13 @@
+name: Build Feature Branch
+
+on:
+ push:
+ branches:
+ - '**'
+ - '!main'
+ - '!fix/[0-9]+.[0-9]+.x'
+
+jobs:
+ branch:
+ uses: onecx/ci-quarkus/.github/workflows/build-branch.yml@v1
+ secrets: inherit
\ No newline at end of file
diff --git a/.github/workflows/build-pr.yml b/.github/workflows/build-pr.yml
new file mode 100644
index 0000000..acff155
--- /dev/null
+++ b/.github/workflows/build-pr.yml
@@ -0,0 +1,9 @@
+name: Build Pull Request
+
+on:
+ pull_request:
+
+jobs:
+ pr:
+ uses: onecx/ci-quarkus/.github/workflows/build-pr.yml@v1
+ secrets: inherit
\ No newline at end of file
diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml
new file mode 100644
index 0000000..b04a59a
--- /dev/null
+++ b/.github/workflows/build-release.yml
@@ -0,0 +1,9 @@
+name: Build Release
+on:
+ push:
+ tags:
+ - '**'
+jobs:
+ release:
+ uses: onecx/ci-quarkus/.github/workflows/build-release.yml@v1
+ secrets: inherit
\ No newline at end of file
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 0000000..d4cd036
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,23 @@
+name: Build
+
+on:
+ workflow_dispatch:
+ push:
+ branches:
+ - 'main'
+ - 'fix/[0-9]+.[0-9]+.x'
+ paths-ignore:
+ - '.gitignore'
+ - 'CODEOWNERS'
+ - 'LICENSE'
+ - '*.md'
+ - '*.adoc'
+ - '*.txt'
+ - '.all-contributorsrc'
+
+jobs:
+ build:
+ uses: onecx/ci-quarkus/.github/workflows/build.yml@v1
+ secrets: inherit
+ with:
+ helmEventTargetRepository: onecx/onecx-theme
\ No newline at end of file
diff --git a/.github/workflows/create-fix-branch.yml b/.github/workflows/create-fix-branch.yml
new file mode 100644
index 0000000..92af624
--- /dev/null
+++ b/.github/workflows/create-fix-branch.yml
@@ -0,0 +1,7 @@
+name: Create Fix Branch
+on:
+ workflow_dispatch:
+jobs:
+ fix:
+ uses: onecx/ci-common/.github/workflows/create-fix-branch.yml@v1
+ secrets: inherit
\ No newline at end of file
diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml
new file mode 100644
index 0000000..c97eb42
--- /dev/null
+++ b/.github/workflows/create-release.yml
@@ -0,0 +1,7 @@
+name: Create Release Version
+on:
+ workflow_dispatch:
+jobs:
+ release:
+ uses: onecx/ci-common/.github/workflows/create-release.yml@v1
+ secrets: inherit
\ No newline at end of file
diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml
new file mode 100644
index 0000000..3d17922
--- /dev/null
+++ b/.github/workflows/documentation.yml
@@ -0,0 +1,10 @@
+name: Update documentation
+on:
+ push:
+ branches: [ main ]
+ paths:
+ - 'docs/**'
+jobs:
+ release:
+ uses: onecx/ci-common/.github/workflows/documentation.yml@v1
+ secrets: inherit
\ No newline at end of file
diff --git a/.github/workflows/sonar-pr.yml b/.github/workflows/sonar-pr.yml
new file mode 100644
index 0000000..02c3e1e
--- /dev/null
+++ b/.github/workflows/sonar-pr.yml
@@ -0,0 +1,12 @@
+name: Sonar Pull Request
+
+on:
+ workflow_run:
+ workflows: ["Build Pull Request"]
+ types:
+ - completed
+
+jobs:
+ pr:
+ uses: onecx/ci-quarkus/.github/workflows/quarkus-pr-sonar.yml@v1
+ secrets: inherit
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..5122ec0
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,44 @@
+#Maven
+target/
+pom.xml.tag
+pom.xml.releaseBackup
+pom.xml.versionsBackup
+release.properties
+.flattened-pom.xml
+
+# Eclipse
+.project
+.classpath
+.settings/
+bin/
+
+# IntelliJ
+.idea
+*.ipr
+*.iml
+*.iws
+
+# NetBeans
+nb-configuration.xml
+
+# Visual Studio Code
+.vscode
+.factorypath
+
+# OSX
+.DS_Store
+
+# Vim
+*.swp
+*.swo
+
+# patch
+*.orig
+*.rej
+
+# Local environment
+.env
+*.sh
+
+# Plugin directory
+/.quarkus/cli/plugins/
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..bcc8e80
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,172 @@
+
+
+ 4.0.0
+
+ io.github.onecx
+ onecx-quarkus3-parent
+ 0.25.0
+
+
+ onecx-user-profile-bff
+ 999-SNAPSHOT
+
+
+
+ io.quarkus
+ quarkus-resteasy-reactive
+
+
+ io.quarkus
+ quarkus-smallrye-openapi
+
+
+ io.quarkus
+ quarkus-resteasy-reactive-jackson
+
+
+ io.quarkus
+ quarkus-smallrye-health
+
+
+ io.quarkiverse.openapi.generator
+ quarkus-openapi-generator
+
+
+ io.quarkus
+ quarkus-rest-client-reactive-jackson
+
+
+
+ org.tkit.quarkus.lib
+ tkit-quarkus-log-cdi
+
+
+ org.tkit.quarkus.lib
+ tkit-quarkus-log-rs
+
+
+ org.tkit.quarkus.lib
+ tkit-quarkus-log-json
+
+
+ org.tkit.quarkus.lib
+ tkit-quarkus-rest
+
+
+ org.tkit.quarkus.lib
+ tkit-quarkus-jpa
+
+
+ org.mapstruct
+ mapstruct
+
+
+ io.quarkus
+ quarkus-hibernate-validator
+
+
+ org.tkit.quarkus.lib
+ tkit-quarkus-rest-context
+
+
+
+ io.quarkiverse.mockserver
+ quarkus-mockserver
+ provided
+
+
+
+
+ org.eclipse.microprofile.jwt
+ microprofile-jwt-auth-api
+ test
+
+
+ io.quarkiverse.mockserver
+ quarkus-mockserver-test
+
+
+ io.swagger.parser.v3
+ swagger-parser
+
+
+ test
+
+
+ io.swagger.parser.v3
+ swagger-parser
+ test
+
+
+
+
+
+
+ org.openapitools
+ openapi-generator-maven-plugin
+
+
+ internal
+
+ generate
+
+
+ src/main/openapi/openapi-bff.yaml
+ gen.io.github.onecx.user.profile.bff.rs.internal
+ gen.io.github.onecx.user.profile.bff.rs.internal.model
+
+
+
+
+ jaxrs-spec
+ ApiService
+ DTO
+ false
+ false
+ false
+ false
+ false
+ true
+ quarkus
+
+ /
+ false
+ true
+ true
+ true
+ true
+ true
+ java8
+ true
+ true
+ false
+ true
+
+
+
+
+ com.googlecode.maven-download-plugin
+ download-maven-plugin
+
+
+ user-profile-svc-internal
+ generate-resources
+
+ wget
+
+
+
+ https://raw.githubusercontent.com/onecx/onecx-user-profile-svc/main/src/main/openapi/onecx-userprofile-internal-openapi.yaml
+
+ target/tmp/openapi
+ onecx-userprofile-internal-openapi.yaml
+ true
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/docker/Dockerfile.jvm b/src/main/docker/Dockerfile.jvm
new file mode 100644
index 0000000..a554421
--- /dev/null
+++ b/src/main/docker/Dockerfile.jvm
@@ -0,0 +1,13 @@
+FROM registry.access.redhat.com/ubi9/openjdk-17:1.15
+
+ENV LANGUAGE='en_US:en'
+
+COPY --chown=185 target/quarkus-app/lib/ /deployments/lib/
+COPY --chown=185 target/quarkus-app/*.jar /deployments/
+COPY --chown=185 target/quarkus-app/app/ /deployments/app/
+COPY --chown=185 target/quarkus-app/quarkus/ /deployments/quarkus/
+
+EXPOSE 8080
+USER 185
+ENV JAVA_OPTS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager"
+ENV JAVA_APP_JAR="/deployments/quarkus-run.jar"
\ No newline at end of file
diff --git a/src/main/docker/Dockerfile.native b/src/main/docker/Dockerfile.native
new file mode 100644
index 0000000..c97f217
--- /dev/null
+++ b/src/main/docker/Dockerfile.native
@@ -0,0 +1,11 @@
+FROM registry.access.redhat.com/ubi9/ubi-minimal:9.2
+WORKDIR /work/
+RUN chown 1001 /work \
+ && chmod "g+rwX" /work \
+ && chown 1001:root /work
+COPY --chown=1001:root target/*-runner /work/application
+
+EXPOSE 8080
+USER 1001
+
+CMD ["./application", "-Dquarkus.http.host=0.0.0.0"]
\ No newline at end of file
diff --git a/src/main/helm/Chart.yaml b/src/main/helm/Chart.yaml
new file mode 100644
index 0000000..0560b2a
--- /dev/null
+++ b/src/main/helm/Chart.yaml
@@ -0,0 +1,17 @@
+apiVersion: v2
+name: onecx-user-profile-bff
+version: 0.0.0
+appVersion: 0.0.0
+description: Onecx user profile bff
+keywords:
+ - user profile
+sources:
+ - https://github.com/onecx/onecx-user-profile-bff
+maintainers:
+ - name: Tkit Developer
+ email: tkit_dev@1000kit.org
+dependencies:
+ - name: helm-quarkus-app
+ alias: app
+ version: ^0
+ repository: oci://ghcr.io/onecx/charts
\ No newline at end of file
diff --git a/src/main/helm/values.yaml b/src/main/helm/values.yaml
new file mode 100644
index 0000000..c65d440
--- /dev/null
+++ b/src/main/helm/values.yaml
@@ -0,0 +1,4 @@
+app:
+ name: bff
+ image:
+ repository: "onecx/onecx-user-profile-bff"
diff --git a/src/main/java/io/github/onecx/user/profile/bff/rs/controllers/UserAvatarRestController.java b/src/main/java/io/github/onecx/user/profile/bff/rs/controllers/UserAvatarRestController.java
new file mode 100644
index 0000000..a983042
--- /dev/null
+++ b/src/main/java/io/github/onecx/user/profile/bff/rs/controllers/UserAvatarRestController.java
@@ -0,0 +1,93 @@
+package io.github.onecx.user.profile.bff.rs.controllers;
+
+import java.io.File;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import jakarta.transaction.Transactional;
+import jakarta.ws.rs.WebApplicationException;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
+
+import org.eclipse.microprofile.rest.client.inject.RestClient;
+import org.jboss.resteasy.reactive.RestResponse;
+import org.jboss.resteasy.reactive.server.ServerExceptionMapper;
+import org.tkit.quarkus.log.cdi.LogService;
+
+import gen.io.github.onecx.user.profile.bff.clients.api.AvatarApi;
+import gen.io.github.onecx.user.profile.bff.clients.model.ImageInfo;
+import gen.io.github.onecx.user.profile.bff.clients.model.ProblemDetailResponse;
+import gen.io.github.onecx.user.profile.bff.rs.internal.UserAvatarApiService;
+import io.github.onecx.user.profile.bff.rs.mappers.ExceptionMapper;
+import io.github.onecx.user.profile.bff.rs.mappers.ProblemDetailMapper;
+import io.github.onecx.user.profile.bff.rs.mappers.UserProfileMapper;
+
+@ApplicationScoped
+@Transactional(value = Transactional.TxType.NOT_SUPPORTED)
+@LogService
+public class UserAvatarRestController implements UserAvatarApiService {
+
+ @Inject
+ @RestClient
+ AvatarApi client;
+
+ @Inject
+ UserProfileMapper mapper;
+
+ @Inject
+ ExceptionMapper exceptionMapper;
+
+ @Inject
+ ProblemDetailMapper problemDetailMapper;
+
+ @Override
+ public Response deleteUserAvatar() {
+ try (Response response = client.deleteUserAvatar()) {
+ return Response.status(response.getStatus()).build();
+ }
+ }
+
+ @Override
+ public Response getUserAvatar(String id) {
+ try (Response response = client.getUserAvatar(id)) {
+ var byteResponse = response.readEntity(byte[].class);
+ return Response.status(response.getStatus())
+ .entity(byteResponse).build();
+ }
+ }
+
+ @Override
+ public Response getUserAvatarInfo() {
+ try (Response response = client.getUserAvatarInfo()) {
+ var imageInfo = response.readEntity(ImageInfo.class);
+ return Response.status(response.getStatus())
+ .entity(mapper.map(imageInfo)).build();
+ }
+ }
+
+ @Override
+ public Response uploadAvatar(File body) {
+ try (Response response = client.uploadAvatar(body)) {
+ var imageInfo = response.readEntity(ImageInfo.class);
+ return Response.status(response.getStatus())
+ .entity(mapper.map(imageInfo)).build();
+ }
+ }
+
+ @ServerExceptionMapper
+ public Response restException(WebApplicationException ex) {
+ // if client response is bad request remap bad request to DTO
+ if (ex.getResponse().getStatus() == RestResponse.StatusCode.BAD_REQUEST) {
+ try {
+ var clientError = ex.getResponse().readEntity(ProblemDetailResponse.class);
+ return Response.status(ex.getResponse().getStatus()).type(MediaType.APPLICATION_JSON_TYPE)
+ .entity(problemDetailMapper.map(clientError)).build();
+ } catch (Exception e) {
+ // ignore error the bad request has not problem detail response object
+ return Response.status(ex.getResponse().getStatus()).build();
+ }
+ } else {
+ return Response.status(ex.getResponse().getStatus()).build();
+ }
+ }
+}
diff --git a/src/main/java/io/github/onecx/user/profile/bff/rs/controllers/UserProfileAdminRestController.java b/src/main/java/io/github/onecx/user/profile/bff/rs/controllers/UserProfileAdminRestController.java
new file mode 100644
index 0000000..d49903c
--- /dev/null
+++ b/src/main/java/io/github/onecx/user/profile/bff/rs/controllers/UserProfileAdminRestController.java
@@ -0,0 +1,96 @@
+package io.github.onecx.user.profile.bff.rs.controllers;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import jakarta.transaction.Transactional;
+import jakarta.validation.ConstraintViolationException;
+import jakarta.ws.rs.WebApplicationException;
+import jakarta.ws.rs.core.Response;
+
+import org.eclipse.microprofile.rest.client.inject.RestClient;
+import org.jboss.resteasy.reactive.RestResponse;
+import org.jboss.resteasy.reactive.server.ServerExceptionMapper;
+import org.tkit.quarkus.log.cdi.LogService;
+
+import gen.io.github.onecx.user.profile.bff.clients.api.UserProfileAdminApi;
+import gen.io.github.onecx.user.profile.bff.clients.model.ProblemDetailResponse;
+import gen.io.github.onecx.user.profile.bff.clients.model.UserProfile;
+import gen.io.github.onecx.user.profile.bff.clients.model.UserProfilePageResult;
+import gen.io.github.onecx.user.profile.bff.rs.internal.UserProfileAdminApiService;
+import gen.io.github.onecx.user.profile.bff.rs.internal.model.ProblemDetailResponseDTO;
+import gen.io.github.onecx.user.profile.bff.rs.internal.model.UpdateUserPersonRequestDTO;
+import gen.io.github.onecx.user.profile.bff.rs.internal.model.UserPersonCriteriaDTO;
+import io.github.onecx.user.profile.bff.rs.mappers.ExceptionMapper;
+import io.github.onecx.user.profile.bff.rs.mappers.ProblemDetailMapper;
+import io.github.onecx.user.profile.bff.rs.mappers.UserProfileMapper;
+
+@ApplicationScoped
+@Transactional(value = Transactional.TxType.NOT_SUPPORTED)
+@LogService
+public class UserProfileAdminRestController implements UserProfileAdminApiService {
+
+ @Inject
+ @RestClient
+ UserProfileAdminApi client;
+
+ @Inject
+ UserProfileMapper mapper;
+
+ @Inject
+ ExceptionMapper exceptionMapper;
+
+ @Inject
+ ProblemDetailMapper problemDetailMapper;
+
+ @Override
+ public Response deleteUserProfile(String id) {
+ try (Response response = client.deleteUserProfile(id)) {
+ return Response.status(response.getStatus()).build();
+ }
+ }
+
+ @Override
+ public Response getUserProfile(String id) {
+ try (Response response = client.getUserProfile(id)) {
+ var userProfile = response.readEntity(UserProfile.class);
+ return Response.status(response.getStatus()).entity(mapper.map(userProfile)).build();
+ }
+ }
+
+ @Override
+ public Response searchUserProfile(UserPersonCriteriaDTO userPersonCriteriaDTO) {
+ try (Response response = client.searchUserProfile(mapper.map(userPersonCriteriaDTO))) {
+ var userProfilePageResult = response.readEntity(UserProfilePageResult.class);
+ return Response.status(response.getStatus()).entity(mapper.map(userProfilePageResult)).build();
+ }
+ }
+
+ @Override
+ public Response updateUserProfile(String id, UpdateUserPersonRequestDTO updateUserPersonRequestDTO) {
+ try (Response response = client.updateUserProfile(id, mapper.map(updateUserPersonRequestDTO))) {
+ return Response.status(response.getStatus()).build();
+ }
+ }
+
+ @ServerExceptionMapper
+ public RestResponse constraint(ConstraintViolationException ex) {
+ return exceptionMapper.constraint(ex);
+ }
+
+ @ServerExceptionMapper
+ public Response restException(WebApplicationException ex) {
+ // if client response is bad request remap bad request to DTO
+ if (ex.getResponse().getStatus() == RestResponse.StatusCode.BAD_REQUEST) {
+ try {
+ var clientError = ex.getResponse().readEntity(ProblemDetailResponse.class);
+ return Response.status(ex.getResponse().getStatus())
+ .entity(problemDetailMapper.map(clientError)).build();
+ } catch (Exception e) {
+ // ignore error the bad request has not problem detail response object
+ return Response.status(ex.getResponse().getStatus()).build();
+ }
+ } else {
+ return Response.status(ex.getResponse().getStatus()).build();
+ }
+ }
+}
diff --git a/src/main/java/io/github/onecx/user/profile/bff/rs/controllers/UserProfileRestController.java b/src/main/java/io/github/onecx/user/profile/bff/rs/controllers/UserProfileRestController.java
new file mode 100644
index 0000000..e064bce
--- /dev/null
+++ b/src/main/java/io/github/onecx/user/profile/bff/rs/controllers/UserProfileRestController.java
@@ -0,0 +1,152 @@
+package io.github.onecx.user.profile.bff.rs.controllers;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import jakarta.transaction.Transactional;
+import jakarta.validation.ConstraintViolationException;
+import jakarta.ws.rs.WebApplicationException;
+import jakarta.ws.rs.core.Response;
+
+import org.eclipse.microprofile.rest.client.inject.RestClient;
+import org.jboss.resteasy.reactive.RestResponse;
+import org.jboss.resteasy.reactive.server.ServerExceptionMapper;
+import org.tkit.quarkus.log.cdi.LogService;
+
+import gen.io.github.onecx.user.profile.bff.clients.api.UserProfileApi;
+import gen.io.github.onecx.user.profile.bff.clients.model.*;
+import gen.io.github.onecx.user.profile.bff.rs.internal.UserProfileApiService;
+import gen.io.github.onecx.user.profile.bff.rs.internal.model.CreateUserPreferenceDTO;
+import gen.io.github.onecx.user.profile.bff.rs.internal.model.ProblemDetailResponseDTO;
+import gen.io.github.onecx.user.profile.bff.rs.internal.model.UpdateUserPersonDTO;
+import gen.io.github.onecx.user.profile.bff.rs.internal.model.UpdateUserSettingsDTO;
+import io.github.onecx.user.profile.bff.rs.mappers.ExceptionMapper;
+import io.github.onecx.user.profile.bff.rs.mappers.ProblemDetailMapper;
+import io.github.onecx.user.profile.bff.rs.mappers.UserProfileMapper;
+
+@ApplicationScoped
+@Transactional(value = Transactional.TxType.NOT_SUPPORTED)
+@LogService
+public class UserProfileRestController implements UserProfileApiService {
+
+ @Inject
+ @RestClient
+ UserProfileApi client;
+
+ @Inject
+ UserProfileMapper mapper;
+
+ @Inject
+ ExceptionMapper exceptionMapper;
+
+ @Inject
+ ProblemDetailMapper problemDetailMapper;
+
+ @Override
+ public Response createUserPreference(CreateUserPreferenceDTO createUserPreferenceDTO) {
+ try (Response response = client.createUserPreference(mapper.map(createUserPreferenceDTO))) {
+ var preferenceResponse = response.readEntity(UserPreference.class);
+ return Response.status(response.getStatus())
+ .location(response.getLocation())
+ .entity(mapper.map(preferenceResponse)).build();
+ }
+ }
+
+ @Override
+ public Response deleteMyUserProfile() {
+ try (Response response = client.deleteMyUserProfile()) {
+ return Response.status(response.getStatus()).build();
+ }
+ }
+
+ @Override
+ public Response deleteUserPreference(String id) {
+ try (Response response = client.deleteUserPreference(id)) {
+ return Response.status(response.getStatus()).build();
+ }
+ }
+
+ @Override
+ public Response getMyUserProfile() {
+ try (Response response = client.getMyUserProfile()) {
+ var profileResponse = response.readEntity(UserProfile.class);
+ return Response.status(response.getStatus())
+ .entity(mapper.map(profileResponse)).build();
+ }
+ }
+
+ @Override
+ public Response getUserPerson() {
+ try (Response response = client.getUserPerson()) {
+ var userPerson = response.readEntity(UserPerson.class);
+ return Response.status(response.getStatus())
+ .entity(mapper.map(userPerson)).build();
+ }
+ }
+
+ @Override
+ public Response getUserPreference() {
+ try (Response response = client.getUserPreference()) {
+ var userPreferences = response.readEntity(UserPreferences.class);
+ return Response.status(response.getStatus())
+ .entity(mapper.map(userPreferences)).build();
+ }
+ }
+
+ @Override
+ public Response getUserSettings() {
+ try (Response response = client.getUserSettings()) {
+ var userProfileAccountSettings = response.readEntity(UserProfileAccountSettings.class);
+ return Response.status(response.getStatus())
+ .entity(mapper.map(userProfileAccountSettings)).build();
+ }
+ }
+
+ @Override
+ public Response updateUserPerson(UpdateUserPersonDTO updateUserPersonDTO) {
+ try (Response response = client.updateUserPerson(mapper.map(updateUserPersonDTO))) {
+ var userPerson = response.readEntity(UserPerson.class);
+ return Response.status(response.getStatus())
+ .entity(mapper.map(userPerson)).build();
+ }
+ }
+
+ @Override
+ public Response updateUserPreference(String id, String body) {
+ try (Response response = client.updateUserPreference(id, body)) {
+ var userPreference = response.readEntity(UserPreference.class);
+ return Response.status(response.getStatus())
+ .entity(mapper.map(userPreference)).build();
+ }
+ }
+
+ @Override
+ public Response updateUserSettings(UpdateUserSettingsDTO updateUserSettingsDTO) {
+ try (Response response = client.updateUserSettings(mapper.map(updateUserSettingsDTO))) {
+ var userProfileAccountSettings = response.readEntity(UserProfileAccountSettings.class);
+ return Response.status(response.getStatus())
+ .entity(mapper.map(userProfileAccountSettings)).build();
+ }
+ }
+
+ @ServerExceptionMapper
+ public RestResponse constraint(ConstraintViolationException ex) {
+ return exceptionMapper.constraint(ex);
+ }
+
+ @ServerExceptionMapper
+ public Response restException(WebApplicationException ex) {
+ // if client response is bad request remap bad request to DTO
+ if (ex.getResponse().getStatus() == RestResponse.StatusCode.BAD_REQUEST) {
+ try {
+ var clientError = ex.getResponse().readEntity(ProblemDetailResponse.class);
+ return Response.status(ex.getResponse().getStatus())
+ .entity(problemDetailMapper.map(clientError)).build();
+ } catch (Exception e) {
+ // ignore error the bad request has not problem detail response object
+ return Response.status(ex.getResponse().getStatus()).build();
+ }
+ } else {
+ return Response.status(ex.getResponse().getStatus()).build();
+ }
+ }
+}
diff --git a/src/main/java/io/github/onecx/user/profile/bff/rs/mappers/ExceptionMapper.java b/src/main/java/io/github/onecx/user/profile/bff/rs/mappers/ExceptionMapper.java
new file mode 100644
index 0000000..12be4d7
--- /dev/null
+++ b/src/main/java/io/github/onecx/user/profile/bff/rs/mappers/ExceptionMapper.java
@@ -0,0 +1,60 @@
+package io.github.onecx.user.profile.bff.rs.mappers;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import jakarta.validation.ConstraintViolation;
+import jakarta.validation.ConstraintViolationException;
+import jakarta.validation.Path;
+import jakarta.ws.rs.core.Response;
+
+import org.jboss.resteasy.reactive.RestResponse;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.tkit.quarkus.rs.mappers.OffsetDateTimeMapper;
+
+import gen.io.github.onecx.user.profile.bff.rs.internal.model.ProblemDetailInvalidParamDTO;
+import gen.io.github.onecx.user.profile.bff.rs.internal.model.ProblemDetailParamDTO;
+import gen.io.github.onecx.user.profile.bff.rs.internal.model.ProblemDetailResponseDTO;
+
+@Mapper(uses = { OffsetDateTimeMapper.class })
+public interface ExceptionMapper {
+
+ default RestResponse constraint(ConstraintViolationException ex) {
+ var dto = exception("CONSTRAINT_VIOLATIONS", ex.getMessage());
+ dto.setInvalidParams(createErrorValidationResponse(ex.getConstraintViolations()));
+ return RestResponse.status(Response.Status.BAD_REQUEST, dto);
+ }
+
+ @Mapping(target = "removeParamsItem", ignore = true)
+ @Mapping(target = "params", ignore = true)
+ @Mapping(target = "invalidParams", ignore = true)
+ @Mapping(target = "removeInvalidParamsItem", ignore = true)
+ ProblemDetailResponseDTO exception(String errorCode, String detail);
+
+ default List map(Map params) {
+ if (params == null) {
+ return List.of();
+ }
+ return params.entrySet().stream().map(e -> {
+ var item = new ProblemDetailParamDTO();
+ item.setKey(e.getKey());
+ if (e.getValue() != null) {
+ item.setValue(e.getValue().toString());
+ }
+ return item;
+ }).toList();
+ }
+
+ List createErrorValidationResponse(
+ Set> constraintViolation);
+
+ @Mapping(target = "name", source = "propertyPath")
+ @Mapping(target = "message", source = "message")
+ ProblemDetailInvalidParamDTO createError(ConstraintViolation> constraintViolation);
+
+ default String mapPath(Path path) {
+ return path.toString();
+ }
+}
diff --git a/src/main/java/io/github/onecx/user/profile/bff/rs/mappers/ProblemDetailMapper.java b/src/main/java/io/github/onecx/user/profile/bff/rs/mappers/ProblemDetailMapper.java
new file mode 100644
index 0000000..5e298b5
--- /dev/null
+++ b/src/main/java/io/github/onecx/user/profile/bff/rs/mappers/ProblemDetailMapper.java
@@ -0,0 +1,17 @@
+package io.github.onecx.user.profile.bff.rs.mappers;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.tkit.quarkus.rs.mappers.OffsetDateTimeMapper;
+
+import gen.io.github.onecx.user.profile.bff.clients.model.ProblemDetailResponse;
+import gen.io.github.onecx.user.profile.bff.rs.internal.model.ProblemDetailResponseDTO;
+
+@Mapper(uses = { OffsetDateTimeMapper.class })
+public interface ProblemDetailMapper {
+
+ @Mapping(target = "removeParamsItem", ignore = true)
+ @Mapping(target = "removeInvalidParamsItem", ignore = true)
+ ProblemDetailResponseDTO map(ProblemDetailResponse problemDetailResponse);
+
+}
diff --git a/src/main/java/io/github/onecx/user/profile/bff/rs/mappers/UserProfileMapper.java b/src/main/java/io/github/onecx/user/profile/bff/rs/mappers/UserProfileMapper.java
new file mode 100644
index 0000000..8acde3f
--- /dev/null
+++ b/src/main/java/io/github/onecx/user/profile/bff/rs/mappers/UserProfileMapper.java
@@ -0,0 +1,38 @@
+package io.github.onecx.user.profile.bff.rs.mappers;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.tkit.quarkus.rs.mappers.OffsetDateTimeMapper;
+
+import gen.io.github.onecx.user.profile.bff.clients.model.*;
+import gen.io.github.onecx.user.profile.bff.rs.internal.model.*;
+
+@Mapper(uses = { OffsetDateTimeMapper.class })
+public interface UserProfileMapper {
+
+ UserProfileDTO map(UserProfile userProfile);
+
+ UserPersonCriteria map(UserPersonCriteriaDTO userPersonCriteriaDTO);
+
+ @Mapping(target = "removeStreamItem", ignore = true)
+ UserProfilePageResultDTO map(UserProfilePageResult userProfilePageResult);
+
+ UpdateUserPersonRequest map(UpdateUserPersonRequestDTO updateUserPersonRequestDTO);
+
+ CreateUserPreference map(CreateUserPreferenceDTO createUserPreferenceDTO);
+
+ UserPreferenceDTO map(UserPreference preferenceResponse);
+
+ UserPersonDTO map(UserPerson userPerson);
+
+ @Mapping(target = "removePreferencesItem", ignore = true)
+ UserPreferencesDTO map(UserPreferences userPreferences);
+
+ UserProfileAccountSettingsDTO map(UserProfileAccountSettings userProfileAccountSettings);
+
+ UpdateUserPersonRequest map(UpdateUserPersonDTO updateUserPersonDTO);
+
+ UpdateUserSettings map(UpdateUserSettingsDTO updateUserSettingsDTO);
+
+ ImageInfoDTO map(ImageInfo imageInfo);
+}
diff --git a/src/main/openapi/openapi-bff.yaml b/src/main/openapi/openapi-bff.yaml
new file mode 100644
index 0000000..d77bcc9
--- /dev/null
+++ b/src/main/openapi/openapi-bff.yaml
@@ -0,0 +1,678 @@
+---
+openapi: 3.0.3
+info:
+ title: onecx-user-profile-bff
+ description: OneCx user profile Bff
+ version: "1.0"
+servers:
+ - url: http://onecx-user-profile-bff:8080/
+tags:
+ - name: userProfile
+ - name: userProfileAdmin
+ - name: userAvatar
+paths:
+ /userProfile/me:
+ get:
+ tags:
+ - userProfile
+ description: Load user profile for current user
+ operationId: getMyUserProfile
+ responses:
+ "200":
+ description: User profile of current user
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/UserProfile'
+ "400":
+ description: Bad request
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ProblemDetailResponse'
+ delete:
+ tags:
+ - userProfile
+ description: Delete user profile for current user
+ operationId: deleteMyUserProfile
+ responses:
+ "204":
+ description: OK
+ "400":
+ description: Internal Server Error
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ProblemDetailResponse'
+ /userProfile/me/preferences:
+ get:
+ tags:
+ - userProfile
+ description: Load user preferences for current user
+ operationId: getUserPreference
+ responses:
+ "200":
+ description: User profile of current user
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/UserPreferences'
+ "400":
+ description: Bad request
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ProblemDetailResponse'
+ post:
+ tags:
+ - userProfile
+ description: Create user preference
+ operationId: createUserPreference
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/CreateUserPreference'
+ responses:
+ "201":
+ description: OK
+ headers:
+ Location:
+ required: true
+ schema:
+ type: string
+ format: url
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/UserPreference'
+ "400":
+ description: Bad request
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ProblemDetailResponse'
+ /userProfile/me/preferences/{id}:
+ patch:
+ tags:
+ - userProfile
+ description: Update preference value
+ operationId: updateUserPreference
+ parameters:
+ - $ref: '#/components/parameters/id'
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: string
+ responses:
+ "200":
+ description: OK
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/UserPreference'
+ "400":
+ description: Bad request
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ProblemDetailResponse'
+ "404":
+ description: User preference not found
+ delete:
+ tags:
+ - userProfile
+ description: Delete preference by id for current user
+ operationId: deleteUserPreference
+ parameters:
+ - $ref: '#/components/parameters/id'
+ responses:
+ "204":
+ description: OK
+ "400":
+ description: Internal Server Error
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ProblemDetailResponse'
+ /userProfile/me/person:
+ get:
+ tags:
+ - userProfile
+ description: Load user person for current user
+ operationId: getUserPerson
+ responses:
+ "200":
+ description: User person of current user
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/UserPerson'
+ "400":
+ description: Bad request
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ProblemDetailResponse'
+ put:
+ tags:
+ - userProfile
+ description: Update person information
+ operationId: updateUserPerson
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/UpdateUserPerson'
+ responses:
+ "200":
+ description: OK
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/UserPerson'
+ "400":
+ description: Bad request
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ProblemDetailResponse'
+ /userProfile/me/settings:
+ get:
+ tags:
+ - userProfile
+ description: Load user profile account settings for current user
+ operationId: getUserSettings
+ responses:
+ "200":
+ description: User profile account settings of current user
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/UserProfileAccountSettings'
+ "400":
+ description: Bad request
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ProblemDetailResponse'
+ put:
+ tags:
+ - userProfile
+ description: Update user account settings
+ operationId: updateUserSettings
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/UpdateUserSettings'
+ responses:
+ "200":
+ description: OK
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/UserProfileAccountSettings'
+ "400":
+ description: Bad request
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ProblemDetailResponse'
+ /userProfile/me/avatar:
+ get:
+ tags:
+ - userAvatar
+ description: Get user avatar info
+ operationId: getUserAvatarInfo
+ responses:
+ "200":
+ description: OK
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ImageInfo'
+ "404":
+ description: User avatar not found
+ put:
+ tags:
+ - userAvatar
+ description: Upload user avatar
+ operationId: uploadAvatar
+ requestBody:
+ required: true
+ content:
+ 'image/*':
+ schema:
+ type: string
+ format: binary
+ maxLength: 10
+ responses:
+ '200':
+ description: OK
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ImageInfo'
+ "400":
+ description: Bad request
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ProblemDetailResponse'
+ delete:
+ tags:
+ - userAvatar
+ description: Delete user's avatar
+ operationId: deleteUserAvatar
+ responses:
+ '204':
+ description: No Content
+ "400":
+ description: Bad request
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ProblemDetailResponse'
+ /userProfile/me/avatar/{id}:
+ get:
+ tags:
+ - userAvatar
+ description: Get the image of user's avatar
+ operationId: getUserAvatar
+ parameters:
+ - $ref: '#/components/parameters/id'
+ responses:
+ "200":
+ description: OK
+ content:
+ application/octet-stream:
+ schema:
+ type: string
+ format: binary
+ "400":
+ description: Bad request
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ProblemDetailResponse'
+ "404":
+ description: Not found
+ /userProfiles/search:
+ post:
+ tags:
+ - userProfileAdmin
+ description: Search for user profiles by search criteria
+ operationId: searchUserProfile
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/UserPersonCriteria'
+ responses:
+ "200":
+ description: Corresponding user profiles
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/UserProfilePageResult'
+ "400":
+ description: Bad request
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ProblemDetailResponse'
+ /userProfiles/{id}:
+ get:
+ tags:
+ - userProfileAdmin
+ description: Return user profile by id
+ operationId: getUserProfile
+ parameters:
+ - $ref: '#/components/parameters/id'
+ responses:
+ "200":
+ description: OK
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/UserProfile'
+ "404":
+ description: Not found
+ put:
+ tags:
+ - userProfileAdmin
+ description: Update workspace by ID
+ operationId: updateUserProfile
+ parameters:
+ - $ref: '#/components/parameters/id'
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/UpdateUserPersonRequest'
+ responses:
+ "204":
+ description: User profile updated
+ "400":
+ description: Bad request
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ProblemDetailResponse'
+ "404":
+ description: User profile not found
+ delete:
+ tags:
+ - userProfileAdmin
+ description: Delete user profile by ID
+ operationId: deleteUserProfile
+ parameters:
+ - $ref: '#/components/parameters/id'
+ responses:
+ "204":
+ description: No Content
+ "400":
+ description: Internal Server Error
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ProblemDetailResponse'
+components:
+ schemas:
+ UserPersonCriteria:
+ type: object
+ properties:
+ userId:
+ type: string
+ firstName:
+ type: string
+ lastName:
+ type: string
+ email:
+ type: string
+ pageNumber:
+ format: int32
+ description: The number of page.
+ default: 0
+ type: integer
+ pageSize:
+ format: int32
+ description: The size of page
+ default: 10
+ type: integer
+ UpdateUserPersonRequest:
+ type: object
+ properties:
+ modificationCount:
+ format: int32
+ type: integer
+ firstName:
+ type: string
+ lastName:
+ type: string
+ displayName:
+ type: string
+ email:
+ type: string
+ address:
+ $ref: '#/components/schemas/UserPersonAddress'
+ phone:
+ $ref: '#/components/schemas/UserPersonPhone'
+ UserProfilePageResult:
+ type: object
+ properties:
+ totalElements:
+ format: int64
+ description: The total elements in the resource.
+ type: integer
+ number:
+ format: int32
+ type: integer
+ size:
+ format: int32
+ type: integer
+ totalPages:
+ format: int64
+ type: integer
+ stream:
+ type: array
+ items:
+ $ref: '#/components/schemas/UserProfile'
+ CreateUserPreference:
+ type: object
+ properties:
+ applicationId:
+ type: string
+ name:
+ type: string
+ description:
+ type: string
+ value:
+ type: string
+ UserPreferences:
+ type: object
+ properties:
+ preferences:
+ items:
+ $ref: '#/components/schemas/UserPreference'
+ UserPreference:
+ type: object
+ properties:
+ id:
+ type: string
+ applicationId:
+ type: string
+ name:
+ type: string
+ description:
+ type: string
+ value:
+ type: string
+ UserProfile:
+ type: object
+ properties:
+ id:
+ type: string
+ modificationCount:
+ format: int32
+ type: integer
+ creationDate:
+ $ref: '#/components/schemas/OffsetDateTime'
+ creationUser:
+ type: string
+ modificationDate:
+ $ref: '#/components/schemas/OffsetDateTime'
+ modificationUser:
+ type: string
+ userId:
+ type: string
+ identityProvider:
+ type: string
+ identityProviderId:
+ type: string
+ organization:
+ type: string
+ person:
+ $ref: '#/components/schemas/UserPerson'
+ accountSettings:
+ $ref: '#/components/schemas/UserProfileAccountSettings'
+ UserPerson:
+ type: object
+ properties:
+ modificationCount:
+ format: int32
+ type: integer
+ firstName:
+ type: string
+ lastName:
+ type: string
+ displayName:
+ type: string
+ email:
+ type: string
+ address:
+ $ref: '#/components/schemas/UserPersonAddress'
+ phone:
+ $ref: '#/components/schemas/UserPersonPhone'
+ UserPersonAddress:
+ type: object
+ properties:
+ street:
+ type: string
+ streetNo:
+ type: string
+ city:
+ type: string
+ country:
+ type: string
+ postalCode:
+ type: string
+ UserPersonPhone:
+ type: object
+ properties:
+ number:
+ type: string
+ type:
+ $ref: '#/components/schemas/PhoneType'
+ PhoneType:
+ enum:
+ - MOBILE
+ - LANDLINE
+ type: string
+ UpdateUserPerson:
+ type: object
+ properties:
+ modificationCount:
+ format: int32
+ type: integer
+ firstName:
+ type: string
+ lastName:
+ type: string
+ displayName:
+ type: string
+ email:
+ type: string
+ address:
+ $ref: '#/components/schemas/UserPersonAddress'
+ phone:
+ $ref: '#/components/schemas/UserPersonPhone'
+ UpdateUserSettings:
+ type: object
+ properties:
+ modificationCount:
+ format: int32
+ type: integer
+ hideMyProfile:
+ type: boolean
+ locale:
+ type: string
+ timezone:
+ type: string
+ menuMode:
+ $ref: '#/components/schemas/MenuMode'
+ colorScheme:
+ $ref: '#/components/schemas/ColorScheme'
+ UserProfileAccountSettings:
+ type: object
+ properties:
+ modificationCount:
+ format: int32
+ type: integer
+ hideMyProfile:
+ type: boolean
+ locale:
+ type: string
+ timezone:
+ type: string
+ menuMode:
+ $ref: '#/components/schemas/MenuMode'
+ colorScheme:
+ $ref: '#/components/schemas/ColorScheme'
+ MenuMode:
+ enum:
+ - STATIC
+ - HORIZONTAL
+ - OVERLAY
+ - SLIM
+ - SLIMPLUS
+ type: string
+ ColorScheme:
+ enum:
+ - AUTO
+ - LIGHT
+ - DARK
+ type: string
+ ImageInfo:
+ type: object
+ properties:
+ modificationCount:
+ format: int32
+ type: integer
+ creationDate:
+ $ref: '#/components/schemas/OffsetDateTime'
+ creationUser:
+ type: string
+ modificationDate:
+ $ref: '#/components/schemas/OffsetDateTime'
+ modificationUser:
+ type: string
+ id:
+ type: string
+ userUploaded:
+ description: flag whether Image was uploaded by user
+ type: boolean
+ imageUrl:
+ type: string
+ example: http://tkit-portal/data/afcc5d0d-6509-497a-8125-614f82b106ae
+ smallImageUrl:
+ type: string
+ example: http://tkit-portal/data/afcc5d0d-6509-497a-8125-614f82asb106ae
+ ProblemDetailResponse:
+ type: object
+ properties:
+ errorCode:
+ type: string
+ detail:
+ type: string
+ params:
+ type: array
+ items:
+ $ref: '#/components/schemas/ProblemDetailParam'
+ invalidParams:
+ type: array
+ items:
+ $ref: '#/components/schemas/ProblemDetailInvalidParam'
+ ProblemDetailParam:
+ type: object
+ properties:
+ key:
+ type: string
+ value:
+ type: string
+ ProblemDetailInvalidParam:
+ type: object
+ properties:
+ name:
+ type: string
+ message:
+ type: string
+ OffsetDateTime:
+ format: date-time
+ type: string
+ example: 2022-03-10T12:15:50-04:00
+ parameters:
+ id:
+ in: path
+ name: id
+ required: true
+ schema:
+ type: string
\ No newline at end of file
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
new file mode 100644
index 0000000..dde30ad
--- /dev/null
+++ b/src/main/resources/application.properties
@@ -0,0 +1,40 @@
+# DEFAULT
+tkit.log.json.enabled=false
+tkit.log.json.pretty-print=false
+tkit.log.json.print-details=false
+
+# propagate the apm-principal-token from requests we receive
+org.eclipse.microprofile.rest.client.propagateHeaders=apm-principal-token
+
+# PROD
+%prod.quarkus.rest-client.onecx_user_profile_svc.url=http://onecx-user-profile-svc:8080
+
+# DEV
+
+# BUILD
+quarkus.openapi-generator.codegen.input-base-dir=target/tmp/openapi
+quarkus.openapi-generator.codegen.spec.onecx_userprofile_internal_openapi_yaml.config-key=onecx_user_profile_svc
+quarkus.openapi-generator.codegen.spec.onecx_userprofile_internal_openapi_yaml.base-package=gen.io.github.onecx.user.profile.bff.clients
+quarkus.openapi-generator.codegen.spec.onecx_userprofile_internal_openapi_yaml.return-response=true
+quarkus.openapi-generator.codegen.spec.onecx_userprofile_internal_openapi_yaml.additional-api-type-annotations=@org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders;
+quarkus.openapi-generator.codegen.spec.onecx_userprofile_v1_openapi_yaml.config-key=onecx_user_profile_svc
+quarkus.openapi-generator.codegen.spec.onecx_userprofile_v1_openapi_yaml.base-package=gen.io.github.onecx.user.profile.bff.clients
+quarkus.openapi-generator.codegen.spec.onecx_userprofile_v1_openapi_yaml.return-response=true
+quarkus.openapi-generator.codegen.spec.onecx_userprofile_v1_openapi_yaml.additional-api-type-annotations=@org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders;
+
+# INTEGRATION TEST
+quarkus.test.integration-test-profile=test
+
+# TEST
+%test.quarkus.http.test-port=0
+%test.tkit.rs.context.tenant-id.mock.claim-org-id=orgId
+%test.tkit.rs.context.tenant-id.mock.token-header-param=apm-principal-token
+%test.tkit.log.json.enabled=false
+%test.quarkus.mockserver.devservices.config-class-path=true
+%test.quarkus.mockserver.devservices.config-file=/mockserver.properties
+%test.quarkus.mockserver.devservices.config-dir=/mockserver
+%test.quarkus.mockserver.devservices.log=false
+%test.quarkus.mockserver.devservices.reuse=true
+%test.quarkus.rest-client.onecx_user_profile_svc.url=${quarkus.mockserver.endpoint}
+
+# PIPE CONFIG
diff --git a/src/test/java/io/github/onecx/user/profile/bff/rs/AbstractTest.java b/src/test/java/io/github/onecx/user/profile/bff/rs/AbstractTest.java
new file mode 100644
index 0000000..b9784c5
--- /dev/null
+++ b/src/test/java/io/github/onecx/user/profile/bff/rs/AbstractTest.java
@@ -0,0 +1,58 @@
+package io.github.onecx.user.profile.bff.rs;
+
+import java.security.PrivateKey;
+
+import jakarta.json.Json;
+import jakarta.json.JsonObjectBuilder;
+
+import org.eclipse.microprofile.config.ConfigProvider;
+import org.eclipse.microprofile.jwt.Claims;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+
+import io.quarkiverse.mockserver.test.MockServerTestResource;
+import io.quarkus.test.common.QuarkusTestResource;
+import io.restassured.RestAssured;
+import io.restassured.config.ObjectMapperConfig;
+import io.restassured.config.RestAssuredConfig;
+import io.smallrye.jwt.build.Jwt;
+import io.smallrye.jwt.util.KeyUtils;
+
+@QuarkusTestResource(MockServerTestResource.class)
+public abstract class AbstractTest {
+
+ protected static final String APM_HEADER_PARAM = ConfigProvider.getConfig()
+ .getValue("%test.tkit.rs.context.tenant-id.mock.token-header-param", String.class);
+
+ protected static final String CLAIMS_ORG_ID = ConfigProvider.getConfig()
+ .getValue("%test.tkit.rs.context.tenant-id.mock.claim-org-id", String.class);;
+
+ static {
+ RestAssured.config = RestAssuredConfig.config().objectMapperConfig(
+ ObjectMapperConfig.objectMapperConfig().jackson2ObjectMapperFactory(
+ (cls, charset) -> {
+ ObjectMapper objectMapper = new ObjectMapper();
+ objectMapper.registerModule(new JavaTimeModule());
+ objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
+ return objectMapper;
+ }));
+ }
+
+ protected static String createToken(String userId, String orgId) {
+ try {
+ String userName = userId != null ? userId : "test-user";
+ String organizationId = orgId != null ? orgId : "org1";
+ JsonObjectBuilder claims = Json.createObjectBuilder();
+ claims.add(Claims.preferred_username.name(), userName);
+ claims.add(Claims.sub.name(), userName);
+ claims.add(CLAIMS_ORG_ID, organizationId);
+ PrivateKey privateKey = KeyUtils.generateKeyPair(2048).getPrivate();
+ return Jwt.claims(claims.build()).sign(privateKey);
+ } catch (Exception ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+
+}
diff --git a/src/test/java/io/github/onecx/user/profile/bff/rs/UserAvatarRestControllerIT.java b/src/test/java/io/github/onecx/user/profile/bff/rs/UserAvatarRestControllerIT.java
new file mode 100644
index 0000000..a279af0
--- /dev/null
+++ b/src/test/java/io/github/onecx/user/profile/bff/rs/UserAvatarRestControllerIT.java
@@ -0,0 +1,7 @@
+package io.github.onecx.user.profile.bff.rs;
+
+import io.quarkus.test.junit.QuarkusIntegrationTest;
+
+@QuarkusIntegrationTest
+public class UserAvatarRestControllerIT extends UserAvatarRestControllerTest {
+}
diff --git a/src/test/java/io/github/onecx/user/profile/bff/rs/UserAvatarRestControllerTest.java b/src/test/java/io/github/onecx/user/profile/bff/rs/UserAvatarRestControllerTest.java
new file mode 100644
index 0000000..3aad2d7
--- /dev/null
+++ b/src/test/java/io/github/onecx/user/profile/bff/rs/UserAvatarRestControllerTest.java
@@ -0,0 +1,301 @@
+package io.github.onecx.user.profile.bff.rs;
+
+import static io.restassured.RestAssured.given;
+import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON;
+import static jakarta.ws.rs.core.Response.Status.OK;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockserver.model.HttpRequest.request;
+import static org.mockserver.model.HttpResponse.response;
+import static org.mockserver.model.NottableString.not;
+import static org.mockserver.model.NottableString.string;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+
+import jakarta.ws.rs.HttpMethod;
+import jakarta.ws.rs.core.Response;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockserver.client.MockServerClient;
+import org.mockserver.model.JsonBody;
+import org.mockserver.model.MediaType;
+import org.tkit.quarkus.log.cdi.LogService;
+
+import gen.io.github.onecx.user.profile.bff.clients.model.*;
+import gen.io.github.onecx.user.profile.bff.rs.internal.model.*;
+import io.github.onecx.user.profile.bff.rs.controllers.UserAvatarRestController;
+import io.quarkiverse.mockserver.test.InjectMockServerClient;
+import io.quarkus.test.common.http.TestHTTPEndpoint;
+import io.quarkus.test.junit.QuarkusTest;
+
+@QuarkusTest
+@LogService
+@TestHTTPEndpoint(UserAvatarRestController.class)
+class UserAvatarRestControllerTest extends AbstractTest {
+
+ @InjectMockServerClient
+ MockServerClient mockServerClient;
+
+ @BeforeEach
+ void resetMockServer() {
+ mockServerClient.reset();
+ // throw 500 if the apm-principal token is not there
+ mockServerClient.when(request().withHeader(not(APM_HEADER_PARAM), string(".*")))
+ .withPriority(999)
+ .respond(httpRequest -> response().withStatusCode(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()));
+
+ }
+
+ @Test
+ void deleteUserAvatarTest() {
+ given()
+ .when()
+ .contentType(APPLICATION_JSON)
+ .delete()
+ .then()
+ .statusCode(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode());
+
+ mockServerClient.when(request().withPath("/internal/userProfile/me/avatar")
+ .withMethod(HttpMethod.DELETE))
+ .withPriority(100)
+ .respond(httpRequest -> response().withStatusCode(Response.Status.NO_CONTENT.getStatusCode()));
+
+ given()
+ .when()
+ .header(APM_HEADER_PARAM, createToken("user1", null))
+ .contentType(APPLICATION_JSON)
+ .delete()
+ .then()
+ .statusCode(Response.Status.NO_CONTENT.getStatusCode());
+
+ ProblemDetailResponse problem = new ProblemDetailResponse();
+ problem.setErrorCode("MANUAL_ERROR");
+ problem.setDetail("Manual detail of error");
+ mockServerClient.when(request().withPath("/internal/userProfile/me/avatar")
+ .withMethod(HttpMethod.DELETE))
+ .withPriority(200)
+ .respond(httpRequest -> response().withStatusCode(Response.Status.BAD_REQUEST.getStatusCode())
+ .withContentType(MediaType.APPLICATION_JSON)
+ .withBody(JsonBody.json(problem)));
+
+ var error = given()
+ .when()
+ .header(APM_HEADER_PARAM, createToken("user1", null))
+ .contentType(APPLICATION_JSON)
+ .delete()
+ .then()
+ .statusCode(Response.Status.BAD_REQUEST.getStatusCode())
+ .extract().as(ProblemDetailResponseDTO.class);
+
+ assertThat(error.getErrorCode()).isEqualTo(problem.getErrorCode());
+ assertThat(error.getDetail()).isEqualTo(problem.getDetail());
+ }
+
+ @Test
+ void getUserAvatarTest() throws IOException {
+ File avatar = new File("src/test/resources/data/avatar_test.jpg");
+ byte[] bytes = Files.readAllBytes(avatar.toPath());
+ given()
+ .when()
+ .contentType(APPLICATION_JSON)
+ .get("/avatarId1")
+ .then()
+ .statusCode(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode());
+
+ mockServerClient.when(request().withPath("/internal/userProfile/me/avatar/avatarId1")
+ .withMethod(HttpMethod.GET))
+ .withPriority(100)
+ .respond(httpRequest -> response().withStatusCode(Response.Status.OK.getStatusCode())
+ .withContentType(MediaType.APPLICATION_OCTET_STREAM)
+ .withBody(bytes));
+
+ var avatarByteArray = given()
+ .when()
+ .header(APM_HEADER_PARAM, createToken("user1", null))
+ .get("/avatarId1")
+ .then()
+ .contentType("application/octet-stream")
+ .statusCode(OK.getStatusCode())
+ .extract().asByteArray();
+
+ assertThat(avatarByteArray).isNotNull().isEqualTo(bytes);
+
+ ProblemDetailResponse problem = new ProblemDetailResponse();
+ problem.setErrorCode("MANUAL_ERROR");
+ problem.setDetail("Manual detail of error");
+ mockServerClient.when(request().withPath("/internal/userProfile/me/avatar/avatarId1")
+ .withMethod(HttpMethod.GET))
+ .withPriority(200)
+ .respond(httpRequest -> response().withStatusCode(Response.Status.BAD_REQUEST.getStatusCode())
+ .withContentType(MediaType.APPLICATION_JSON)
+ .withBody(JsonBody.json(problem)));
+
+ var error = given()
+ .when()
+ .header(APM_HEADER_PARAM, createToken("user1", null))
+ .get("/avatarId1")
+ .then()
+ .contentType(APPLICATION_JSON)
+ .statusCode(Response.Status.BAD_REQUEST.getStatusCode())
+ .log().all()
+ .extract().as(ProblemDetailResponseDTO.class);
+
+ assertThat(error.getErrorCode()).isEqualTo(problem.getErrorCode());
+ assertThat(error.getDetail()).isEqualTo(problem.getDetail());
+
+ mockServerClient.when(request().withPath("/internal/userProfile/me/avatar/avatarId2")
+ .withMethod(HttpMethod.GET))
+ .withPriority(200)
+ .respond(httpRequest -> response().withStatusCode(Response.Status.NOT_FOUND.getStatusCode()));
+
+ given()
+ .when()
+ .header(APM_HEADER_PARAM, createToken("user1", null))
+ .contentType(APPLICATION_JSON)
+ .get("/avatarId2")
+ .then()
+ .statusCode(Response.Status.NOT_FOUND.getStatusCode());
+ }
+
+ @Test
+ void getUserAvatarInfoTest() {
+ given()
+ .when()
+ .contentType(APPLICATION_JSON)
+ .get()
+ .then()
+ .statusCode(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode());
+
+ ImageInfo info = new ImageInfo();
+ info.setId("avatarId1");
+ info.setUserUploaded(Boolean.FALSE);
+ info.setImageUrl("/image/url/big/avatar");
+ info.setSmallImageUrl("/image/url/small/avatar");
+ mockServerClient.when(request().withPath("/internal/userProfile/me/avatar")
+ .withMethod(HttpMethod.GET))
+ .withPriority(100)
+ .respond(httpRequest -> response().withStatusCode(Response.Status.OK.getStatusCode())
+ .withContentType(MediaType.APPLICATION_JSON)
+ .withBody(JsonBody.json(info)));
+
+ var response = given()
+ .when()
+ .header(APM_HEADER_PARAM, createToken("user1", null))
+ .contentType(APPLICATION_JSON)
+ .get()
+ .then()
+ .contentType(APPLICATION_JSON)
+ .statusCode(Response.Status.OK.getStatusCode())
+ .extract().as(ImageInfoDTO.class);
+
+ assertThat(response).isNotNull();
+ assertThat(response.getImageUrl()).isEqualTo(info.getImageUrl());
+ assertThat(response.getSmallImageUrl()).isEqualTo(info.getSmallImageUrl());
+
+ ProblemDetailResponse problem = new ProblemDetailResponse();
+ problem.setErrorCode("MANUAL_ERROR");
+ problem.setDetail("Manual detail of error");
+ mockServerClient.when(request().withPath("/internal/userProfile/me/avatar")
+ .withMethod(HttpMethod.GET))
+ .withPriority(200)
+ .respond(httpRequest -> response().withStatusCode(Response.Status.BAD_REQUEST.getStatusCode())
+ .withContentType(MediaType.APPLICATION_JSON)
+ .withBody(JsonBody.json(problem)));
+
+ var error = given()
+ .when()
+ .header(APM_HEADER_PARAM, createToken("user1", null))
+ .contentType(APPLICATION_JSON)
+ .get()
+ .then()
+ .statusCode(Response.Status.BAD_REQUEST.getStatusCode())
+ .extract().as(ProblemDetailResponseDTO.class);
+
+ assertThat(error.getErrorCode()).isEqualTo(problem.getErrorCode());
+ assertThat(error.getDetail()).isEqualTo(problem.getDetail());
+ }
+
+ @Test
+ void uploadAvatarTest() {
+ File avatar = new File("src/test/resources/data/avatar_test.jpg");
+ given()
+ .when()
+ .contentType("image/jpg")
+ .body(avatar)
+ .put()
+ .then()
+ .statusCode(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode());
+
+ ImageInfo info = new ImageInfo();
+ info.setId("avatarId1");
+ info.setUserUploaded(Boolean.FALSE);
+ info.setImageUrl("/image/url/big/avatar");
+ info.setSmallImageUrl("/image/url/small/avatar");
+ mockServerClient.when(request().withPath("/internal/userProfile/me/avatar")
+ .withMethod(HttpMethod.PUT))
+ .withPriority(100)
+ .respond(httpRequest -> response().withStatusCode(Response.Status.OK.getStatusCode())
+ .withContentType(MediaType.APPLICATION_JSON)
+ .withBody(JsonBody.json(info)));
+
+ var response = given()
+ .when()
+ .header(APM_HEADER_PARAM, createToken("user1", null))
+ .contentType("image/jpg")
+ .body(avatar)
+ .put()
+ .then()
+ .contentType(APPLICATION_JSON)
+ .statusCode(Response.Status.OK.getStatusCode())
+ .extract().as(ImageInfoDTO.class);
+
+ assertThat(response).isNotNull();
+ assertThat(response.getImageUrl()).isEqualTo(info.getImageUrl());
+ assertThat(response.getSmallImageUrl()).isEqualTo(info.getSmallImageUrl());
+
+ ProblemDetailResponse problem = new ProblemDetailResponse();
+ problem.setErrorCode("MANUAL_ERROR");
+ problem.setDetail("Manual detail of error");
+ mockServerClient.when(request().withPath("/internal/userProfile/me/avatar")
+ .withMethod(HttpMethod.PUT))
+ .withPriority(200)
+ .respond(httpRequest -> response().withStatusCode(Response.Status.BAD_REQUEST.getStatusCode())
+ .withContentType(MediaType.APPLICATION_JSON)
+ .withBody(JsonBody.json(problem)));
+
+ var error = given()
+ .when()
+ .header(APM_HEADER_PARAM, createToken("user1", null))
+ .contentType("image/jpg")
+ .body(avatar)
+ .put()
+ .then()
+ .statusCode(Response.Status.BAD_REQUEST.getStatusCode())
+ .extract().as(ProblemDetailResponseDTO.class);
+
+ assertThat(error.getErrorCode()).isEqualTo(problem.getErrorCode());
+ assertThat(error.getDetail()).isEqualTo(problem.getDetail());
+ }
+
+ @Test
+ void testBadRequestWithoutProblemResponse() {
+ mockServerClient.when(request().withPath("/internal/userProfile/me/avatar")
+ .withMethod(HttpMethod.GET))
+ .withPriority(200)
+ .respond(httpRequest -> response().withStatusCode(Response.Status.BAD_REQUEST.getStatusCode()));
+
+ var response = given()
+ .when()
+ .header(APM_HEADER_PARAM, createToken("user1", null))
+ .contentType(APPLICATION_JSON)
+ .get();
+
+ response.then()
+ .statusCode(Response.Status.BAD_REQUEST.getStatusCode());
+
+ assertThat(response.getBody().prettyPrint()).isEmpty();
+ }
+
+}
diff --git a/src/test/java/io/github/onecx/user/profile/bff/rs/UserProfileAdminRestControllerIT.java b/src/test/java/io/github/onecx/user/profile/bff/rs/UserProfileAdminRestControllerIT.java
new file mode 100644
index 0000000..6ab52c4
--- /dev/null
+++ b/src/test/java/io/github/onecx/user/profile/bff/rs/UserProfileAdminRestControllerIT.java
@@ -0,0 +1,7 @@
+package io.github.onecx.user.profile.bff.rs;
+
+import io.quarkus.test.junit.QuarkusIntegrationTest;
+
+@QuarkusIntegrationTest
+public class UserProfileAdminRestControllerIT extends UserProfileAdminRestControllerTest {
+}
diff --git a/src/test/java/io/github/onecx/user/profile/bff/rs/UserProfileAdminRestControllerTest.java b/src/test/java/io/github/onecx/user/profile/bff/rs/UserProfileAdminRestControllerTest.java
new file mode 100644
index 0000000..4de2b70
--- /dev/null
+++ b/src/test/java/io/github/onecx/user/profile/bff/rs/UserProfileAdminRestControllerTest.java
@@ -0,0 +1,339 @@
+package io.github.onecx.user.profile.bff.rs;
+
+import static io.restassured.RestAssured.given;
+import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockserver.model.HttpRequest.request;
+import static org.mockserver.model.HttpResponse.response;
+import static org.mockserver.model.NottableString.not;
+import static org.mockserver.model.NottableString.string;
+
+import java.util.Arrays;
+import java.util.List;
+
+import jakarta.ws.rs.HttpMethod;
+import jakarta.ws.rs.core.Response;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockserver.client.MockServerClient;
+import org.mockserver.model.JsonBody;
+import org.mockserver.model.MediaType;
+import org.tkit.quarkus.log.cdi.LogService;
+
+import gen.io.github.onecx.user.profile.bff.clients.model.*;
+import gen.io.github.onecx.user.profile.bff.rs.internal.model.*;
+import io.github.onecx.user.profile.bff.rs.controllers.UserProfileAdminRestController;
+import io.quarkiverse.mockserver.test.InjectMockServerClient;
+import io.quarkus.test.common.http.TestHTTPEndpoint;
+import io.quarkus.test.junit.QuarkusTest;
+
+@QuarkusTest
+@LogService
+@TestHTTPEndpoint(UserProfileAdminRestController.class)
+class UserProfileAdminRestControllerTest extends AbstractTest {
+
+ @InjectMockServerClient
+ MockServerClient mockServerClient;
+
+ @BeforeEach
+ void resetMockServer() {
+ mockServerClient.reset();
+ // throw 500 if the apm-principal token is not there
+ mockServerClient.when(request().withHeader(not(APM_HEADER_PARAM), string(".*")))
+ .withPriority(999)
+ .respond(httpRequest -> response().withStatusCode(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()));
+
+ }
+
+ @Test
+ void deleteUserProfileTest() {
+ mockServerClient.when(request().withPath("/internal/userProfiles/user1")
+ .withMethod(HttpMethod.DELETE))
+ .withPriority(100)
+ .respond(httpRequest -> response().withStatusCode(Response.Status.NO_CONTENT.getStatusCode()));
+
+ // Test with header apm-principal-token
+ given()
+ .when()
+ .pathParam("id", "user1")
+ .header(APM_HEADER_PARAM, createToken("user1", null))
+ .delete("/{id}")
+ .then()
+ .statusCode(Response.Status.NO_CONTENT.getStatusCode());
+
+ // Test without header apm-principal-token
+ given()
+ .when()
+ .pathParam("id", "user1")
+ .delete("/{id}")
+ .then()
+ .statusCode(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode());
+ }
+
+ @Test
+ void getUserProfileTest() {
+ // user2 not found
+ mockServerClient.when(request().withPath("/internal/userProfiles/user2")
+ .withMethod(HttpMethod.GET))
+ .withPriority(100)
+ .respond(httpRequest -> response().withStatusCode(Response.Status.NOT_FOUND.getStatusCode()));
+
+ // user1 exists
+ UserProfile userProfile = new UserProfile();
+ userProfile.setUserId("user1");
+ userProfile.setOrganization("capgemini");
+ userProfile.setIdentityProvider("database");
+ userProfile.setIdentityProviderId("db");
+ var person = new UserPerson();
+ userProfile.setPerson(person);
+ person.setDisplayName("Capgemini super user");
+ person.setEmail("cap@capgemini.com");
+ person.setFirstName("Superuser");
+ person.setLastName("Capgeminius");
+ mockServerClient.when(request().withPath("/internal/userProfiles/user1")
+ .withMethod(HttpMethod.GET))
+ .withPriority(100)
+ .respond(httpRequest -> response().withStatusCode(Response.Status.OK.getStatusCode())
+ .withContentType(MediaType.APPLICATION_JSON)
+ .withBody(JsonBody.json(userProfile)));
+
+ // Test without header apm-principal-token
+ given()
+ .when()
+ .pathParam("id", "user1")
+ .get("/{id}")
+ .then()
+ .statusCode(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode());
+
+ // found for user1
+ var response = given()
+ .when()
+ .header(APM_HEADER_PARAM, createToken("user1", null))
+ .pathParam("id", "user1")
+ .get("/{id}")
+ .then()
+ .statusCode(Response.Status.OK.getStatusCode())
+ .extract().as(UserProfileDTO.class);
+
+ assertThat(response).isNotNull();
+ assertThat(response.getUserId()).isEqualTo(userProfile.getUserId());
+ assertThat(response.getPerson().getEmail()).isEqualTo(userProfile.getPerson().getEmail());
+
+ // not found for user 2
+ given()
+ .when()
+ .header(APM_HEADER_PARAM, createToken("user2", null))
+ .pathParam("id", "user2")
+ .get("/{id}")
+ .then()
+ .statusCode(Response.Status.NOT_FOUND.getStatusCode());
+ }
+
+ @Test
+ void searchUserProfileErrorTest() {
+ UserPersonCriteria criteria = new UserPersonCriteria();
+ ProblemDetailResponse problemDetailResponse = new ProblemDetailResponse();
+ problemDetailResponse.setErrorCode("CONSTRAINT_VIOLATIONS");
+ problemDetailResponse.setDetail("searchUserProfile.userPersonCriteriaDTO: must not be null");
+ mockServerClient.when(request().withPath("/internal/userProfiles/search").withBody(JsonBody.json(criteria)))
+ .withPriority(999)
+ .respond(httpRequest -> response().withStatusCode(Response.Status.BAD_REQUEST.getStatusCode())
+ .withContentType(MediaType.APPLICATION_JSON)
+ .withBody(JsonBody.json(problemDetailResponse)));
+
+ // Test without apm
+ given()
+ .when()
+ .contentType(APPLICATION_JSON)
+ .body(new UserPersonCriteriaDTO())
+ .post("/search")
+ .then()
+ .statusCode(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode());
+
+ // without criteria
+ given()
+ .when()
+ .contentType(APPLICATION_JSON)
+ .header(APM_HEADER_PARAM, createToken("user1", null))
+ .post("/search")
+ .then()
+ .statusCode(Response.Status.BAD_REQUEST.getStatusCode())
+ .contentType(APPLICATION_JSON);
+
+ // with empty criteria
+ var error = given()
+ .when()
+ .contentType(APPLICATION_JSON)
+ .header(APM_HEADER_PARAM, createToken("user1", null))
+ .body(new UserPersonCriteriaDTO())
+ .post("/search")
+ .then()
+ .statusCode(Response.Status.BAD_REQUEST.getStatusCode())
+ .contentType(APPLICATION_JSON)
+ .extract().as(ProblemDetailResponseDTO.class);
+
+ assertThat(error).isNotNull();
+ assertThat(error.getErrorCode()).isEqualTo(problemDetailResponse.getErrorCode());
+
+ }
+
+ @Test
+ void searchUserProfileTest() {
+ UserPersonCriteria criteria = new UserPersonCriteria();
+ criteria.setUserId("user1");
+ criteria.setEmail("*cap.de");
+ UserProfilePageResult result = new UserProfilePageResult();
+ result.setSize(2);
+ UserProfile up1 = new UserProfile();
+ up1.setUserId("user1");
+ up1.setId("u1");
+ UserProfileAccountSettings as1 = new UserProfileAccountSettings();
+ as1.setColorScheme(ColorScheme.AUTO);
+ as1.setMenuMode(MenuMode.SLIMPLUS);
+ up1.setAccountSettings(as1);
+ UserPerson person1 = new UserPerson();
+ person1.setDisplayName("User 1");
+ person1.setEmail("user1@cap.de");
+ up1.setPerson(person1);
+
+ UserProfile up2 = new UserProfile();
+ up2.setUserId("user1");
+ up2.setId("u1");
+ UserProfileAccountSettings as2 = new UserProfileAccountSettings();
+ as2.setColorScheme(ColorScheme.AUTO);
+ as2.setMenuMode(MenuMode.SLIMPLUS);
+ up2.setAccountSettings(as2);
+ UserPerson person2 = new UserPerson();
+ person2.setDisplayName("User 1");
+ person2.setEmail("user1@cap.de");
+ up2.setPerson(person2);
+
+ List profileList = Arrays.asList(up1, up2);
+ result.setStream(profileList);
+ mockServerClient.when(request().withPath("/internal/userProfiles/search").withBody(JsonBody.json(criteria)))
+ .withPriority(100)
+ .respond(httpRequest -> response().withStatusCode(Response.Status.OK.getStatusCode())
+ .withContentType(MediaType.APPLICATION_JSON)
+ .withBody(JsonBody.json(result)));
+ UserPersonCriteriaDTO criteriaDTO = new UserPersonCriteriaDTO();
+ criteriaDTO.setUserId("user1");
+ criteriaDTO.setEmail("*cap.de");
+ var response = given()
+ .when()
+ .contentType(APPLICATION_JSON)
+ .header(APM_HEADER_PARAM, createToken("user1", null))
+ .body(criteriaDTO)
+ .post("/search")
+ .then()
+ .statusCode(Response.Status.OK.getStatusCode())
+ .contentType(APPLICATION_JSON)
+ .extract().as(UserProfilePageResultDTO.class);
+
+ assertThat(response).isNotNull();
+ assertThat(response.getSize()).isEqualTo(2);
+ assertThat(response.getStream().get(1).getPerson().getEmail()).isEqualTo(person2.getEmail());
+ }
+
+ @Test
+ void updateUserProfileTest() {
+ mockServerClient.when(request().withPath("/internal/userProfiles/user2"))
+ .withPriority(100)
+ .respond(httpRequest -> response().withStatusCode(Response.Status.NOT_FOUND.getStatusCode()));
+ mockServerClient.when(request().withPath("/internal/userProfiles/user3"))
+ .withPriority(100)
+ .respond(httpRequest -> response().withStatusCode(Response.Status.BAD_REQUEST.getStatusCode()));
+ ProblemDetailResponse problemDetailResponse = new ProblemDetailResponse();
+ problemDetailResponse.setErrorCode("CONSTRAINT_VIOLATIONS");
+ problemDetailResponse.setDetail("searchUserProfile.userPersonCriteriaDTO: must not be null");
+ mockServerClient.when(request().withPath("/internal/userProfiles/user4"))
+ .withPriority(100)
+ .respond(httpRequest -> response().withStatusCode(Response.Status.BAD_REQUEST.getStatusCode())
+ .withContentType(MediaType.APPLICATION_JSON)
+ .withBody(JsonBody.json(problemDetailResponse)));
+ mockServerClient.when(request().withPath("/internal/userProfiles/user1"))
+ .withPriority(100)
+ .respond(httpRequest -> response().withStatusCode(Response.Status.NO_CONTENT.getStatusCode()));
+
+ UpdateUserPersonRequestDTO update = new UpdateUserPersonRequestDTO();
+ UserPersonAddressDTO address = new UserPersonAddressDTO();
+ address.setStreetNo("10");
+ address.setCity("Muenich");
+ update.setAddress(address);
+ update.setDisplayName("User 1");
+ update.setEmail("user1@cap.de");
+ update.setModificationCount(2);
+
+ // Test without apm
+ given()
+ .when()
+ .contentType(APPLICATION_JSON)
+ .body(new UserPersonCriteriaDTO())
+ .put("/user1")
+ .then()
+ .statusCode(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode());
+
+ given()
+ .when()
+ .header(APM_HEADER_PARAM, createToken("user2", null))
+ .contentType(APPLICATION_JSON)
+ .body(update)
+ .put("/user2")
+ .then()
+ .statusCode(Response.Status.NOT_FOUND.getStatusCode());
+
+ given()
+ .when()
+ .header(APM_HEADER_PARAM, createToken("user3", null))
+ .contentType(APPLICATION_JSON)
+ .body(update)
+ .body(new UserPersonCriteriaDTO())
+ .put("/user3")
+ .then()
+ .statusCode(Response.Status.BAD_REQUEST.getStatusCode());
+
+ var error = given()
+ .when()
+ .header(APM_HEADER_PARAM, createToken("user4", null))
+ .contentType(APPLICATION_JSON)
+ .body(update)
+ .body(new UserPersonCriteriaDTO())
+ .put("/user4")
+ .then()
+ .contentType(APPLICATION_JSON)
+ .statusCode(Response.Status.BAD_REQUEST.getStatusCode())
+ .extract().as(ProblemDetailResponseDTO.class);
+ assertThat(error.getErrorCode()).isEqualTo(problemDetailResponse.getErrorCode());
+
+ given()
+ .when()
+ .header(APM_HEADER_PARAM, createToken("user1", null))
+ .contentType(APPLICATION_JSON)
+ .body(update)
+ .body(new UserPersonCriteriaDTO())
+ .put("/user1")
+ .then()
+ .statusCode(Response.Status.NO_CONTENT.getStatusCode());
+
+ // opt lock test
+ problemDetailResponse.setErrorCode("OPTIMISTIC_LOCK");
+ mockServerClient.when(request().withPath("/internal/userProfiles/user5"))
+ .withPriority(100)
+ .respond(httpRequest -> response().withStatusCode(Response.Status.BAD_REQUEST.getStatusCode())
+ .withContentType(MediaType.APPLICATION_JSON)
+ .withBody(JsonBody.json(problemDetailResponse)));
+
+ error = given()
+ .when()
+ .header(APM_HEADER_PARAM, createToken("user5", null))
+ .contentType(APPLICATION_JSON)
+ .body(update)
+ .body(new UserPersonCriteriaDTO())
+ .put("/user5")
+ .then()
+ .contentType(APPLICATION_JSON)
+ .statusCode(Response.Status.BAD_REQUEST.getStatusCode())
+ .extract().as(ProblemDetailResponseDTO.class);
+ assertThat(error.getErrorCode()).isEqualTo(problemDetailResponse.getErrorCode());
+ }
+}
diff --git a/src/test/java/io/github/onecx/user/profile/bff/rs/UserProfileRestControllerIT.java b/src/test/java/io/github/onecx/user/profile/bff/rs/UserProfileRestControllerIT.java
new file mode 100644
index 0000000..5ecaaac
--- /dev/null
+++ b/src/test/java/io/github/onecx/user/profile/bff/rs/UserProfileRestControllerIT.java
@@ -0,0 +1,7 @@
+package io.github.onecx.user.profile.bff.rs;
+
+import io.quarkus.test.junit.QuarkusIntegrationTest;
+
+@QuarkusIntegrationTest
+public class UserProfileRestControllerIT extends UserProfileRestControllerTest {
+}
diff --git a/src/test/java/io/github/onecx/user/profile/bff/rs/UserProfileRestControllerTest.java b/src/test/java/io/github/onecx/user/profile/bff/rs/UserProfileRestControllerTest.java
new file mode 100644
index 0000000..8620978
--- /dev/null
+++ b/src/test/java/io/github/onecx/user/profile/bff/rs/UserProfileRestControllerTest.java
@@ -0,0 +1,735 @@
+package io.github.onecx.user.profile.bff.rs;
+
+import static io.restassured.RestAssured.given;
+import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockserver.model.HttpRequest.request;
+import static org.mockserver.model.HttpResponse.response;
+import static org.mockserver.model.NottableString.not;
+import static org.mockserver.model.NottableString.string;
+
+import java.util.ArrayList;
+
+import jakarta.ws.rs.HttpMethod;
+import jakarta.ws.rs.core.Response;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockserver.client.MockServerClient;
+import org.mockserver.model.JsonBody;
+import org.mockserver.model.MediaType;
+import org.tkit.quarkus.log.cdi.LogService;
+
+import gen.io.github.onecx.user.profile.bff.clients.model.*;
+import gen.io.github.onecx.user.profile.bff.rs.internal.model.*;
+import io.github.onecx.user.profile.bff.rs.controllers.UserProfileRestController;
+import io.quarkiverse.mockserver.test.InjectMockServerClient;
+import io.quarkus.test.common.http.TestHTTPEndpoint;
+import io.quarkus.test.junit.QuarkusTest;
+
+@QuarkusTest
+@LogService
+@TestHTTPEndpoint(UserProfileRestController.class)
+class UserProfileRestControllerTest extends AbstractTest {
+
+ @InjectMockServerClient
+ MockServerClient mockServerClient;
+
+ @BeforeEach
+ void resetMockServer() {
+ mockServerClient.reset();
+ // throw 500 if the apm-principal token is not there
+ mockServerClient.when(request().withHeader(not(APM_HEADER_PARAM), string(".*")))
+ .withPriority(999)
+ .respond(httpRequest -> response().withStatusCode(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()));
+
+ }
+
+ @Test
+ void createUserPreferenceTest() {
+ CreateUserPreferenceDTO cupDTO = new CreateUserPreferenceDTO();
+ cupDTO.setApplicationId("app1");
+ cupDTO.setName("name1");
+ cupDTO.setValue("value1");
+ cupDTO.setDescription("desc1");
+ given()
+ .when()
+ .contentType(APPLICATION_JSON)
+ .body(cupDTO)
+ .post("/preferences")
+ .then()
+ .statusCode(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode());
+
+ UserPreference userPreference = new UserPreference();
+ userPreference.setApplicationId(cupDTO.getApplicationId());
+ userPreference.setName(cupDTO.getName());
+ userPreference.setDescription(cupDTO.getDescription());
+ userPreference.setValue(cupDTO.getValue());
+ userPreference.setId("id1");
+ mockServerClient.when(request().withPath("/internal/userProfile/me/preferences")
+ .withMethod(HttpMethod.POST))
+ .withPriority(100)
+ .respond(httpRequest -> response().withStatusCode(Response.Status.CREATED.getStatusCode())
+ .withContentType(MediaType.APPLICATION_JSON)
+ .withBody(JsonBody.json(userPreference)));
+
+ var response = given()
+ .when()
+ .header(APM_HEADER_PARAM, createToken("user1", null))
+ .contentType(APPLICATION_JSON)
+ .body(cupDTO)
+ .post("/preferences")
+ .then()
+ .contentType(APPLICATION_JSON)
+ .statusCode(Response.Status.CREATED.getStatusCode())
+ .extract().as(UserPreferenceDTO.class);
+ assertThat(response.getId()).isEqualTo(userPreference.getId());
+
+ ProblemDetailResponse problem = new ProblemDetailResponse();
+ problem.setErrorCode("MANUAL_ERROR");
+ problem.setDetail("Manual detail of error");
+ mockServerClient.when(request().withPath("/internal/userProfile/me/preferences")
+ .withMethod(HttpMethod.POST))
+ .withPriority(200)
+ .respond(httpRequest -> response().withStatusCode(Response.Status.BAD_REQUEST.getStatusCode())
+ .withContentType(MediaType.APPLICATION_JSON)
+ .withBody(JsonBody.json(problem)));
+
+ var error = given()
+ .when()
+ .header(APM_HEADER_PARAM, createToken("user1", null))
+ .contentType(APPLICATION_JSON)
+ .body(cupDTO)
+ .post("/preferences")
+ .then()
+ .contentType(APPLICATION_JSON)
+ .statusCode(Response.Status.BAD_REQUEST.getStatusCode())
+ .extract().as(ProblemDetailResponseDTO.class);
+ assertThat(error.getErrorCode()).isEqualTo(problem.getErrorCode());
+ assertThat(error.getDetail()).isEqualTo(problem.getDetail());
+ }
+
+ @Test
+ void deleteMyUserProfile() {
+ given()
+ .when()
+ .contentType(APPLICATION_JSON)
+ .delete()
+ .then()
+ .statusCode(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode());
+
+ mockServerClient.when(request().withPath("/internal/userProfile/me")
+ .withMethod(HttpMethod.DELETE))
+ .withPriority(100)
+ .respond(httpRequest -> response().withStatusCode(Response.Status.NO_CONTENT.getStatusCode()));
+
+ given()
+ .when()
+ .header(APM_HEADER_PARAM, createToken("user1", null))
+ .contentType(APPLICATION_JSON)
+ .delete()
+ .then()
+ .statusCode(Response.Status.NO_CONTENT.getStatusCode());
+
+ ProblemDetailResponse problem = new ProblemDetailResponse();
+ problem.setErrorCode("MANUAL_ERROR");
+ problem.setDetail("Manual detail of error");
+ mockServerClient.when(request().withPath("/internal/userProfile/me")
+ .withMethod(HttpMethod.DELETE))
+ .withPriority(200)
+ .respond(httpRequest -> response().withStatusCode(Response.Status.BAD_REQUEST.getStatusCode())
+ .withContentType(MediaType.APPLICATION_JSON)
+ .withBody(JsonBody.json(problem)));
+
+ var error = given()
+ .when()
+ .header(APM_HEADER_PARAM, createToken("user1", null))
+ .contentType(APPLICATION_JSON)
+ .delete()
+ .then()
+ .statusCode(Response.Status.BAD_REQUEST.getStatusCode())
+ .extract().as(ProblemDetailResponseDTO.class);
+
+ assertThat(error.getErrorCode()).isEqualTo(problem.getErrorCode());
+ assertThat(error.getDetail()).isEqualTo(problem.getDetail());
+ }
+
+ @Test
+ void deleteUserPreference() {
+ given()
+ .when()
+ .contentType(APPLICATION_JSON)
+ .delete("/preferences/pref1")
+ .then()
+ .statusCode(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode());
+
+ mockServerClient.when(request().withPath("/internal/userProfile/me/preferences/pref1")
+ .withMethod(HttpMethod.DELETE))
+ .withPriority(100)
+ .respond(httpRequest -> response().withStatusCode(Response.Status.NO_CONTENT.getStatusCode()));
+
+ given()
+ .when()
+ .header(APM_HEADER_PARAM, createToken("user1", null))
+ .contentType(APPLICATION_JSON)
+ .delete("/preferences/pref1")
+ .then()
+ .statusCode(Response.Status.NO_CONTENT.getStatusCode());
+
+ ProblemDetailResponse problem = new ProblemDetailResponse();
+ problem.setErrorCode("MANUAL_ERROR");
+ problem.setDetail("Manual detail of error");
+ mockServerClient.when(request().withPath("/internal/userProfile/me/preferences/pref1")
+ .withMethod(HttpMethod.DELETE))
+ .withPriority(200)
+ .respond(httpRequest -> response().withStatusCode(Response.Status.BAD_REQUEST.getStatusCode())
+ .withContentType(MediaType.APPLICATION_JSON)
+ .withBody(JsonBody.json(problem)));
+
+ var error = given()
+ .when()
+ .header(APM_HEADER_PARAM, createToken("user1", null))
+ .contentType(APPLICATION_JSON)
+ .delete("/preferences/pref1")
+ .then()
+ .statusCode(Response.Status.BAD_REQUEST.getStatusCode())
+ .extract().as(ProblemDetailResponseDTO.class);
+
+ assertThat(error.getErrorCode()).isEqualTo(problem.getErrorCode());
+ assertThat(error.getDetail()).isEqualTo(problem.getDetail());
+ }
+
+ @Test
+ void getMyUserProfile() {
+ given()
+ .when()
+ .contentType(APPLICATION_JSON)
+ .get()
+ .then()
+ .statusCode(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode());
+
+ UserProfile userProfile = new UserProfile();
+ userProfile.setUserId("user1");
+ userProfile.setOrganization("capgemini");
+ userProfile.setIdentityProvider("database");
+ userProfile.setIdentityProviderId("db");
+ var person = new UserPerson();
+ userProfile.setPerson(person);
+ person.setDisplayName("Capgemini super user");
+ person.setEmail("cap@capgemini.com");
+ person.setFirstName("Superuser");
+ person.setLastName("Capgeminius");
+ mockServerClient.when(request().withPath("/internal/userProfile/me")
+ .withMethod(HttpMethod.GET))
+ .withPriority(100)
+ .respond(httpRequest -> response().withStatusCode(Response.Status.OK.getStatusCode())
+ .withContentType(MediaType.APPLICATION_JSON)
+ .withBody(JsonBody.json(userProfile)));
+
+ var response = given()
+ .when()
+ .header(APM_HEADER_PARAM, createToken("user1", null))
+ .contentType(APPLICATION_JSON)
+ .get()
+ .then()
+ .contentType(APPLICATION_JSON)
+ .statusCode(Response.Status.OK.getStatusCode())
+ .extract().as(UserProfileDTO.class);
+
+ assertThat(response).isNotNull();
+ assertThat(response.getUserId()).isEqualTo(userProfile.getUserId());
+ assertThat(response.getPerson().getEmail()).isEqualTo(userProfile.getPerson().getEmail());
+
+ ProblemDetailResponse problem = new ProblemDetailResponse();
+ problem.setErrorCode("MANUAL_ERROR");
+ problem.setDetail("Manual detail of error");
+ mockServerClient.when(request().withPath("/internal/userProfile/me")
+ .withMethod(HttpMethod.GET))
+ .withPriority(200)
+ .respond(httpRequest -> response().withStatusCode(Response.Status.BAD_REQUEST.getStatusCode())
+ .withContentType(MediaType.APPLICATION_JSON)
+ .withBody(JsonBody.json(problem)));
+
+ var error = given()
+ .when()
+ .header(APM_HEADER_PARAM, createToken("user1", null))
+ .contentType(APPLICATION_JSON)
+ .get()
+ .then()
+ .statusCode(Response.Status.BAD_REQUEST.getStatusCode())
+ .extract().as(ProblemDetailResponseDTO.class);
+
+ assertThat(error.getErrorCode()).isEqualTo(problem.getErrorCode());
+ assertThat(error.getDetail()).isEqualTo(problem.getDetail());
+ }
+
+ @Test
+ void getUserPerson() {
+ given()
+ .when()
+ .contentType(APPLICATION_JSON)
+ .get("/person")
+ .then()
+ .statusCode(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode());
+
+ var person = new UserPerson();
+ person.setDisplayName("Capgemini super user");
+ person.setEmail("cap@capgemini.com");
+ person.setFirstName("Superuser");
+ person.setLastName("Capgeminius");
+ UserPersonAddress addresss = new UserPersonAddress();
+ addresss.setStreet("Obergasse");
+ person.setAddress(addresss);
+ mockServerClient.when(request().withPath("/internal/userProfile/me/person")
+ .withMethod(HttpMethod.GET))
+ .withPriority(100)
+ .respond(httpRequest -> response().withStatusCode(Response.Status.OK.getStatusCode())
+ .withContentType(MediaType.APPLICATION_JSON)
+ .withBody(JsonBody.json(person)));
+
+ var response = given()
+ .when()
+ .header(APM_HEADER_PARAM, createToken("user1", null))
+ .contentType(APPLICATION_JSON)
+ .get("/person")
+ .then()
+ .contentType(APPLICATION_JSON)
+ .statusCode(Response.Status.OK.getStatusCode())
+ .extract().as(UserPersonDTO.class);
+
+ assertThat(response).isNotNull();
+ assertThat(response.getEmail()).isEqualTo(person.getEmail());
+ assertThat(response.getAddress().getStreet()).isEqualTo(person.getAddress().getStreet());
+
+ ProblemDetailResponse problem = new ProblemDetailResponse();
+ problem.setErrorCode("MANUAL_ERROR");
+ problem.setDetail("Manual detail of error");
+ mockServerClient.when(request().withPath("/internal/userProfile/me/person")
+ .withMethod(HttpMethod.GET))
+ .withPriority(200)
+ .respond(httpRequest -> response().withStatusCode(Response.Status.BAD_REQUEST.getStatusCode())
+ .withContentType(MediaType.APPLICATION_JSON)
+ .withBody(JsonBody.json(problem)));
+
+ var error = given()
+ .when()
+ .header(APM_HEADER_PARAM, createToken("user1", null))
+ .contentType(APPLICATION_JSON)
+ .get("/person")
+ .then()
+ .statusCode(Response.Status.BAD_REQUEST.getStatusCode())
+ .extract().as(ProblemDetailResponseDTO.class);
+
+ assertThat(error.getErrorCode()).isEqualTo(problem.getErrorCode());
+ assertThat(error.getDetail()).isEqualTo(problem.getDetail());
+ }
+
+ @Test
+ void getUserPreference() {
+ given()
+ .when()
+ .contentType(APPLICATION_JSON)
+ .get("/preferences")
+ .then()
+ .statusCode(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode());
+
+ var preferences = new UserPreferences();
+ preferences.setPreferences(new ArrayList<>());
+ UserPreference preference1 = new UserPreference();
+ preference1.setId("1");
+ preference1.setName("name1");
+ preference1.setDescription("desc1");
+ preference1.setApplicationId("app1");
+ preference1.setValue("value1");
+ preferences.getPreferences().add(preference1);
+ UserPreference preference2 = new UserPreference();
+ preference2.setId("2");
+ preference2.setName("name2");
+ preference2.setDescription("desc2");
+ preference2.setApplicationId("app2");
+ preference2.setValue("value2");
+ preferences.getPreferences().add(preference2);
+ mockServerClient.when(request().withPath("/internal/userProfile/me/preferences")
+ .withMethod(HttpMethod.GET))
+ .withPriority(100)
+ .respond(httpRequest -> response().withStatusCode(Response.Status.OK.getStatusCode())
+ .withContentType(MediaType.APPLICATION_JSON)
+ .withBody(JsonBody.json(preferences)));
+
+ var response = given()
+ .when()
+ .header(APM_HEADER_PARAM, createToken("user1", null))
+ .contentType(APPLICATION_JSON)
+ .get("/preferences")
+ .then()
+ .contentType(APPLICATION_JSON)
+ .statusCode(Response.Status.OK.getStatusCode())
+ .extract().as(UserPreferencesDTO.class);
+
+ assertThat(response.getPreferences()).hasSize(2);
+
+ ProblemDetailResponse problem = new ProblemDetailResponse();
+ problem.setErrorCode("MANUAL_ERROR");
+ problem.setDetail("Manual detail of error");
+ mockServerClient.when(request().withPath("/internal/userProfile/me/preferences")
+ .withMethod(HttpMethod.GET))
+ .withPriority(200)
+ .respond(httpRequest -> response().withStatusCode(Response.Status.BAD_REQUEST.getStatusCode())
+ .withContentType(MediaType.APPLICATION_JSON)
+ .withBody(JsonBody.json(problem)));
+
+ var error = given()
+ .when()
+ .header(APM_HEADER_PARAM, createToken("user1", null))
+ .contentType(APPLICATION_JSON)
+ .get("/preferences")
+ .then()
+ .statusCode(Response.Status.BAD_REQUEST.getStatusCode())
+ .extract().as(ProblemDetailResponseDTO.class);
+
+ assertThat(error.getErrorCode()).isEqualTo(problem.getErrorCode());
+ assertThat(error.getDetail()).isEqualTo(problem.getDetail());
+ }
+
+ @Test
+ void getUserSettings() {
+ given()
+ .when()
+ .contentType(APPLICATION_JSON)
+ .get("/settings")
+ .then()
+ .statusCode(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode());
+
+ var settings = new UserProfileAccountSettings();
+ settings.setMenuMode(MenuMode.STATIC);
+ settings.setHideMyProfile(false);
+ settings.setColorScheme(ColorScheme.DARK);
+ settings.setTimezone("get/muenich");
+
+ mockServerClient.when(request().withPath("/internal/userProfile/me/settings")
+ .withMethod(HttpMethod.GET))
+ .withPriority(100)
+ .respond(httpRequest -> response().withStatusCode(Response.Status.OK.getStatusCode())
+ .withContentType(MediaType.APPLICATION_JSON)
+ .withBody(JsonBody.json(settings)));
+
+ var response = given()
+ .when()
+ .header(APM_HEADER_PARAM, createToken("user1", null))
+ .contentType(APPLICATION_JSON)
+ .get("/settings")
+ .then()
+ .contentType(APPLICATION_JSON)
+ .statusCode(Response.Status.OK.getStatusCode())
+ .extract().as(UserProfileAccountSettingsDTO.class);
+
+ assertThat(response).isNotNull();
+ assertThat(response.getHideMyProfile()).isEqualTo(settings.getHideMyProfile());
+ assertThat(response.getMenuMode()).hasToString(settings.getMenuMode().toString());
+
+ ProblemDetailResponse problem = new ProblemDetailResponse();
+ problem.setErrorCode("MANUAL_ERROR");
+ problem.setDetail("Manual detail of error");
+ mockServerClient.when(request().withPath("/internal/userProfile/me/settings")
+ .withMethod(HttpMethod.GET))
+ .withPriority(200)
+ .respond(httpRequest -> response().withStatusCode(Response.Status.BAD_REQUEST.getStatusCode())
+ .withContentType(MediaType.APPLICATION_JSON)
+ .withBody(JsonBody.json(problem)));
+
+ var error = given()
+ .when()
+ .header(APM_HEADER_PARAM, createToken("user1", null))
+ .contentType(APPLICATION_JSON)
+ .get("/settings")
+ .then()
+ .statusCode(Response.Status.BAD_REQUEST.getStatusCode())
+ .extract().as(ProblemDetailResponseDTO.class);
+
+ assertThat(error.getErrorCode()).isEqualTo(problem.getErrorCode());
+ assertThat(error.getDetail()).isEqualTo(problem.getDetail());
+ }
+
+ @Test
+ void updateUserPerson() {
+ var update = new UpdateUserPersonRequestDTO();
+ update.setEmail("test@email.de");
+
+ given()
+ .when()
+ .contentType(APPLICATION_JSON)
+ .body(update)
+ .put("/person")
+ .then()
+ .statusCode(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode());
+
+ var person = new UserPerson();
+ person.setDisplayName("Capgemini super user");
+ person.setEmail("test@email.de");
+ person.setFirstName("Superuser");
+ person.setLastName("Capgeminius");
+ UserPersonAddress addresss = new UserPersonAddress();
+ addresss.setStreet("Obergasse");
+ person.setAddress(addresss);
+ mockServerClient.when(request().withPath("/internal/userProfile/me/person")
+ .withMethod(HttpMethod.PUT))
+ .withPriority(100)
+ .respond(httpRequest -> response().withStatusCode(Response.Status.OK.getStatusCode())
+ .withContentType(MediaType.APPLICATION_JSON)
+ .withBody(JsonBody.json(person)));
+
+ var response = given()
+ .when()
+ .header(APM_HEADER_PARAM, createToken("user1", null))
+ .body(update)
+ .contentType(APPLICATION_JSON)
+ .put("/person")
+ .then()
+ .contentType(APPLICATION_JSON)
+ .statusCode(Response.Status.OK.getStatusCode())
+ .extract().as(UserPersonDTO.class);
+
+ assertThat(response).isNotNull();
+ assertThat(response.getEmail()).isEqualTo(person.getEmail());
+
+ ProblemDetailResponse problem = new ProblemDetailResponse();
+ problem.setErrorCode("MANUAL_ERROR");
+ problem.setDetail("Manual detail of error");
+ mockServerClient.when(request().withPath("/internal/userProfile/me/person")
+ .withMethod(HttpMethod.PUT))
+ .withPriority(200)
+ .respond(httpRequest -> response().withStatusCode(Response.Status.BAD_REQUEST.getStatusCode())
+ .withContentType(MediaType.APPLICATION_JSON)
+ .withBody(JsonBody.json(problem)));
+
+ var error = given()
+ .when()
+ .header(APM_HEADER_PARAM, createToken("user1", null))
+ .contentType(APPLICATION_JSON)
+ .body(update)
+ .put("/person")
+ .then()
+ .statusCode(Response.Status.BAD_REQUEST.getStatusCode())
+ .extract().as(ProblemDetailResponseDTO.class);
+
+ assertThat(error.getErrorCode()).isEqualTo(problem.getErrorCode());
+ assertThat(error.getDetail()).isEqualTo(problem.getDetail());
+
+ problem.setErrorCode("OPTIMISTIC_LOCK");
+ mockServerClient.when(request().withPath("/internal/userProfile/me/person")
+ .withMethod(HttpMethod.PUT))
+ .withPriority(300)
+ .respond(httpRequest -> response().withStatusCode(Response.Status.BAD_REQUEST.getStatusCode())
+ .withContentType(MediaType.APPLICATION_JSON)
+ .withBody(JsonBody.json(problem)));
+
+ error = given()
+ .when()
+ .header(APM_HEADER_PARAM, createToken("user1", null))
+ .contentType(APPLICATION_JSON)
+ .body(update)
+ .put("/person")
+ .then()
+ .statusCode(Response.Status.BAD_REQUEST.getStatusCode())
+ .extract().as(ProblemDetailResponseDTO.class);
+
+ assertThat(error.getErrorCode()).isEqualTo(problem.getErrorCode());
+ }
+
+ @Test
+ void updateUserPreference() {
+ var update = "newValue1";
+ given()
+ .when()
+ .contentType(APPLICATION_JSON)
+ .body(update)
+ .patch("/preferences/pref1")
+ .then()
+ .statusCode(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode());
+
+ UserPreference preference1 = new UserPreference();
+ preference1.setId("1");
+ preference1.setName("name1");
+ preference1.setDescription("desc1");
+ preference1.setApplicationId("app1");
+ preference1.setValue(update);
+ mockServerClient.when(request().withPath("/internal/userProfile/me/preferences/pref1")
+ .withMethod(HttpMethod.PATCH))
+ .withPriority(100)
+ .respond(httpRequest -> response().withStatusCode(Response.Status.OK.getStatusCode())
+ .withContentType(MediaType.APPLICATION_JSON)
+ .withBody(JsonBody.json(preference1)));
+
+ var response = given()
+ .when()
+ .header(APM_HEADER_PARAM, createToken("user1", null))
+ .contentType(APPLICATION_JSON)
+ .body(update)
+ .patch("/preferences/pref1")
+ .then()
+ .contentType(APPLICATION_JSON)
+ .statusCode(Response.Status.OK.getStatusCode())
+ .extract().as(UserPreferenceDTO.class);
+
+ assertThat(response.getValue()).isEqualTo(update);
+
+ mockServerClient.when(request().withPath("/internal/userProfile/me/preferences/pref2")
+ .withMethod(HttpMethod.PATCH))
+ .withPriority(100)
+ .respond(httpRequest -> response().withStatusCode(Response.Status.NOT_FOUND.getStatusCode()));
+ given()
+ .when()
+ .header(APM_HEADER_PARAM, createToken("user1", null))
+ .contentType(APPLICATION_JSON)
+ .body(update)
+ .patch("/preferences/pref2")
+ .then()
+ .statusCode(Response.Status.NOT_FOUND.getStatusCode());
+
+ ProblemDetailResponse problem = new ProblemDetailResponse();
+ problem.setErrorCode("MANUAL_ERROR");
+ problem.setDetail("Manual detail of error");
+ mockServerClient.when(request().withPath("/internal/userProfile/me/preferences/pref1")
+ .withMethod(HttpMethod.PATCH))
+ .withPriority(200)
+ .respond(httpRequest -> response().withStatusCode(Response.Status.BAD_REQUEST.getStatusCode())
+ .withContentType(MediaType.APPLICATION_JSON)
+ .withBody(JsonBody.json(problem)));
+
+ var error = given()
+ .when()
+ .header(APM_HEADER_PARAM, createToken("user1", null))
+ .contentType(APPLICATION_JSON)
+ .body(update)
+ .patch("/preferences/pref1")
+ .then()
+ .statusCode(Response.Status.BAD_REQUEST.getStatusCode())
+ .extract().as(ProblemDetailResponseDTO.class);
+
+ assertThat(error.getErrorCode()).isEqualTo(problem.getErrorCode());
+ assertThat(error.getDetail()).isEqualTo(problem.getDetail());
+ }
+
+ @Test
+ void updateUserSettings() {
+ var update = new UpdateUserSettingsDTO();
+ update.setColorScheme(ColorSchemeDTO.LIGHT);
+ given()
+ .when()
+ .contentType(APPLICATION_JSON)
+ .body(update)
+ .get("/settings")
+ .then()
+ .statusCode(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode());
+
+ var settings = new UserProfileAccountSettings();
+ settings.setMenuMode(MenuMode.STATIC);
+ settings.setHideMyProfile(false);
+ settings.setColorScheme(ColorScheme.LIGHT);
+ settings.setTimezone("get/muenich");
+
+ mockServerClient.when(request().withPath("/internal/userProfile/me/settings")
+ .withMethod(HttpMethod.PUT))
+ .withPriority(100)
+ .respond(httpRequest -> response().withStatusCode(Response.Status.OK.getStatusCode())
+ .withContentType(MediaType.APPLICATION_JSON)
+ .withBody(JsonBody.json(settings)));
+
+ var response = given()
+ .when()
+ .header(APM_HEADER_PARAM, createToken("user1", null))
+ .contentType(APPLICATION_JSON)
+ .body(update)
+ .put("/settings")
+ .then()
+ .contentType(APPLICATION_JSON)
+ .statusCode(Response.Status.OK.getStatusCode())
+ .extract().as(UserProfileAccountSettingsDTO.class);
+
+ assertThat(response).isNotNull();
+ assertThat(response.getHideMyProfile()).isEqualTo(settings.getHideMyProfile());
+ assertThat(response.getMenuMode()).hasToString(settings.getMenuMode().toString());
+
+ ProblemDetailResponse problem = new ProblemDetailResponse();
+ problem.setErrorCode("MANUAL_ERROR");
+ problem.setDetail("Manual detail of error");
+ mockServerClient.when(request().withPath("/internal/userProfile/me/settings")
+ .withMethod(HttpMethod.PUT))
+ .withPriority(200)
+ .respond(httpRequest -> response().withStatusCode(Response.Status.BAD_REQUEST.getStatusCode())
+ .withContentType(MediaType.APPLICATION_JSON)
+ .withBody(JsonBody.json(problem)));
+
+ var error = given()
+ .when()
+ .header(APM_HEADER_PARAM, createToken("user1", null))
+ .contentType(APPLICATION_JSON)
+ .body(update)
+ .put("/settings")
+ .then()
+ .statusCode(Response.Status.BAD_REQUEST.getStatusCode())
+ .extract().as(ProblemDetailResponseDTO.class);
+
+ assertThat(error.getErrorCode()).isEqualTo(problem.getErrorCode());
+ assertThat(error.getDetail()).isEqualTo(problem.getDetail());
+
+ problem.setErrorCode("OPTIMISTIC_LOCK");
+ mockServerClient.when(request().withPath("/internal/userProfile/me/settings")
+ .withMethod(HttpMethod.PUT))
+ .withPriority(300)
+ .respond(httpRequest -> response().withStatusCode(Response.Status.BAD_REQUEST.getStatusCode())
+ .withContentType(MediaType.APPLICATION_JSON)
+ .withBody(JsonBody.json(problem)));
+
+ error = given()
+ .when()
+ .header(APM_HEADER_PARAM, createToken("user1", null))
+ .contentType(APPLICATION_JSON)
+ .body(update)
+ .put("/settings")
+ .then()
+ .statusCode(Response.Status.BAD_REQUEST.getStatusCode())
+ .extract().as(ProblemDetailResponseDTO.class);
+
+ assertThat(error.getErrorCode()).isEqualTo(problem.getErrorCode());
+
+ }
+
+ @Test
+ void testConstraintViolationException() {
+ var error = given()
+ .when()
+ .header(APM_HEADER_PARAM, createToken("user1", null))
+ .contentType(APPLICATION_JSON)
+ .put("/settings")
+ .then()
+ .contentType(APPLICATION_JSON)
+ .statusCode(Response.Status.BAD_REQUEST.getStatusCode())
+ .extract().as(ProblemDetailResponseDTO.class);
+
+ assertThat(error).isNotNull();
+ assertThat(error.getErrorCode()).isEqualTo("CONSTRAINT_VIOLATIONS");
+ }
+
+ @Test
+ void testBadRequestWithoutProblemResponse() {
+ var update = new UpdateUserSettingsDTO();
+ mockServerClient.when(request().withPath("/internal/userProfile/me/settings")
+ .withMethod(HttpMethod.PUT))
+ .withPriority(200)
+ .respond(httpRequest -> response().withStatusCode(Response.Status.BAD_REQUEST.getStatusCode()));
+
+ var response = given()
+ .when()
+ .header(APM_HEADER_PARAM, createToken("user1", null))
+ .contentType(APPLICATION_JSON)
+ .body(update)
+ .put("/settings");
+
+ response.then()
+ .statusCode(Response.Status.BAD_REQUEST.getStatusCode());
+
+ assertThat(response.getBody().prettyPrint()).isEmpty();
+
+ }
+}
diff --git a/src/test/resources/data/avatar_test.jpg b/src/test/resources/data/avatar_test.jpg
new file mode 100644
index 0000000..2714c0a
Binary files /dev/null and b/src/test/resources/data/avatar_test.jpg differ
diff --git a/src/test/resources/mockserver.properties b/src/test/resources/mockserver.properties
new file mode 100644
index 0000000..7f9d719
--- /dev/null
+++ b/src/test/resources/mockserver.properties
@@ -0,0 +1,15 @@
+mockserver.initializationJsonPath=/mockserver/*.json
+# watch changes in the file
+mockserver.watchInitializationJson=true
+
+# Certificate Generation
+# dynamically generated CA key pair (if they don't already exist in specified directory)
+mockserver.dynamicallyCreateCertificateAuthorityCertificate=true
+# save dynamically generated CA key pair in working directory
+mockserver.directoryToSaveDynamicSSLCertificate=.
+# certificate domain name (default "localhost")
+mockserver.sslCertificateDomainName=localhost
+# comma separated list of ip addresses for Subject Alternative Name domain names (default empty list)
+mockserver.sslSubjectAlternativeNameDomains=www.example.com,www.another.com
+# comma separated list of ip addresses for Subject Alternative Name ips (default empty list)
+mockserver.sslSubjectAlternativeNameIps=127.0.0.1
\ No newline at end of file
diff --git a/src/test/resources/mockserver/internal.json b/src/test/resources/mockserver/internal.json
new file mode 100644
index 0000000..8500701
--- /dev/null
+++ b/src/test/resources/mockserver/internal.json
@@ -0,0 +1,21 @@
+[
+ {
+ "id": "1",
+ "httpRequest": {
+ "path": "/internal/themes"
+ },
+ "httpResponse": {
+ "body": {
+ "type": "JSON",
+ "json": {
+ "totalElements": 1,
+ "number": 1,
+ "size": 1,
+ "totalPages": 1,
+ "stream": ["app1","app2"]
+ },
+ "contentType": "application/json"
+ }
+ }
+ }
+]
\ No newline at end of file