From 2fde360787663519ba6269d203d7a08d93a9cde5 Mon Sep 17 00:00:00 2001
From: JordenReuter <149687553+JordenReuter@users.noreply.github.com>
Date: Wed, 3 Apr 2024 08:27:10 +0200
Subject: [PATCH] Feature/p002271 6933 init api implementation (#2)
* feat: initial impl
* feat: refactored routes and added permission test
* feat: readded profile test
* fix: formatting
* feat: logparams
* feat: added native build support
* feat: added endpoint to get favicon
* feat: renamed route path to url
* feat: tests, errorhandling and small adjustments
* feat: added temporary mocks for remote components
* fix: adjusted mock configs
* fix: try to fix conifg init
* fix: try to fix configs
* feat: theme properties as string
---
.github/workflows/build-branch.yml | 4 +-
.github/workflows/build-pr.yml | 4 +-
.github/workflows/build-release.yml | 4 +-
.github/workflows/build.yml | 1 +
pom.xml | 13 +
src/main/helm/values.yaml | 8 +-
.../onecx/shell/bff/rs/PermissionConfig.java | 13 +
.../bff/rs/RemoteComponentMockConfig.java | 41 ++
.../controllers/PermissionRestController.java | 71 +++
.../UserProfileRestController.java | 42 ++
.../WorkspaceConfigRestController.java | 161 ++++++
.../onecx/shell/bff/rs/log/PermissionLog.java | 22 +
.../shell/bff/rs/log/WorkspaceConfigLog.java | 20 +
.../shell/bff/rs/mappers/ExceptionMapper.java | 65 +++
.../bff/rs/mappers/PermissionMapper.java | 19 +
.../bff/rs/mappers/UserProfileMapper.java | 30 ++
.../bff/rs/mappers/WorkspaceConfigMapper.java | 55 +++
src/main/openapi/openapi-bff.yaml | 420 ++++++++++++++++
src/main/resources/application.properties | 38 +-
.../tkit/onecx/shell/bff/rs/AbstractTest.java | 6 +-
.../bff/rs/PermissionRestControllerIT.java | 7 +
.../bff/rs/PermissionRestControllerTest.java | 165 +++++++
.../shell/bff/rs/UserProfileControllerIT.java | 7 +
.../bff/rs/UserProfileControllerTest.java | 83 ++++
.../rs/WorkspaceConfigRestControllerIT.java | 7 +
.../rs/WorkspaceConfigRestControllerTest.java | 461 ++++++++++++++++++
.../resources/mockserver/permissions.json | 5 +-
27 files changed, 1762 insertions(+), 10 deletions(-)
create mode 100644 src/main/java/org/tkit/onecx/shell/bff/rs/PermissionConfig.java
create mode 100644 src/main/java/org/tkit/onecx/shell/bff/rs/RemoteComponentMockConfig.java
create mode 100644 src/main/java/org/tkit/onecx/shell/bff/rs/controllers/PermissionRestController.java
create mode 100644 src/main/java/org/tkit/onecx/shell/bff/rs/controllers/UserProfileRestController.java
create mode 100644 src/main/java/org/tkit/onecx/shell/bff/rs/controllers/WorkspaceConfigRestController.java
create mode 100644 src/main/java/org/tkit/onecx/shell/bff/rs/log/PermissionLog.java
create mode 100644 src/main/java/org/tkit/onecx/shell/bff/rs/log/WorkspaceConfigLog.java
create mode 100644 src/main/java/org/tkit/onecx/shell/bff/rs/mappers/ExceptionMapper.java
create mode 100644 src/main/java/org/tkit/onecx/shell/bff/rs/mappers/PermissionMapper.java
create mode 100644 src/main/java/org/tkit/onecx/shell/bff/rs/mappers/UserProfileMapper.java
create mode 100644 src/main/java/org/tkit/onecx/shell/bff/rs/mappers/WorkspaceConfigMapper.java
create mode 100644 src/test/java/org/tkit/onecx/shell/bff/rs/PermissionRestControllerIT.java
create mode 100644 src/test/java/org/tkit/onecx/shell/bff/rs/PermissionRestControllerTest.java
create mode 100644 src/test/java/org/tkit/onecx/shell/bff/rs/UserProfileControllerIT.java
create mode 100644 src/test/java/org/tkit/onecx/shell/bff/rs/UserProfileControllerTest.java
create mode 100644 src/test/java/org/tkit/onecx/shell/bff/rs/WorkspaceConfigRestControllerIT.java
create mode 100644 src/test/java/org/tkit/onecx/shell/bff/rs/WorkspaceConfigRestControllerTest.java
diff --git a/.github/workflows/build-branch.yml b/.github/workflows/build-branch.yml
index 7270213..d0aea6d 100644
--- a/.github/workflows/build-branch.yml
+++ b/.github/workflows/build-branch.yml
@@ -10,4 +10,6 @@ on:
jobs:
branch:
uses: onecx/ci-quarkus/.github/workflows/build-branch.yml@v1
- secrets: inherit
\ No newline at end of file
+ secrets: inherit
+ with:
+ native: true
\ No newline at end of file
diff --git a/.github/workflows/build-pr.yml b/.github/workflows/build-pr.yml
index acff155..dcc442b 100644
--- a/.github/workflows/build-pr.yml
+++ b/.github/workflows/build-pr.yml
@@ -6,4 +6,6 @@ on:
jobs:
pr:
uses: onecx/ci-quarkus/.github/workflows/build-pr.yml@v1
- secrets: inherit
\ No newline at end of file
+ secrets: inherit
+ with:
+ native: true
\ No newline at end of file
diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml
index b04a59a..26d536a 100644
--- a/.github/workflows/build-release.yml
+++ b/.github/workflows/build-release.yml
@@ -6,4 +6,6 @@ on:
jobs:
release:
uses: onecx/ci-quarkus/.github/workflows/build-release.yml@v1
- secrets: inherit
\ No newline at end of file
+ secrets: inherit
+ with:
+ native: true
\ No newline at end of file
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index f5a5b68..b206acc 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -12,4 +12,5 @@ jobs:
uses: onecx/ci-quarkus/.github/workflows/build.yml@v1
secrets: inherit
with:
+ native: true
helmEventTargetRepository: onecx/onecx-shell
diff --git a/pom.xml b/pom.xml
index ea54cef..8c7a322 100644
--- a/pom.xml
+++ b/pom.xml
@@ -195,6 +195,19 @@
true
+
+ product-store-svc-external-v1
+ generate-resources
+
+ wget
+
+
+ https://raw.githubusercontent.com/onecx/onecx-product-store-svc/main/src/main/openapi/onecx-product-store-v1.yaml
+ target/tmp/openapi
+ onecx-product-store-svc-v1.yaml
+ true
+
+
permission-svc-external-v1
generate-resources
diff --git a/src/main/helm/values.yaml b/src/main/helm/values.yaml
index dbee734..9f4cc48 100644
--- a/src/main/helm/values.yaml
+++ b/src/main/helm/values.yaml
@@ -8,7 +8,9 @@ app:
enabled: true
spec:
permissions:
- example:
+ workspaceConfig:
read: permission on all GET requests and POST search
- write: permission on PUT, POST, PATCH requests, where objects are saved or updated
- delete: permission on all DELETE requests
\ No newline at end of file
+ userProfile:
+ read: permission on all GET requests and POST search
+ permission:
+ read: permission on all GET requests and POST search
\ No newline at end of file
diff --git a/src/main/java/org/tkit/onecx/shell/bff/rs/PermissionConfig.java b/src/main/java/org/tkit/onecx/shell/bff/rs/PermissionConfig.java
new file mode 100644
index 0000000..7464bf7
--- /dev/null
+++ b/src/main/java/org/tkit/onecx/shell/bff/rs/PermissionConfig.java
@@ -0,0 +1,13 @@
+package org.tkit.onecx.shell.bff.rs;
+
+import io.smallrye.config.ConfigMapping;
+import io.smallrye.config.WithName;
+
+@ConfigMapping(prefix = "onecx.permission")
+public interface PermissionConfig {
+ @WithName("caching.enabled")
+ boolean cachingEnabled();
+
+ @WithName("default.separator")
+ String keySeparator();
+}
diff --git a/src/main/java/org/tkit/onecx/shell/bff/rs/RemoteComponentMockConfig.java b/src/main/java/org/tkit/onecx/shell/bff/rs/RemoteComponentMockConfig.java
new file mode 100644
index 0000000..fabf309
--- /dev/null
+++ b/src/main/java/org/tkit/onecx/shell/bff/rs/RemoteComponentMockConfig.java
@@ -0,0 +1,41 @@
+package org.tkit.onecx.shell.bff.rs;
+
+import java.util.List;
+import java.util.Map;
+
+import io.quarkus.runtime.annotations.StaticInitSafe;
+import io.smallrye.config.ConfigMapping;
+import io.smallrye.config.WithName;
+
+@StaticInitSafe
+@ConfigMapping(prefix = "onecx.component.mock")
+public interface RemoteComponentMockConfig {
+
+ @WithName("keys")
+ List keys();
+
+ @WithName("baseurl")
+ Map baseUrl();
+
+ @WithName("name")
+ Map name();
+
+ @WithName("appid")
+ Map appId();
+
+ @WithName("productname")
+ Map productName();
+
+ @WithName("remotebaseurl")
+ Map remoteBaseUrl();
+
+ @WithName("remoteentryurl")
+ Map remoteEntryUrl();
+
+ @WithName("slot")
+ Map slot();
+
+ @WithName("exposedmodule")
+ Map exposedModule();
+
+}
diff --git a/src/main/java/org/tkit/onecx/shell/bff/rs/controllers/PermissionRestController.java b/src/main/java/org/tkit/onecx/shell/bff/rs/controllers/PermissionRestController.java
new file mode 100644
index 0000000..d2dc2b3
--- /dev/null
+++ b/src/main/java/org/tkit/onecx/shell/bff/rs/controllers/PermissionRestController.java
@@ -0,0 +1,71 @@
+package org.tkit.onecx.shell.bff.rs.controllers;
+
+import static jakarta.ws.rs.core.HttpHeaders.AUTHORIZATION;
+
+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.Context;
+import jakarta.ws.rs.core.HttpHeaders;
+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.onecx.quarkus.permission.client.PermissionClientService;
+import org.tkit.onecx.shell.bff.rs.PermissionConfig;
+import org.tkit.onecx.shell.bff.rs.mappers.ExceptionMapper;
+import org.tkit.onecx.shell.bff.rs.mappers.PermissionMapper;
+import org.tkit.quarkus.log.cdi.LogService;
+
+import gen.org.tkit.onecx.permission.client.api.PermissionApi;
+import gen.org.tkit.onecx.shell.bff.rs.internal.PermissionApiService;
+import gen.org.tkit.onecx.shell.bff.rs.internal.model.GetPermissionsRequestDTO;
+import gen.org.tkit.onecx.shell.bff.rs.internal.model.ProblemDetailResponseDTO;
+
+@ApplicationScoped
+@Transactional(value = Transactional.TxType.NOT_SUPPORTED)
+@LogService
+public class PermissionRestController implements PermissionApiService {
+
+ @Inject
+ @RestClient
+ PermissionApi permissionClient;
+
+ @Inject
+ PermissionClientService permissionClientService;
+
+ @Inject
+ PermissionConfig config;
+
+ @Inject
+ PermissionMapper mapper;
+
+ @Context
+ HttpHeaders httpHeaders;
+
+ @Inject
+ ExceptionMapper exceptionMapper;
+
+ @Override
+ public Response getPermissions(GetPermissionsRequestDTO getPermissionsRequestDTO) {
+ var principalToken = httpHeaders.getRequestHeader(AUTHORIZATION).get(0);
+ var rawPermission = permissionClientService.getPermissions(getPermissionsRequestDTO.getProductName(),
+ getPermissionsRequestDTO.getAppId(),
+ principalToken, config.keySeparator(), config.cachingEnabled());
+
+ return Response.status(Response.Status.OK).entity(mapper.map(rawPermission)).build();
+ }
+
+ @ServerExceptionMapper
+ public RestResponse constraint(ConstraintViolationException ex) {
+ return exceptionMapper.constraint(ex);
+ }
+
+ @ServerExceptionMapper
+ public Response restException(WebApplicationException ex) {
+ return Response.status(ex.getResponse().getStatus()).build();
+ }
+}
diff --git a/src/main/java/org/tkit/onecx/shell/bff/rs/controllers/UserProfileRestController.java b/src/main/java/org/tkit/onecx/shell/bff/rs/controllers/UserProfileRestController.java
new file mode 100644
index 0000000..5b25037
--- /dev/null
+++ b/src/main/java/org/tkit/onecx/shell/bff/rs/controllers/UserProfileRestController.java
@@ -0,0 +1,42 @@
+package org.tkit.onecx.shell.bff.rs.controllers;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import jakarta.transaction.Transactional;
+import jakarta.ws.rs.WebApplicationException;
+import jakarta.ws.rs.core.Response;
+
+import org.eclipse.microprofile.rest.client.inject.RestClient;
+import org.jboss.resteasy.reactive.server.ServerExceptionMapper;
+import org.tkit.onecx.shell.bff.rs.mappers.UserProfileMapper;
+import org.tkit.quarkus.log.cdi.LogService;
+
+import gen.org.tkit.onecx.shell.bff.rs.internal.UserProfileApiService;
+import gen.org.tkit.onecx.user.profile.client.api.UserProfileV1Api;
+import gen.org.tkit.onecx.user.profile.client.model.UserProfile;
+
+@ApplicationScoped
+@Transactional(value = Transactional.TxType.NOT_SUPPORTED)
+@LogService
+public class UserProfileRestController implements UserProfileApiService {
+
+ @Inject
+ @RestClient
+ UserProfileV1Api userProfileClient;
+
+ @Inject
+ UserProfileMapper mapper;
+
+ @Override
+ public Response getUserProfile() {
+ try (Response response = userProfileClient.getUserProfile()) {
+ return Response.status(response.getStatus())
+ .entity(mapper.map(response.readEntity(UserProfile.class))).build();
+ }
+ }
+
+ @ServerExceptionMapper
+ public Response restException(WebApplicationException ex) {
+ return Response.status(ex.getResponse().getStatus()).build();
+ }
+}
diff --git a/src/main/java/org/tkit/onecx/shell/bff/rs/controllers/WorkspaceConfigRestController.java b/src/main/java/org/tkit/onecx/shell/bff/rs/controllers/WorkspaceConfigRestController.java
new file mode 100644
index 0000000..a38630f
--- /dev/null
+++ b/src/main/java/org/tkit/onecx/shell/bff/rs/controllers/WorkspaceConfigRestController.java
@@ -0,0 +1,161 @@
+package org.tkit.onecx.shell.bff.rs.controllers;
+
+import java.util.ArrayList;
+import java.util.List;
+
+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.HttpHeaders;
+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.onecx.shell.bff.rs.RemoteComponentMockConfig;
+import org.tkit.onecx.shell.bff.rs.mappers.ExceptionMapper;
+import org.tkit.onecx.shell.bff.rs.mappers.WorkspaceConfigMapper;
+import org.tkit.quarkus.log.cdi.LogService;
+
+import gen.org.tkit.onecx.product.store.client.api.ProductsApi;
+import gen.org.tkit.onecx.product.store.client.model.ProductPSV1;
+import gen.org.tkit.onecx.shell.bff.rs.internal.WorkspaceConfigApiService;
+import gen.org.tkit.onecx.shell.bff.rs.internal.model.*;
+import gen.org.tkit.onecx.theme.client.api.ThemesApi;
+import gen.org.tkit.onecx.theme.client.model.Theme;
+import gen.org.tkit.onecx.workspace.client.api.WorkspaceExternalApi;
+import gen.org.tkit.onecx.workspace.client.model.WorkspaceLoad;
+import gen.org.tkit.onecx.workspace.client.model.WorkspacePageResult;
+
+@ApplicationScoped
+@Transactional(value = Transactional.TxType.NOT_SUPPORTED)
+@LogService
+public class WorkspaceConfigRestController implements WorkspaceConfigApiService {
+
+ @Inject
+ @RestClient
+ WorkspaceExternalApi workspaceClient;
+
+ @Inject
+ @RestClient
+ ThemesApi themeClient;
+
+ @Inject
+ @RestClient
+ ProductsApi productStoreClient;
+
+ @Inject
+ WorkspaceConfigMapper mapper;
+
+ @Inject
+ ExceptionMapper exceptionMapper;
+
+ @Inject
+ RemoteComponentMockConfig mockConfig;
+
+ @Override
+ public Response getWorkspaceConfig(GetWorkspaceConfigRequestDTO getWorkspaceConfigRequestDTO) {
+ GetWorkspaceConfigResponseDTO responseDTO = new GetWorkspaceConfigResponseDTO();
+
+ //get base workspace info
+ try (Response response = workspaceClient.searchWorkspaces(mapper.map(getWorkspaceConfigRequestDTO))) {
+ if (!response.readEntity(WorkspacePageResult.class).getStream().isEmpty()) {
+ var workspaceInfo = response.readEntity(WorkspacePageResult.class).getStream().get(0);
+ responseDTO.setWorkspace(mapper.map(workspaceInfo, getWorkspaceConfigRequestDTO));
+
+ //getDetailed workspace info (incl. products, mfes etc.)
+ try (Response workspaceDetailResponse = workspaceClient.loadWorkspaceByName(workspaceInfo.getName())) {
+ var detailedWorkspaceInfo = workspaceDetailResponse.readEntity(WorkspaceLoad.class);
+
+ //get productStore information for each Product
+ List routes = new ArrayList<>();
+ detailedWorkspaceInfo.getProducts().forEach(p -> {
+ try (Response psResponse = productStoreClient.getProductByName(p.getProductName())) {
+ var product = psResponse.readEntity(ProductPSV1.class);
+ product.getMicrofrontends().forEach(mfe -> {
+ routes.add(mapper.mapRoute(mfe, product, p.getMicrofrontends()));
+ });
+ } catch (WebApplicationException ex) {
+ //skip
+ }
+ });
+ responseDTO.setRoutes(routes);
+ }
+ //get theme info
+ try (Response themeResponse = themeClient.getThemeByName(workspaceInfo.getTheme())) {
+ var themeInfo = themeResponse.readEntity(Theme.class);
+ responseDTO.setTheme(mapper.mapTheme(themeInfo));
+ }
+ //call remoteComponent Mocks => should be removed after implementation
+ responseDTO = mockRemoteComponents(responseDTO);
+ return Response.status(Response.Status.OK).entity(responseDTO).build();
+ } else {
+ return Response.status(Response.Status.NOT_FOUND.getStatusCode(),
+ "No workspace with matching url found").build();
+ }
+ }
+ }
+
+ @Override
+ public Response getThemeFaviconByName(String name) {
+ Response.ResponseBuilder responseBuilder;
+ try (Response response = themeClient.getThemeFaviconByName(name)) {
+ var contentType = response.getHeaderString(HttpHeaders.CONTENT_TYPE);
+ var contentLength = response.getHeaderString(HttpHeaders.CONTENT_LENGTH);
+ var body = response.readEntity(byte[].class);
+ if (contentType != null && body.length != 0) {
+ responseBuilder = Response.status(response.getStatus())
+ .header(HttpHeaders.CONTENT_TYPE, contentType)
+ .header(HttpHeaders.CONTENT_LENGTH, contentLength)
+ .entity(body);
+ } else {
+ responseBuilder = Response.status(Response.Status.BAD_REQUEST);
+ }
+ return responseBuilder.build();
+ }
+ }
+
+ /**
+ * SHOULD BE REMOVED AFTER IMPLEMENTATION
+ *
+ * Method to mock remoteComponents based on application.properties
+ *
+ * @param responseDTO responseDTO without remoteComponents
+ * @return responseDTO with mocked remoteComponents
+ */
+ public GetWorkspaceConfigResponseDTO mockRemoteComponents(GetWorkspaceConfigResponseDTO responseDTO) {
+ List remoteComponents = new ArrayList<>();
+ List remoteShellComponents = new ArrayList<>();
+
+ mockConfig.keys().forEach(componentKey -> {
+ RemoteComponentDTO componentDTO = new RemoteComponentDTO();
+ componentDTO.setName(mockConfig.name().get(componentKey));
+ componentDTO.setAppId(mockConfig.appId().get(componentKey));
+ componentDTO.setBaseUrl(mockConfig.baseUrl().get(componentKey));
+ componentDTO.setRemoteEntryUrl(mockConfig.remoteEntryUrl().get(componentKey));
+ componentDTO.setExposedModule(mockConfig.exposedModule().get(componentKey));
+ componentDTO.setProductName(mockConfig.productName().get(componentKey));
+ remoteComponents.add(componentDTO);
+
+ RemoteComponentMappingDTO componentMappingDTO = new RemoteComponentMappingDTO();
+ componentMappingDTO.setRemoteComponent(mockConfig.name().get(componentKey));
+ componentMappingDTO.setSlotName(mockConfig.slot().get(componentKey));
+ remoteShellComponents.add(componentMappingDTO);
+ });
+ responseDTO.setRemoteComponents(remoteComponents);
+ responseDTO.setShellRemoteComponents(remoteShellComponents);
+ return responseDTO;
+ }
+
+ @ServerExceptionMapper
+ public RestResponse constraint(ConstraintViolationException ex) {
+ return exceptionMapper.constraint(ex);
+ }
+
+ @ServerExceptionMapper
+ public Response restException(WebApplicationException ex) {
+ return Response.status(ex.getResponse().getStatus()).build();
+ }
+}
diff --git a/src/main/java/org/tkit/onecx/shell/bff/rs/log/PermissionLog.java b/src/main/java/org/tkit/onecx/shell/bff/rs/log/PermissionLog.java
new file mode 100644
index 0000000..00ef425
--- /dev/null
+++ b/src/main/java/org/tkit/onecx/shell/bff/rs/log/PermissionLog.java
@@ -0,0 +1,22 @@
+package org.tkit.onecx.shell.bff.rs.log;
+
+import java.util.List;
+
+import jakarta.enterprise.context.ApplicationScoped;
+
+import org.tkit.quarkus.log.cdi.LogParam;
+
+import gen.org.tkit.onecx.shell.bff.rs.internal.model.GetPermissionsRequestDTO;
+
+@ApplicationScoped
+public class PermissionLog implements LogParam {
+ @Override
+ public List- getClasses() {
+ return List.of(
+ this.item(10, GetPermissionsRequestDTO.class,
+ x -> GetPermissionsRequestDTO.class.getSimpleName() + "[productName:"
+ + ((GetPermissionsRequestDTO) x).getProductName() +
+ ", appId:"
+ + ((GetPermissionsRequestDTO) x).getAppId() + "]"));
+ }
+}
diff --git a/src/main/java/org/tkit/onecx/shell/bff/rs/log/WorkspaceConfigLog.java b/src/main/java/org/tkit/onecx/shell/bff/rs/log/WorkspaceConfigLog.java
new file mode 100644
index 0000000..d56a5b6
--- /dev/null
+++ b/src/main/java/org/tkit/onecx/shell/bff/rs/log/WorkspaceConfigLog.java
@@ -0,0 +1,20 @@
+package org.tkit.onecx.shell.bff.rs.log;
+
+import java.util.List;
+
+import jakarta.enterprise.context.ApplicationScoped;
+
+import org.tkit.quarkus.log.cdi.LogParam;
+
+import gen.org.tkit.onecx.shell.bff.rs.internal.model.GetWorkspaceConfigRequestDTO;
+
+@ApplicationScoped
+public class WorkspaceConfigLog implements LogParam {
+ @Override
+ public List
- getClasses() {
+ return List.of(
+ this.item(10, GetWorkspaceConfigRequestDTO.class,
+ x -> GetWorkspaceConfigRequestDTO.class.getSimpleName() + "[baseUrl:"
+ + ((GetWorkspaceConfigRequestDTO) x).getBaseUrl() + "]"));
+ }
+}
diff --git a/src/main/java/org/tkit/onecx/shell/bff/rs/mappers/ExceptionMapper.java b/src/main/java/org/tkit/onecx/shell/bff/rs/mappers/ExceptionMapper.java
new file mode 100644
index 0000000..23e4956
--- /dev/null
+++ b/src/main/java/org/tkit/onecx/shell/bff/rs/mappers/ExceptionMapper.java
@@ -0,0 +1,65 @@
+package org.tkit.onecx.shell.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.org.tkit.onecx.shell.bff.rs.internal.model.ProblemDetailInvalidParamDTO;
+import gen.org.tkit.onecx.shell.bff.rs.internal.model.ProblemDetailParamDTO;
+import gen.org.tkit.onecx.shell.bff.rs.internal.model.ProblemDetailResponseDTO;
+import gen.org.tkit.onecx.user.profile.client.model.ProblemDetailResponse;
+
+@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 = "removeInvalidParamsItem", ignore = true)
+ ProblemDetailResponseDTO map(ProblemDetailResponse problemDetailResponse);
+
+ @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/org/tkit/onecx/shell/bff/rs/mappers/PermissionMapper.java b/src/main/java/org/tkit/onecx/shell/bff/rs/mappers/PermissionMapper.java
new file mode 100644
index 0000000..7fd09d4
--- /dev/null
+++ b/src/main/java/org/tkit/onecx/shell/bff/rs/mappers/PermissionMapper.java
@@ -0,0 +1,19 @@
+package org.tkit.onecx.shell.bff.rs.mappers;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.tkit.onecx.quarkus.permission.client.PermissionResponse;
+
+import gen.org.tkit.onecx.shell.bff.rs.internal.model.GetPermissionsResponseDTO;
+import io.smallrye.mutiny.Uni;
+
+@Mapper
+public interface PermissionMapper {
+ @Mapping(target = "removePermissionsItem", ignore = true)
+ @Mapping(target = "permissions", ignore = true)
+ default GetPermissionsResponseDTO map(Uni rawPermission) {
+ GetPermissionsResponseDTO permissionsResponseDTO = new GetPermissionsResponseDTO();
+ permissionsResponseDTO.setPermissions(rawPermission.await().indefinitely().getActions());
+ return permissionsResponseDTO;
+ }
+}
diff --git a/src/main/java/org/tkit/onecx/shell/bff/rs/mappers/UserProfileMapper.java b/src/main/java/org/tkit/onecx/shell/bff/rs/mappers/UserProfileMapper.java
new file mode 100644
index 0000000..c40a34d
--- /dev/null
+++ b/src/main/java/org/tkit/onecx/shell/bff/rs/mappers/UserProfileMapper.java
@@ -0,0 +1,30 @@
+package org.tkit.onecx.shell.bff.rs.mappers;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+
+import gen.org.tkit.onecx.shell.bff.rs.internal.model.*;
+import gen.org.tkit.onecx.user.profile.client.model.UserPerson;
+import gen.org.tkit.onecx.user.profile.client.model.UserProfile;
+import gen.org.tkit.onecx.user.profile.client.model.UserProfileAccountSettings;
+
+@Mapper
+public interface UserProfileMapper {
+
+ @Mapping(target = "userProfile", source = ".")
+ GetUserProfileResponseDTO map(UserProfile userProfile);
+
+ UserPersonDTO map(UserPerson person);
+
+ default AccountSettingsDTO map(UserProfileAccountSettings accountSettings) {
+ AccountSettingsDTO settingsDTO = new AccountSettingsDTO();
+ settingsDTO.setLayoutAndThemeSettings(mapLayoutAndTheme(accountSettings));
+ settingsDTO.setLocaleAndTimeSettings(mapLocaleAndTime(accountSettings));
+ return settingsDTO;
+ }
+
+ LocaleAndTimeSettingsDTO mapLocaleAndTime(UserProfileAccountSettings accountSettings);
+
+ LayoutAndThemeSettingsDTO mapLayoutAndTheme(UserProfileAccountSettings accountSettings);
+
+}
diff --git a/src/main/java/org/tkit/onecx/shell/bff/rs/mappers/WorkspaceConfigMapper.java b/src/main/java/org/tkit/onecx/shell/bff/rs/mappers/WorkspaceConfigMapper.java
new file mode 100644
index 0000000..73077ce
--- /dev/null
+++ b/src/main/java/org/tkit/onecx/shell/bff/rs/mappers/WorkspaceConfigMapper.java
@@ -0,0 +1,55 @@
+package org.tkit.onecx.shell.bff.rs.mappers;
+
+import java.util.List;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+
+import gen.org.tkit.onecx.product.store.client.model.MicrofrontendPSV1;
+import gen.org.tkit.onecx.product.store.client.model.ProductItemSearchCriteriaPSV1;
+import gen.org.tkit.onecx.product.store.client.model.ProductPSV1;
+import gen.org.tkit.onecx.shell.bff.rs.internal.model.*;
+import gen.org.tkit.onecx.theme.client.model.Theme;
+import gen.org.tkit.onecx.workspace.client.model.Microfrontend;
+import gen.org.tkit.onecx.workspace.client.model.WorkspaceAbstract;
+import gen.org.tkit.onecx.workspace.client.model.WorkspaceSearchCriteria;
+
+@Mapper
+public interface WorkspaceConfigMapper {
+
+ @Mapping(target = "themeName", ignore = true)
+ @Mapping(target = "productName", ignore = true)
+ @Mapping(target = "pageSize", constant = "1")
+ @Mapping(target = "pageNumber", constant = "0")
+ WorkspaceSearchCriteria map(GetWorkspaceConfigRequestDTO getWorkspaceConfigRequestDTO);
+
+ @Mapping(target = "name", source = "workspaceInfo.name")
+ @Mapping(target = "baseUrl", source = "requestDTO.baseUrl")
+ WorkspaceDTO map(WorkspaceAbstract workspaceInfo, GetWorkspaceConfigRequestDTO requestDTO);
+
+ @Mapping(expression = "java( String.valueOf(themeInfo.getProperties()) )", target = "properties")
+ ThemeDTO mapTheme(Theme themeInfo);
+
+ @Mapping(target = "productNames", source = "products")
+ @Mapping(target = "pageSize", ignore = true)
+ @Mapping(target = "pageNumber", ignore = true)
+ ProductItemSearchCriteriaPSV1 map(WorkspaceAbstract workspaceInfo);
+
+ default RouteDTO mapRoute(MicrofrontendPSV1 mfe, ProductPSV1 product,
+ List wsMfes) {
+ RouteDTO route = new RouteDTO();
+ route.setRemoteEntryUrl(mfe.getRemoteEntry());
+ route.setExposedModule(mfe.getExposedModule());
+ route.setProductName(product.getName());
+ route.setAppId(mfe.getAppId());
+ route.setTechnology(TechnologiesDTO.ANGULAR);
+ var selectedMfe = wsMfes.stream().filter(microfrontend -> microfrontend.getMfeId().equals(mfe.getAppId())).findFirst();
+ selectedMfe.ifPresent(microfrontend -> route.setBaseUrl(microfrontend.getBasePath()));
+ route.setUrl(mfe.getRemoteBaseUrl());
+ route.setPathMatch(PathMatchDTO.FULL); //temp fixed value
+ if (mfe.getRemoteName() != null) {
+ route.setRemoteName(mfe.getRemoteName());
+ }
+ return route;
+ }
+}
diff --git a/src/main/openapi/openapi-bff.yaml b/src/main/openapi/openapi-bff.yaml
index e69de29..aa9c3f6 100644
--- a/src/main/openapi/openapi-bff.yaml
+++ b/src/main/openapi/openapi-bff.yaml
@@ -0,0 +1,420 @@
+---
+openapi: 3.0.3
+info:
+ title: onecx-shell-bff
+ description: OneCx shell Bff
+ version: "1.0"
+servers:
+ - url: http://onecx-shell-bff:8080/
+
+paths:
+ /workspaceConfig:
+ post:
+ x-onecx:
+ permissions:
+ workspaceConfig:
+ - read
+ tags:
+ - "WorkspaceConfig"
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/GetWorkspaceConfigRequest'
+ operationId: getWorkspaceConfig
+ responses:
+ '200':
+ description: OK
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/GetWorkspaceConfigResponse'
+ '400':
+ description: Bad request
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ProblemDetailResponse'
+ '404':
+ description: 'Not Found'
+
+ /workspaceConfig/themes/{name}/favicon:
+ get:
+ x-onecx:
+ permissions:
+ workspaceConfig:
+ - read
+ tags:
+ - "WorkspaceConfig"
+ description: Load favicon by theme name
+ operationId: getThemeFaviconByName
+ parameters:
+ - name: name
+ in: path
+ required: true
+ schema:
+ type: string
+ responses:
+ 200:
+ description: OK
+ content:
+ image/*:
+ schema:
+ minimum: 1
+ maximum: 110000
+ type: string
+ format: binary
+ 404:
+ description: Not found
+
+ /userProfile:
+ get:
+ x-onecx:
+ permissions:
+ userProfile:
+ - read
+ tags:
+ - userProfile
+ operationId: getUserProfile
+ responses:
+ '200':
+ description: OK
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/GetUserProfileResponse'
+ '400':
+ description: Bad request
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ProblemDetailResponse'
+ '404':
+ description: 'Not Found'
+
+ /permissions:
+ post:
+ x-onecx:
+ permissions:
+ permission:
+ - read
+ tags:
+ - permission
+ operationId: getPermissions
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/GetPermissionsRequest'
+ responses:
+ '200':
+ description: OK
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/GetPermissionsResponse'
+ '400':
+ description: Bad request
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ProblemDetailResponse'
+ '404':
+ description: 'Not Found'
+
+components:
+ schemas:
+ GetWorkspaceConfigResponse:
+ type: object
+ required:
+ - 'routes'
+ - 'theme'
+ - 'workspace'
+ - 'remoteComponents'
+ - 'shellRemoteComponents'
+ properties:
+ routes:
+ type: array
+ items:
+ $ref: '#/components/schemas/Route'
+ theme:
+ $ref: '#/components/schemas/Theme'
+ workspace:
+ $ref: '#/components/schemas/Workspace'
+ remoteComponents:
+ type: array
+ items:
+ $ref: '#/components/schemas/RemoteComponent'
+ shellRemoteComponents:
+ type: array
+ items:
+ $ref: '#/components/schemas/RemoteComponentMapping'
+
+ GetWorkspaceConfigRequest:
+ type: object
+ required:
+ - baseUrl
+ properties:
+ baseUrl:
+ type: string
+
+ GetPermissionsRequest:
+ type: object
+ required:
+ - appId
+ - productName
+ properties:
+ appId:
+ type: string
+ productName:
+ type: string
+
+ Workspace:
+ type: object
+ required:
+ - name
+ - baseUrl
+ properties:
+ name:
+ type: string
+ baseUrl:
+ type: string
+
+ Theme:
+ required:
+ - name
+ - properties
+ type: object
+ properties:
+ name:
+ minLength: 2
+ type: string
+ cssFile:
+ type: string
+ description:
+ type: string
+ assetsUrl:
+ type: string
+ logoUrl:
+ type: string
+ faviconUrl:
+ type: string
+ previewImageUrl:
+ type: string
+ assetsUpdateDate:
+ type: string
+ properties:
+ type: string
+
+ Route:
+ type: object
+ required:
+ - 'url'
+ - 'baseUrl'
+ - 'remoteEntryUrl'
+ - 'type'
+ - 'exposedModule'
+ - 'appId'
+ - 'productName'
+ - 'pathMatch'
+ properties:
+ url:
+ type: string
+ baseUrl:
+ type: string
+ remoteEntryUrl:
+ type: string
+ appId:
+ type: string
+ productName:
+ type: string
+ technology:
+ $ref: '#/components/schemas/Technologies'
+ exposedModule:
+ type: string
+ pathMatch:
+ $ref: '#/components/schemas/PathMatch'
+ remoteName:
+ type: string
+
+ Technologies:
+ type: string
+ enum: ['Angular', 'WebComponent']
+
+ PathMatch:
+ type: string
+ enum: ['full', 'prefix']
+
+ RemoteComponent:
+ type: object
+ required:
+ - 'name'
+ - 'url'
+ - 'baseUrl'
+ - 'remoteEntryUrl'
+ - 'exposedModule'
+ - 'appId'
+ - 'productName'
+ properties:
+ name:
+ type: string
+ baseUrl:
+ type: string
+ remoteEntryUrl:
+ type: string
+ appId:
+ type: string
+ productName:
+ type: string
+ exposedModule:
+ type: string
+
+ RemoteComponentMapping:
+ type: object
+ required:
+ - 'slotName'
+ - 'remoteComponent'
+ properties:
+ slotName:
+ type: string
+ remoteComponent:
+ type: string
+
+ GetUserProfileResponse:
+ type: object
+ required:
+ - 'userProfile'
+ properties:
+ userProfile:
+ $ref: '#/components/schemas/UserProfile'
+
+ UserProfile:
+ type: object
+ required:
+ - 'userId'
+ - 'person'
+ properties:
+ userId:
+ type: string
+ person:
+ $ref: '#/components/schemas/UserPerson'
+ accountSettings:
+ $ref: '#/components/schemas/AccountSettings'
+
+ UserPerson:
+ type: object
+ properties:
+ 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
+ postalCode:
+ type: string
+ country:
+ type: string
+
+ UserPersonPhone:
+ type: object
+ properties:
+ type:
+ $ref: '#/components/schemas/PhoneType'
+ number:
+ type: string
+
+ PhoneType:
+ type: string
+ enum: ['MOBILE', 'LANDLINE']
+
+ AccountSettings:
+ type: object
+ properties:
+ layoutAndThemeSettings:
+ $ref: '#/components/schemas/LayoutAndThemeSettings'
+ localeAndTimeSettings:
+ $ref: '#/components/schemas/LocaleAndTimeSettings'
+
+ LayoutAndThemeSettings:
+ type: object
+ properties:
+ colorScheme:
+ $ref: '#/components/schemas/ColorScheme'
+ menuMode:
+ $ref: '#/components/schemas/MenuMode'
+
+ ColorScheme:
+ type: string
+ enum: ['AUTO', 'LIGHT', 'DARK']
+
+ MenuMode:
+ type: string
+ enum: ['HORIZONTAL', 'STATIC', 'OVERLAY', 'SLIM', 'SLIMPLUS']
+
+ LocaleAndTimeSettings:
+ type: object
+ properties:
+ locale:
+ type: string
+ timezone:
+ type: string
+
+ GetPermissionsResponse:
+ type: object
+ required:
+ - permissions
+ properties:
+ permissions:
+ type: array
+ items:
+ type: string
+
+ 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
\ No newline at end of file
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index a99e21f..58d3947 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -6,24 +6,31 @@ quarkus.http.auth.permission.default.policy=authenticated
onecx.permissions.application-id=${quarkus.application.name}
+onecx.permission.caching.enabled=true
+onecx.permission.default.separator=#
+
# propagate the apm-principal-token from requests we receive
org.eclipse.microprofile.rest.client.propagateHeaders=apm-principal-token
# PROD
%prod.quarkus.rest-client.onecx_workspace_svc.url=http://onecx-workspace-svc:8080
%prod.quarkus.rest-client.onecx_theme_svc.url=http://onecx-theme-svc:8080
+%prod.quarkus.rest-client.onecx_product_store_svc.url=http://onecx-product-store-svc:8080
%prod.quarkus.rest-client.onecx_permission_svc.url=http://onecx-permission-svc:8080
%prod.quarkus.rest-client.onecx_user_profile_svc.url=http://onecx-user-profile-svc:8080
+#MOCK FOR REMOTE COMPONENTS => should be removed when implemented
+onecx.component.mock.keys[0]=portalmenu
+
%prod.quarkus.oidc-client.client-id=${quarkus.application.name}
# DEV
%dev.quarkus.rest-client.onecx_workspace_svc.url=http://onecx-workspace-svc
%dev.quarkus.rest-client.onecx_theme_svc.url=http://onecx-theme-svc
+%dev.quarkus.rest-client.onecx_product_store_svc.url=http://onecx-product-store-svc
%dev.quarkus.rest-client.onecx_permission_svc.url=http://onecx-permission-svc
%dev.quarkus.rest-client.onecx_user_profile_svc.url=http://onecx-user-profile-svc
-
%dev.quarkus.oidc-client.auth-server-url=${quarkus.oidc.auth-server-url}
%dev.quarkus.oidc-client.client-id=${quarkus.oidc.client-id}
%dev.quarkus.oidc-client.credentials.secret=${quarkus.oidc.credentials.secret}
@@ -39,21 +46,36 @@ quarkus.openapi-generator.codegen.spec.onecx_workspace_svc_external_v1_yaml.conf
quarkus.openapi-generator.codegen.spec.onecx_workspace_svc_external_v1_yaml.base-package=gen.org.tkit.onecx.workspace.client
quarkus.openapi-generator.codegen.spec.onecx_workspace_svc_external_v1_yaml.return-response=true
quarkus.openapi-generator.codegen.spec.onecx_workspace_svc_external_v1_yaml.additional-api-type-annotations=@org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders;
+quarkus.openapi-generator.codegen.spec.onecx_workspace_svc_external_v1_yaml.additional-model-type-annotations=@io.quarkus.runtime.annotations.RegisterForReflection;
+
# theme v1 client
quarkus.openapi-generator.codegen.spec.onecx_theme_svc_external_v1_yaml.config-key=onecx_theme_svc
quarkus.openapi-generator.codegen.spec.onecx_theme_svc_external_v1_yaml.base-package=gen.org.tkit.onecx.theme.client
quarkus.openapi-generator.codegen.spec.onecx_theme_svc_external_v1_yaml.return-response=true
quarkus.openapi-generator.codegen.spec.onecx_theme_svc_external_v1_yaml.additional-api-type-annotations=@org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders;
+quarkus.openapi-generator.codegen.spec.onecx_theme_svc_external_v1_yaml.additional-model-type-annotations=@io.quarkus.runtime.annotations.RegisterForReflection;
+
+# product-store v1 client
+quarkus.openapi-generator.codegen.spec.onecx_product_store_svc_v1_yaml.config-key=onecx_product_store_svc
+quarkus.openapi-generator.codegen.spec.onecx_product_store_svc_v1_yaml.base-package=gen.org.tkit.onecx.product.store.client
+quarkus.openapi-generator.codegen.spec.onecx_product_store_svc_v1_yaml.return-response=true
+quarkus.openapi-generator.codegen.spec.onecx_product_store_svc_v1_yaml.additional-api-type-annotations=@org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders;
+quarkus.openapi-generator.codegen.spec.onecx_product_store_svc_v1_yaml.model-name-suffix=PSV1
+quarkus.openapi-generator.codegen.spec.onecx_product_store_svc_v1_yaml.additional-model-type-annotations=@io.quarkus.runtime.annotations.RegisterForReflection;
+
# permission v1 client
quarkus.openapi-generator.codegen.spec.onecx_permission_svc_v1_yaml.config-key=onecx_permission_svc
quarkus.openapi-generator.codegen.spec.onecx_permission_svc_v1_yaml.base-package=gen.org.tkit.onecx.permission.client
quarkus.openapi-generator.codegen.spec.onecx_permission_svc_v1_yaml.return-response=true
quarkus.openapi-generator.codegen.spec.onecx_permission_svc_v1_yaml.additional-api-type-annotations=@org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders;
+quarkus.openapi-generator.codegen.spec.onecx_permission_svc_v1_yaml.additional-model-type-annotations=@io.quarkus.runtime.annotations.RegisterForReflection;
+
# user profile v1 client
quarkus.openapi-generator.codegen.spec.onecx_user_profile_svc_v1_yaml.config-key=onecx_user_profile_svc
quarkus.openapi-generator.codegen.spec.onecx_user_profile_svc_v1_yaml.base-package=gen.org.tkit.onecx.user.profile.client
quarkus.openapi-generator.codegen.spec.onecx_user_profile_svc_v1_yaml.return-response=true
quarkus.openapi-generator.codegen.spec.onecx_user_profile_svc_v1_yaml.additional-api-type-annotations=@org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders;
+quarkus.openapi-generator.codegen.spec.onecx_user_profile_svc_v1_yaml.additional-model-type-annotations=@io.quarkus.runtime.annotations.RegisterForReflection;
# INTEGRATION TEST
quarkus.test.integration-test-profile=test
@@ -69,6 +91,7 @@ quarkus.test.integration-test-profile=test
%test.quarkus.rest-client.onecx_theme_svc.url=${quarkus.mockserver.endpoint}
%test.quarkus.rest-client.onecx_permission_svc.url=${quarkus.mockserver.endpoint}
%test.quarkus.rest-client.onecx_user_profile_svc.url=${quarkus.mockserver.endpoint}
+%test.quarkus.rest-client.onecx_product_store_svc.url=${quarkus.mockserver.endpoint}
%test.tkit.rs.context.token.header-param=apm-principal-token
%test.tkit.rs.context.token.enabled=false
@@ -76,6 +99,7 @@ quarkus.test.integration-test-profile=test
%test.quarkus.rest-client.onecx_theme_svc.providers=io.quarkus.oidc.client.reactive.filter.OidcClientRequestReactiveFilter
%test.quarkus.rest-client.onecx_permission_svc.providers=io.quarkus.oidc.client.reactive.filter.OidcClientRequestReactiveFilter
%test.quarkus.rest-client.onecx_user_profile_svc.providers=io.quarkus.oidc.client.reactive.filter.OidcClientRequestReactiveFilter
+%test.quarkus.rest-client.onecx_product_store_svc.providers=io.quarkus.oidc.client.reactive.filter.OidcClientRequestReactiveFilter
%test.tkit.rs.context.tenant-id.mock.claim-org-id=orgId
%test.quarkus.rest-client.onecx_permission.url=${quarkus.mockserver.endpoint}
@@ -85,6 +109,18 @@ quarkus.test.integration-test-profile=test
%test.quarkus.oidc-client.client-id=${quarkus.oidc.client-id}
%test.quarkus.oidc-client.credentials.secret=${quarkus.oidc.credentials.secret}
%test.onecx.permissions.product-name=applications
+%test.onecx.permission.caching.enabled=false
+
+#MOCK FOR REMOTE COMPONENTS => should be removed when implemented
+%test.onecx.component.mock.keys[0]=portalmenu
+%test.onecx.component.mock.name.portalmenu=PortalMenu
+%test.onecx.component.mock.appid.portalmenu=appId
+%test.onecx.component.mock.baseurl.portalmenu=http://localhost:4400/core/portal-mgmt/
+%test.onecx.component.mock.remotebaseurl.portalmenu=http://localhost:4400/core/portal-mgmt/
+%test.onecx.component.mock.remoteentryurl.portalmenu=http://localhost:4400/core/portal-mgmt/remoteEntry.js
+%test.onecx.component.mock.productname.portalmenu=PortalMgmt
+%test.onecx.component.mock.exposedmodule.portalmenu=MenuComponent
+%test.onecx.component.mock.slot.portalmenu=menu
# PIPE CONFIG
diff --git a/src/test/java/org/tkit/onecx/shell/bff/rs/AbstractTest.java b/src/test/java/org/tkit/onecx/shell/bff/rs/AbstractTest.java
index e7019ca..f5c9885 100644
--- a/src/test/java/org/tkit/onecx/shell/bff/rs/AbstractTest.java
+++ b/src/test/java/org/tkit/onecx/shell/bff/rs/AbstractTest.java
@@ -1,15 +1,17 @@
package org.tkit.onecx.shell.bff.rs;
+import org.eclipse.microprofile.config.ConfigProvider;
+
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.quarkus.test.keycloak.client.KeycloakTestClient;
import io.restassured.RestAssured;
import io.restassured.config.ObjectMapperConfig;
import io.restassured.config.RestAssuredConfig;
-import org.eclipse.microprofile.config.ConfigProvider;
@QuarkusTestResource(MockServerTestResource.class)
public abstract class AbstractTest {
@@ -22,6 +24,8 @@ public abstract class AbstractTest {
protected static final String APM_HEADER_PARAM = ConfigProvider.getConfig()
.getValue("%test.tkit.rs.context.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(
diff --git a/src/test/java/org/tkit/onecx/shell/bff/rs/PermissionRestControllerIT.java b/src/test/java/org/tkit/onecx/shell/bff/rs/PermissionRestControllerIT.java
new file mode 100644
index 0000000..f459cea
--- /dev/null
+++ b/src/test/java/org/tkit/onecx/shell/bff/rs/PermissionRestControllerIT.java
@@ -0,0 +1,7 @@
+package org.tkit.onecx.shell.bff.rs;
+
+import io.quarkus.test.junit.QuarkusIntegrationTest;
+
+@QuarkusIntegrationTest
+public class PermissionRestControllerIT extends PermissionRestControllerTest {
+}
diff --git a/src/test/java/org/tkit/onecx/shell/bff/rs/PermissionRestControllerTest.java b/src/test/java/org/tkit/onecx/shell/bff/rs/PermissionRestControllerTest.java
new file mode 100644
index 0000000..4c6e725
--- /dev/null
+++ b/src/test/java/org/tkit/onecx/shell/bff/rs/PermissionRestControllerTest.java
@@ -0,0 +1,165 @@
+package org.tkit.onecx.shell.bff.rs;
+
+import static io.restassured.RestAssured.given;
+import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON;
+import static org.mockserver.model.HttpRequest.request;
+import static org.mockserver.model.HttpResponse.response;
+
+import java.util.*;
+
+import jakarta.ws.rs.HttpMethod;
+import jakarta.ws.rs.core.Response;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.mockserver.client.MockServerClient;
+import org.mockserver.model.JsonBody;
+import org.mockserver.model.MediaType;
+import org.tkit.onecx.shell.bff.rs.controllers.PermissionRestController;
+
+import gen.org.tkit.onecx.permission.model.ApplicationPermissions;
+import gen.org.tkit.onecx.permission.model.PermissionRequest;
+import gen.org.tkit.onecx.shell.bff.rs.internal.model.GetPermissionsRequestDTO;
+import gen.org.tkit.onecx.shell.bff.rs.internal.model.GetPermissionsResponseDTO;
+import gen.org.tkit.onecx.shell.bff.rs.internal.model.ProblemDetailResponseDTO;
+import io.quarkiverse.mockserver.test.InjectMockServerClient;
+import io.quarkus.test.common.http.TestHTTPEndpoint;
+import io.quarkus.test.junit.QuarkusTest;
+
+@QuarkusTest
+@TestHTTPEndpoint(PermissionRestController.class)
+class PermissionRestControllerTest extends AbstractTest {
+
+ @InjectMockServerClient
+ MockServerClient mockServerClient;
+
+ @Test
+ void getPermissionsTest() {
+
+ ApplicationPermissions applicationPermissions = new ApplicationPermissions();
+ Map> permissions = new HashMap<>();
+ permissions.put("workspaceConfig", Set.of("read", "write", "delete"));
+ permissions.put("userProfile", Set.of("read", "write", "delete"));
+ permissions.put("permission", Set.of("read"));
+ permissions.put("permissions", Set.of("admin-write", "admin-read"));
+ applicationPermissions.setPermissions(permissions);
+ applicationPermissions.setAppId("onecx-shell-bff");
+ applicationPermissions.setProductName("onecx-shell");
+
+ String AUTHTOKEN = keycloakClient.getAccessToken(ADMIN);
+ PermissionRequest permissionRequest = new PermissionRequest();
+ permissionRequest.setToken("Bearer " + AUTHTOKEN);
+ // create mock rest endpoint for permission svc
+ mockServerClient
+ .when(request().withPath("/v1/permissions/user/applications/onecx-shell-bff").withMethod(HttpMethod.POST)
+ .withContentType(MediaType.APPLICATION_JSON)
+ .withBody(JsonBody.json(permissionRequest)))
+ .withId("mockPermission")
+ .respond(httpRequest -> response().withStatusCode(Response.Status.OK.getStatusCode())
+ .withContentType(MediaType.APPLICATION_JSON)
+ .withBody(JsonBody.json(applicationPermissions)));
+
+ // create mock rest endpoint for permission svc
+ mockServerClient.when(request().withPath("/v1/permissions/user/product1/app1").withMethod(HttpMethod.POST)
+ .withContentType(MediaType.APPLICATION_JSON)
+ .withBody(JsonBody.json(permissionRequest)))
+ .withId("mockPermission2")
+ .respond(httpRequest -> response().withStatusCode(Response.Status.OK.getStatusCode())
+ .withContentType(MediaType.APPLICATION_JSON)
+ .withBody(JsonBody.json(applicationPermissions)));
+
+ GetPermissionsRequestDTO requestDTO = new GetPermissionsRequestDTO();
+ requestDTO.setAppId("app1");
+ requestDTO.setProductName("product1");
+ var output = given()
+ .when()
+ .auth().oauth2(AUTHTOKEN)
+ .header(APM_HEADER_PARAM, ADMIN)
+ .contentType(APPLICATION_JSON)
+ .body(requestDTO)
+ .post()
+ .then()
+ .statusCode(Response.Status.OK.getStatusCode())
+ .contentType(APPLICATION_JSON)
+ .extract().as(GetPermissionsResponseDTO.class);
+ Assertions.assertNotNull(output);
+ Assertions.assertTrue(output.getPermissions().containsAll(
+ List.of("permissions#admin-write", "permissions#admin-read", "permission#read",
+ "workspaceConfig#read", "workspaceConfig#delete", "workspaceConfig#write",
+ "workspaceConfig#write", "userProfile#read", "userProfile#delete", "userProfile#write")));
+
+ mockServerClient.clear("mockPermission2");
+ mockServerClient.clear("mockPermission");
+ }
+
+ @Test
+ void getPermissions_missing_input_Test() {
+ String AUTHTOKEN = keycloakClient.getAccessToken(ADMIN);
+
+ GetPermissionsRequestDTO requestDTO = new GetPermissionsRequestDTO();
+ var output = given()
+ .when()
+ .auth().oauth2(AUTHTOKEN)
+ .header(APM_HEADER_PARAM, ADMIN)
+ .contentType(APPLICATION_JSON)
+ .body(requestDTO)
+ .post()
+ .then()
+ .statusCode(Response.Status.BAD_REQUEST.getStatusCode())
+ .contentType(APPLICATION_JSON)
+ .extract().as(ProblemDetailResponseDTO.class);
+ Assertions.assertNotNull(output);
+ }
+
+ @Test
+ void getPermissions_svc_error_Test() {
+
+ ApplicationPermissions applicationPermissions = new ApplicationPermissions();
+ Map> permissions = new HashMap<>();
+ permissions.put("workspaceConfig", Set.of("read", "write", "delete"));
+ permissions.put("userProfile", Set.of("read", "write", "delete"));
+ permissions.put("permission", Set.of("read"));
+ permissions.put("permissions", Set.of("admin-write", "admin-read"));
+ applicationPermissions.setPermissions(permissions);
+ applicationPermissions.setAppId("onecx-shell-bff");
+ applicationPermissions.setProductName("onecx-shell");
+
+ String AUTHTOKEN = keycloakClient.getAccessToken(ADMIN);
+ PermissionRequest permissionRequest = new PermissionRequest();
+ permissionRequest.setToken("Bearer " + AUTHTOKEN);
+ // create mock rest endpoint for permission svc
+ mockServerClient
+ .when(request().withPath("/v1/permissions/user/applications/onecx-shell-bff").withMethod(HttpMethod.POST)
+ .withContentType(MediaType.APPLICATION_JSON)
+ .withBody(JsonBody.json(permissionRequest)))
+ .withId("mockPermission")
+ .respond(httpRequest -> response().withStatusCode(Response.Status.OK.getStatusCode())
+ .withContentType(MediaType.APPLICATION_JSON)
+ .withBody(JsonBody.json(applicationPermissions)));
+
+ // create mock rest endpoint for permission svc
+ mockServerClient.when(request().withPath("/v1/permissions/user/product1/app1").withMethod(HttpMethod.POST)
+ .withContentType(MediaType.APPLICATION_JSON)
+ .withBody(JsonBody.json(permissionRequest)))
+ .withId("mockPermission2")
+ .respond(httpRequest -> response().withStatusCode(Response.Status.BAD_REQUEST.getStatusCode()));
+
+ GetPermissionsRequestDTO requestDTO = new GetPermissionsRequestDTO();
+ requestDTO.setAppId("app1");
+ requestDTO.setProductName("product1");
+ var output = given()
+ .when()
+ .auth().oauth2(AUTHTOKEN)
+ .header(APM_HEADER_PARAM, ADMIN)
+ .contentType(APPLICATION_JSON)
+ .body(requestDTO)
+ .post()
+ .then()
+ .statusCode(Response.Status.BAD_REQUEST.getStatusCode());
+ Assertions.assertNotNull(output);
+
+ mockServerClient.clear("mockPermission2");
+ mockServerClient.clear("mockPermission");
+
+ }
+}
diff --git a/src/test/java/org/tkit/onecx/shell/bff/rs/UserProfileControllerIT.java b/src/test/java/org/tkit/onecx/shell/bff/rs/UserProfileControllerIT.java
new file mode 100644
index 0000000..aff16f6
--- /dev/null
+++ b/src/test/java/org/tkit/onecx/shell/bff/rs/UserProfileControllerIT.java
@@ -0,0 +1,7 @@
+package org.tkit.onecx.shell.bff.rs;
+
+import io.quarkus.test.junit.QuarkusIntegrationTest;
+
+@QuarkusIntegrationTest
+public class UserProfileControllerIT extends UserProfileControllerTest {
+}
diff --git a/src/test/java/org/tkit/onecx/shell/bff/rs/UserProfileControllerTest.java b/src/test/java/org/tkit/onecx/shell/bff/rs/UserProfileControllerTest.java
new file mode 100644
index 0000000..0438174
--- /dev/null
+++ b/src/test/java/org/tkit/onecx/shell/bff/rs/UserProfileControllerTest.java
@@ -0,0 +1,83 @@
+package org.tkit.onecx.shell.bff.rs;
+
+import static io.restassured.RestAssured.given;
+import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON;
+import static org.mockserver.model.HttpRequest.request;
+import static org.mockserver.model.HttpResponse.response;
+
+import jakarta.ws.rs.HttpMethod;
+import jakarta.ws.rs.core.Response;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.mockserver.client.MockServerClient;
+import org.mockserver.model.JsonBody;
+import org.mockserver.model.MediaType;
+import org.tkit.onecx.shell.bff.rs.controllers.UserProfileRestController;
+
+import gen.org.tkit.onecx.shell.bff.rs.internal.model.GetUserProfileResponseDTO;
+import gen.org.tkit.onecx.user.profile.client.model.MenuMode;
+import gen.org.tkit.onecx.user.profile.client.model.UserPerson;
+import gen.org.tkit.onecx.user.profile.client.model.UserProfile;
+import gen.org.tkit.onecx.user.profile.client.model.UserProfileAccountSettings;
+import io.quarkiverse.mockserver.test.InjectMockServerClient;
+import io.quarkus.test.common.http.TestHTTPEndpoint;
+import io.quarkus.test.junit.QuarkusTest;
+
+@QuarkusTest
+@TestHTTPEndpoint(UserProfileRestController.class)
+class UserProfileControllerTest extends AbstractTest {
+
+ @InjectMockServerClient
+ MockServerClient mockServerClient;
+
+ @Test
+ void getUserProfileTest() {
+
+ UserProfile profileResponse = new UserProfile();
+ profileResponse.organization("org1").id("profile1")
+ .person(new UserPerson().firstName("Max").lastName("Mustermann").email("testEmail"))
+ .accountSettings(new UserProfileAccountSettings().menuMode(MenuMode.HORIZONTAL));
+
+ // create mock rest endpoint for user-profile-svc
+ mockServerClient.when(request().withPath("/v1/userProfile/me").withMethod(HttpMethod.GET))
+ .withId("mock")
+ .respond(httpRequest -> response().withStatusCode(Response.Status.OK.getStatusCode())
+ .withContentType(MediaType.APPLICATION_JSON)
+ .withBody(JsonBody.json(profileResponse)));
+
+ var output = given()
+ .when()
+ .auth().oauth2(keycloakClient.getAccessToken(ADMIN))
+ .header(APM_HEADER_PARAM, ADMIN)
+ .get()
+ .then()
+ .statusCode(Response.Status.OK.getStatusCode())
+ .contentType(APPLICATION_JSON)
+ .extract().as(GetUserProfileResponseDTO.class);
+
+ Assertions.assertNotNull(output);
+
+ mockServerClient.clear("mock");
+ }
+
+ @Test
+ void getUserProfile_svc_error_Test() {
+
+ // create mock rest endpoint for user-profile-svc
+ mockServerClient.when(request().withPath("/v1/userProfile/me").withMethod(HttpMethod.GET))
+ .withId("mock")
+ .respond(httpRequest -> response().withStatusCode(Response.Status.BAD_REQUEST.getStatusCode()));
+
+ var output = given()
+ .when()
+ .auth().oauth2(keycloakClient.getAccessToken(ADMIN))
+ .header(APM_HEADER_PARAM, ADMIN)
+ .get()
+ .then()
+ .statusCode(Response.Status.BAD_REQUEST.getStatusCode());
+
+ Assertions.assertNotNull(output);
+ mockServerClient.clear("mock");
+ }
+}
diff --git a/src/test/java/org/tkit/onecx/shell/bff/rs/WorkspaceConfigRestControllerIT.java b/src/test/java/org/tkit/onecx/shell/bff/rs/WorkspaceConfigRestControllerIT.java
new file mode 100644
index 0000000..3286c42
--- /dev/null
+++ b/src/test/java/org/tkit/onecx/shell/bff/rs/WorkspaceConfigRestControllerIT.java
@@ -0,0 +1,7 @@
+package org.tkit.onecx.shell.bff.rs;
+
+import io.quarkus.test.junit.QuarkusIntegrationTest;
+
+@QuarkusIntegrationTest
+public class WorkspaceConfigRestControllerIT extends WorkspaceConfigRestControllerTest {
+}
diff --git a/src/test/java/org/tkit/onecx/shell/bff/rs/WorkspaceConfigRestControllerTest.java b/src/test/java/org/tkit/onecx/shell/bff/rs/WorkspaceConfigRestControllerTest.java
new file mode 100644
index 0000000..6f0de8f
--- /dev/null
+++ b/src/test/java/org/tkit/onecx/shell/bff/rs/WorkspaceConfigRestControllerTest.java
@@ -0,0 +1,461 @@
+package org.tkit.onecx.shell.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.*;
+import static org.mockserver.model.HttpRequest.request;
+import static org.mockserver.model.HttpResponse.response;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import jakarta.ws.rs.HttpMethod;
+import jakarta.ws.rs.core.HttpHeaders;
+import jakarta.ws.rs.core.Response;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.mockserver.client.MockServerClient;
+import org.mockserver.model.Header;
+import org.mockserver.model.JsonBody;
+import org.mockserver.model.MediaType;
+import org.tkit.onecx.shell.bff.rs.controllers.WorkspaceConfigRestController;
+
+import gen.org.tkit.onecx.product.store.client.model.MicrofrontendPSV1;
+import gen.org.tkit.onecx.product.store.client.model.ProductPSV1;
+import gen.org.tkit.onecx.shell.bff.rs.internal.model.GetWorkspaceConfigRequestDTO;
+import gen.org.tkit.onecx.shell.bff.rs.internal.model.GetWorkspaceConfigResponseDTO;
+import gen.org.tkit.onecx.shell.bff.rs.internal.model.ProblemDetailResponseDTO;
+import gen.org.tkit.onecx.theme.client.model.Theme;
+import gen.org.tkit.onecx.workspace.client.model.*;
+import io.quarkiverse.mockserver.test.InjectMockServerClient;
+import io.quarkus.test.common.http.TestHTTPEndpoint;
+import io.quarkus.test.junit.QuarkusTest;
+
+@QuarkusTest
+@TestHTTPEndpoint(WorkspaceConfigRestController.class)
+class WorkspaceConfigRestControllerTest extends AbstractTest {
+
+ @InjectMockServerClient
+ MockServerClient mockServerClient;
+
+ @Test
+ void getWorkspaceConfigByBaseUrlTest() {
+ WorkspaceSearchCriteria criteria = new WorkspaceSearchCriteria();
+ criteria.setBaseUrl("/w1Url");
+ criteria.setPageNumber(0);
+ criteria.setPageSize(1);
+ WorkspacePageResult pageResult = new WorkspacePageResult();
+ pageResult.setStream(List.of(new WorkspaceAbstract().name("w1").theme("theme1").products(List.of("p1"))));
+
+ // create mock rest endpoint for workspace search
+ mockServerClient.when(request().withPath("/v1/workspaces/search").withMethod(HttpMethod.POST)
+ .withContentType(MediaType.APPLICATION_JSON)
+ .withBody(JsonBody.json(criteria)))
+ .withId("mockWS")
+ .respond(httpRequest -> response().withStatusCode(Response.Status.OK.getStatusCode())
+ .withContentType(MediaType.APPLICATION_JSON)
+ .withBody(JsonBody.json(pageResult)));
+
+ WorkspaceLoad loadResponse = new WorkspaceLoad();
+ loadResponse.setName("w1");
+ Product product1 = new Product();
+ product1.baseUrl("/product1").productName("product1").microfrontends(List.of(
+ new Microfrontend().basePath("/app1").mfeId("app1")));
+ loadResponse.setProducts(List.of(product1));
+
+ // create mock rest endpoint for load workspace by name
+ mockServerClient.when(request().withPath("/v1/workspaces/w1/load").withMethod(HttpMethod.GET))
+ .withId("mockWSLoad")
+ .respond(httpRequest -> response().withStatusCode(Response.Status.OK.getStatusCode())
+ .withContentType(MediaType.APPLICATION_JSON)
+ .withBody(JsonBody.json(loadResponse)));
+
+ ProductPSV1 productResponse = new ProductPSV1();
+ productResponse.basePath("/product1").name("product1").microfrontends(List.of(
+ new MicrofrontendPSV1().exposedModule("App1Module")
+ .appName("app1")
+ .remoteBaseUrl("/remoteBaseUrl")
+ .remoteEntry("/remoteEntry.js")
+ .technology("ANGULAR")));
+ // create mock rest endpoint for get product by name from product-store
+ mockServerClient.when(request().withPath("/v1/products/product1").withMethod(HttpMethod.GET))
+ .withId("mockPS")
+ .respond(httpRequest -> response().withStatusCode(Response.Status.OK.getStatusCode())
+ .withContentType(MediaType.APPLICATION_JSON)
+ .withBody(JsonBody.json(productResponse)));
+
+ Theme themeResponse = new Theme();
+ themeResponse.name("theme1").cssFile("cssfile").properties(new Object());
+ // create mock rest endpoint for get theme by name from theme-svc
+ mockServerClient.when(request().withPath("/v1/themes/theme1").withMethod(HttpMethod.GET))
+ .withId("mockTheme")
+ .respond(httpRequest -> response().withStatusCode(Response.Status.OK.getStatusCode())
+ .withContentType(MediaType.APPLICATION_JSON)
+ .withBody(JsonBody.json(themeResponse)));
+
+ var input = new GetWorkspaceConfigRequestDTO();
+ input.setBaseUrl("/w1Url");
+
+ var output = given()
+ .when()
+ .auth().oauth2(keycloakClient.getAccessToken(ADMIN))
+ .header(APM_HEADER_PARAM, ADMIN)
+ .contentType(APPLICATION_JSON)
+ .body(input)
+ .post()
+ .then()
+ .statusCode(Response.Status.OK.getStatusCode())
+ .contentType(APPLICATION_JSON)
+ .extract().as(GetWorkspaceConfigResponseDTO.class);
+
+ Assertions.assertNotNull(output);
+ Assertions.assertEquals(output.getWorkspace().getName(), "w1");
+ Assertions.assertEquals(output.getTheme().getName(), "theme1");
+ Assertions.assertEquals(output.getRoutes().size(), 1);
+
+ //CHECK FOR MOCKED REMOTE COMPONENTS
+ //SHOULD BE REMOVED AFTER IMPLEMENTATION
+ Assertions.assertEquals(output.getRemoteComponents().get(0).getName(), "PortalMenu");
+ Assertions.assertEquals(output.getRemoteComponents().get(0).getAppId(), "appId");
+ Assertions.assertEquals(output.getShellRemoteComponents().get(0).getSlotName(), "menu");
+
+ mockServerClient.clear("mockWS");
+ mockServerClient.clear("mockPS");
+ mockServerClient.clear("mockTheme");
+ mockServerClient.clear("mockWSLoad");
+ }
+
+ @Test
+ void getWorkspaceConfig_MissingBaseUrlTest() {
+ var input = new GetWorkspaceConfigRequestDTO();
+
+ var output = given()
+ .when()
+ .auth().oauth2(keycloakClient.getAccessToken(ADMIN))
+ .header(APM_HEADER_PARAM, ADMIN)
+ .contentType(APPLICATION_JSON)
+ .body(input)
+ .post()
+ .then()
+ .statusCode(BAD_REQUEST.getStatusCode())
+ .contentType(APPLICATION_JSON)
+ .extract().as(ProblemDetailResponseDTO.class);
+
+ Assertions.assertNotNull(output);
+ }
+
+ @Test
+ void getWorkspaceConfig_WorkspaceNotFoundTest() {
+ WorkspaceSearchCriteria criteria = new WorkspaceSearchCriteria();
+ criteria.setBaseUrl("/w1Url");
+ criteria.setPageNumber(0);
+ criteria.setPageSize(1);
+ WorkspacePageResult pageResult = new WorkspacePageResult();
+ pageResult.setStream(new ArrayList<>());
+
+ // create mock rest endpoint for workspace search
+ mockServerClient.when(request().withPath("/v1/workspaces/search").withMethod(HttpMethod.POST)
+ .withContentType(MediaType.APPLICATION_JSON)
+ .withBody(JsonBody.json(criteria)))
+ .withId("mockWS")
+ .respond(httpRequest -> response().withStatusCode(Response.Status.OK.getStatusCode())
+ .withContentType(MediaType.APPLICATION_JSON)
+ .withBody(JsonBody.json(pageResult)));
+
+ var input = new GetWorkspaceConfigRequestDTO();
+ input.setBaseUrl("/w1Url");
+
+ var output = given()
+ .when()
+ .auth().oauth2(keycloakClient.getAccessToken(ADMIN))
+ .header(APM_HEADER_PARAM, ADMIN)
+ .contentType(APPLICATION_JSON)
+ .body(input)
+ .post()
+ .then()
+ .statusCode(NOT_FOUND.getStatusCode());
+ Assertions.assertNotNull(output);
+ mockServerClient.clear("mockWS");
+ }
+
+ @Test
+ void getWorkspaceConfig_searchWorkspace_Bad_Request_Test() {
+ WorkspaceSearchCriteria criteria = new WorkspaceSearchCriteria();
+ criteria.setBaseUrl("/w1Url");
+ criteria.setPageNumber(0);
+ criteria.setPageSize(1);
+
+ // create mock rest endpoint for workspace search
+ mockServerClient.when(request().withPath("/v1/workspaces/search").withMethod(HttpMethod.POST)
+ .withContentType(MediaType.APPLICATION_JSON)
+ .withBody(JsonBody.json(criteria)))
+ .withId("mockWS")
+ .respond(httpRequest -> response().withStatusCode(BAD_REQUEST.getStatusCode()));
+
+ var input = new GetWorkspaceConfigRequestDTO();
+ input.setBaseUrl("/w1Url");
+
+ var output = given()
+ .when()
+ .auth().oauth2(keycloakClient.getAccessToken(ADMIN))
+ .header(APM_HEADER_PARAM, ADMIN)
+ .contentType(APPLICATION_JSON)
+ .body(input)
+ .post()
+ .then()
+ .statusCode(BAD_REQUEST.getStatusCode());
+ Assertions.assertNotNull(output);
+ mockServerClient.clear("mockWS");
+ }
+
+ @Test
+ void getWorkspaceConfig_ProductNotFoundTest() {
+ WorkspaceSearchCriteria criteria = new WorkspaceSearchCriteria();
+ criteria.setBaseUrl("/w1Url");
+ criteria.setPageNumber(0);
+ criteria.setPageSize(1);
+ WorkspacePageResult pageResult = new WorkspacePageResult();
+ pageResult.setStream(List.of(new WorkspaceAbstract().name("w1").theme("theme1").products(List.of("p1"))));
+
+ // create mock rest endpoint for workspace search
+ mockServerClient.when(request().withPath("/v1/workspaces/search").withMethod(HttpMethod.POST)
+ .withContentType(MediaType.APPLICATION_JSON)
+ .withBody(JsonBody.json(criteria)))
+ .withId("mockWS")
+ .respond(httpRequest -> response().withStatusCode(Response.Status.OK.getStatusCode())
+ .withContentType(MediaType.APPLICATION_JSON)
+ .withBody(JsonBody.json(pageResult)));
+
+ WorkspaceLoad loadResponse = new WorkspaceLoad();
+ loadResponse.setName("w1");
+ Product product1 = new Product();
+ product1.baseUrl("/product1").productName("product1").microfrontends(List.of(
+ new Microfrontend().basePath("/app1").mfeId("app1")));
+ loadResponse.setProducts(List.of(product1));
+
+ // create mock rest endpoint for load workspace by name
+ mockServerClient.when(request().withPath("/v1/workspaces/w1/load").withMethod(HttpMethod.GET))
+ .withId("mockWSLoad")
+ .respond(httpRequest -> response().withStatusCode(Response.Status.OK.getStatusCode())
+ .withContentType(MediaType.APPLICATION_JSON)
+ .withBody(JsonBody.json(loadResponse)));
+
+ // create mock rest endpoint for get product by name from product-store NOT-FOUND
+ mockServerClient.when(request().withPath("/v1/products/product1").withMethod(HttpMethod.GET))
+ .withId("mockPS")
+ .respond(httpRequest -> response().withStatusCode(NOT_FOUND.getStatusCode()));
+
+ Theme themeResponse = new Theme();
+ themeResponse.name("theme1").cssFile("cssfile").properties(new Object());
+ // create mock rest endpoint for get theme by name from theme-svc
+ mockServerClient.when(request().withPath("/v1/themes/theme1").withMethod(HttpMethod.GET))
+ .withId("mockTheme")
+ .respond(httpRequest -> response().withStatusCode(Response.Status.OK.getStatusCode())
+ .withContentType(MediaType.APPLICATION_JSON)
+ .withBody(JsonBody.json(themeResponse)));
+
+ var input = new GetWorkspaceConfigRequestDTO();
+ input.setBaseUrl("/w1Url");
+
+ var output = given()
+ .when()
+ .auth().oauth2(keycloakClient.getAccessToken(ADMIN))
+ .header(APM_HEADER_PARAM, ADMIN)
+ .contentType(APPLICATION_JSON)
+ .body(input)
+ .post()
+ .then()
+ .statusCode(OK.getStatusCode())
+ .contentType(APPLICATION_JSON)
+ .extract().as(GetWorkspaceConfigResponseDTO.class);
+
+ Assertions.assertNotNull(output);
+
+ mockServerClient.clear("mockWS");
+ mockServerClient.clear("mockPS");
+ mockServerClient.clear("mockWSLoad");
+ mockServerClient.clear("mockTheme");
+
+ }
+
+ @Test
+ void getWorkspaceConfig_searchTheme_throws_BAD_REQUEST_Test() {
+ WorkspaceSearchCriteria criteria = new WorkspaceSearchCriteria();
+ criteria.setBaseUrl("/w1Url");
+ criteria.setPageNumber(0);
+ criteria.setPageSize(1);
+ WorkspacePageResult pageResult = new WorkspacePageResult();
+ pageResult.setStream(List.of(new WorkspaceAbstract().name("w1").theme("theme1").products(List.of("p1"))));
+
+ // create mock rest endpoint for workspace search
+ mockServerClient.when(request().withPath("/v1/workspaces/search").withMethod(HttpMethod.POST)
+ .withContentType(MediaType.APPLICATION_JSON)
+ .withBody(JsonBody.json(criteria)))
+ .withId("mockWS")
+ .respond(httpRequest -> response().withStatusCode(Response.Status.OK.getStatusCode())
+ .withContentType(MediaType.APPLICATION_JSON)
+ .withBody(JsonBody.json(pageResult)));
+
+ WorkspaceLoad loadResponse = new WorkspaceLoad();
+ loadResponse.setName("w1");
+ Product product1 = new Product();
+ product1.baseUrl("/product1").productName("product1").microfrontends(List.of(
+ new Microfrontend().basePath("/app1").mfeId("app1")));
+ loadResponse.setProducts(List.of(product1));
+
+ // create mock rest endpoint for load workspace by name
+ mockServerClient.when(request().withPath("/v1/workspaces/w1/load").withMethod(HttpMethod.GET))
+ .withId("mockWSLoad")
+ .respond(httpRequest -> response().withStatusCode(Response.Status.OK.getStatusCode())
+ .withContentType(MediaType.APPLICATION_JSON)
+ .withBody(JsonBody.json(loadResponse)));
+
+ ProductPSV1 productResponse = new ProductPSV1();
+ productResponse.basePath("/product1").name("product1").microfrontends(List.of(
+ new MicrofrontendPSV1().exposedModule("App1Module")
+ .appName("app1")
+ .remoteBaseUrl("/remoteBaseUrl")
+ .remoteEntry("/remoteEntry.js")
+ .technology("ANGULAR")));
+ // create mock rest endpoint for get product by name from product-store
+ mockServerClient.when(request().withPath("/v1/products/product1").withMethod(HttpMethod.GET))
+ .withId("mockPS")
+ .respond(httpRequest -> response().withStatusCode(Response.Status.OK.getStatusCode())
+ .withContentType(MediaType.APPLICATION_JSON)
+ .withBody(JsonBody.json(productResponse)));
+
+ Theme themeResponse = new Theme();
+ themeResponse.name("theme1").cssFile("cssfile").properties(new Object());
+ // create mock rest endpoint for get theme by name from theme-svc
+ mockServerClient.when(request().withPath("/v1/themes/theme1").withMethod(HttpMethod.GET))
+ .withId("mockTheme")
+ .respond(httpRequest -> response().withStatusCode(BAD_REQUEST.getStatusCode()));
+
+ var input = new GetWorkspaceConfigRequestDTO();
+ input.setBaseUrl("/w1Url");
+
+ var output = given()
+ .when()
+ .auth().oauth2(keycloakClient.getAccessToken(ADMIN))
+ .header(APM_HEADER_PARAM, ADMIN)
+ .contentType(APPLICATION_JSON)
+ .body(input)
+ .post()
+ .then()
+ .statusCode(BAD_REQUEST.getStatusCode());
+
+ Assertions.assertNotNull(output);
+
+ mockServerClient.clear("mockWS");
+ mockServerClient.clear("mockPS");
+ mockServerClient.clear("mockTheme");
+ mockServerClient.clear("mockWSLoad");
+ }
+
+ @Test
+ void getThemeFaviconTest() {
+
+ byte[] bytesRes = new byte[] { (byte) 0xe0, 0x4f, (byte) 0xd0,
+ 0x20, (byte) 0xea, 0x3a, 0x69, 0x10, (byte) 0xa2, (byte) 0xd8, 0x08, 0x00, 0x2b,
+ 0x30, 0x30, (byte) 0x9d };
+
+ // create mock rest endpoint for get theme by name from theme-svc
+ mockServerClient.when(request().withPath("/v1/themes/theme1/favicon").withMethod(HttpMethod.GET))
+ .withId("mockFavicon")
+ .respond(httpRequest -> response().withStatusCode(OK.getStatusCode())
+ .withHeaders(
+ new Header(HttpHeaders.CONTENT_TYPE, "image/png"))
+ .withBody(bytesRes));
+
+ var output = given()
+ .when()
+ .auth().oauth2(keycloakClient.getAccessToken(ADMIN))
+ .header(APM_HEADER_PARAM, ADMIN)
+ .contentType(APPLICATION_JSON)
+ .pathParam("name", "theme1")
+ .get("/themes/{name}/favicon")
+ .then()
+ .statusCode(OK.getStatusCode())
+ .header(HttpHeaders.CONTENT_TYPE, "image/png")
+ .extract().body().asByteArray();
+
+ Assertions.assertNotNull(output);
+ mockServerClient.clear("mockFavicon");
+
+ }
+
+ @Test
+ void getThemeFavicon_shouldReturnBadRequest_whenBodyEmpty() {
+
+ byte[] bytesRes = null;
+
+ mockServerClient.when(request().withPath("/v1/themes/theme1/favicon").withMethod(HttpMethod.GET))
+ .withId("mockFavicon")
+ .withPriority(100)
+ .respond(httpRequest -> response().withStatusCode(OK.getStatusCode())
+ .withHeaders(
+ new Header(HttpHeaders.CONTENT_TYPE, "image/png"))
+ .withBody(bytesRes));
+
+ given()
+ .when()
+ .auth().oauth2(keycloakClient.getAccessToken(ADMIN))
+ .header(APM_HEADER_PARAM, ADMIN)
+ .contentType(APPLICATION_JSON)
+ .pathParam("name", "theme1")
+ .get("/themes/{name}/favicon")
+ .then()
+ .statusCode(BAD_REQUEST.getStatusCode());
+ mockServerClient.clear("mockFavicon");
+
+ }
+
+ @Test
+ void getThemeFavicon_shouldReturnBadRequest_whenContentTypeEmpty() {
+
+ byte[] bytesRes = new byte[] { (byte) 0xe0, 0x4f, (byte) 0xd0,
+ 0x20, (byte) 0xea, 0x3a, 0x69, 0x10, (byte) 0xa2, (byte) 0xd8, 0x08, 0x00, 0x2b,
+ 0x30, 0x30, (byte) 0x9d };
+
+ mockServerClient.when(request().withPath("/v1/themes/theme1/favicon").withMethod(HttpMethod.GET))
+ .withId("mockFavicon")
+ .withPriority(100)
+ .respond(httpRequest -> response().withStatusCode(OK.getStatusCode())
+ .withBody(bytesRes));
+
+ given()
+ .when()
+ .auth().oauth2(keycloakClient.getAccessToken(ADMIN))
+ .header(APM_HEADER_PARAM, ADMIN)
+ .contentType(APPLICATION_JSON)
+ .pathParam("name", "theme1")
+ .get("/themes/{name}/favicon")
+ .then()
+ .statusCode(BAD_REQUEST.getStatusCode());
+
+ mockServerClient.clear("mockFavicon");
+
+ }
+
+ @Test
+ void getImage_shouldReturnBadRequest_whenAllEmpty() {
+
+ mockServerClient.when(request().withPath("/v1/themes/theme1/favicon").withMethod(HttpMethod.GET))
+ .withId("mockFavicon")
+ .withPriority(100)
+ .respond(httpRequest -> response().withStatusCode(OK.getStatusCode()));
+
+ given()
+ .when()
+ .auth().oauth2(keycloakClient.getAccessToken(ADMIN))
+ .header(APM_HEADER_PARAM, ADMIN)
+ .contentType(APPLICATION_JSON)
+ .pathParam("name", "theme1")
+ .get("/themes/{name}/favicon")
+ .then()
+ .statusCode(BAD_REQUEST.getStatusCode());
+ mockServerClient.clear("mockFavicon");
+
+ }
+}
diff --git a/src/test/resources/mockserver/permissions.json b/src/test/resources/mockserver/permissions.json
index 7918f68..9159c29 100644
--- a/src/test/resources/mockserver/permissions.json
+++ b/src/test/resources/mockserver/permissions.json
@@ -13,7 +13,9 @@
"json": {
"appId": "onecx-shell-bff",
"permissions": {
- "example": ["read", "write", "delete"],
+ "workspaceConfig": ["read", "write", "delete"],
+ "userProfile": ["read", "write", "delete"],
+ "permission": ["read"],
"permissions": ["admin-write","admin-read"]
}
},
@@ -35,7 +37,6 @@
"json": {
"appId": "onecx-shell-bff",
"permissions": {
- "example": ["read"],
"permissions": ["admin-write","admin-read"]
}
},