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"] } },