From 7ec4646a70ee7ae23441783d451c9f18470e1cee Mon Sep 17 00:00:00 2001 From: Andrej Petras Date: Thu, 25 Apr 2024 08:46:49 +0200 Subject: [PATCH] feat: add slots (#16) * feat: update maven dependencies * feat: remove old code * feat: add slots and refactoring methods * feat: switch to new workspace api * feat: fix sonar issues --- .../pages/onecx-shell-bff-extensions.adoc | 14 +- pom.xml | 4 +- .../WorkspaceConfigRestController.java | 40 ++- .../bff/rs/mappers/WorkspaceConfigMapper.java | 141 +++++++- src/main/openapi/openapi-bff.yaml | 70 ++++ .../rs/WorkspaceConfigRestControllerTest.java | 303 +++++++++++++++++- 6 files changed, 541 insertions(+), 31 deletions(-) diff --git a/docs/modules/onecx-shell-bff/pages/onecx-shell-bff-extensions.adoc b/docs/modules/onecx-shell-bff/pages/onecx-shell-bff-extensions.adoc index a089a55..4e0abc9 100644 --- a/docs/modules/onecx-shell-bff/pages/onecx-shell-bff-extensions.adoc +++ b/docs/modules/onecx-shell-bff/pages/onecx-shell-bff-extensions.adoc @@ -38,7 +38,7 @@ h| Version | https://github.com/quarkiverse/quarkus-openapi-generator/blob/2.4.1/docs/modules/ROOT/pages/includes/quarkus-openapi-generator.adoc[Link] | 2.4.1 -| quarkus-rest-client-reactive-jackson +| quarkus-rest-client-jackson | https://quarkus.io/guides/rest-client[Link] | @@ -104,12 +104,6 @@ h| Version | https://github.com/quarkusio/quarkusio.github.io/blob/develop/_generated-doc/latest/config/quarkus-oidc.adoc[Link] | 3.9.3 -| quarkus-oidc-client-reactive-filter - -| https://quarkus.io/guides/security-openid-connect-client-reference[Link] -| https://github.com/quarkusio/quarkusio.github.io/blob/develop/_generated-doc/latest/config/quarkus-oidc-client-reactive-filter.adoc[Link] -| 3.9.3 - | onecx-core | https://onecx.github.io/docs/onecx-quarkus/current/onecx-quarkus/onecx-core.html[Link] @@ -129,5 +123,11 @@ h| Version | 3.9.3 +| quarkus-rest-client-oidc-filter + +| +| +| 3.9.3 + |=== \ No newline at end of file diff --git a/pom.xml b/pom.xml index 5a00099..b18c11e 100644 --- a/pom.xml +++ b/pom.xml @@ -35,7 +35,7 @@ io.quarkus - quarkus-rest-client-reactive-jackson + quarkus-rest-client-jackson @@ -84,7 +84,7 @@ io.quarkus - quarkus-oidc-client-reactive-filter + quarkus-rest-client-oidc-filter 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 index 724d3c7..dbdf5d4 100644 --- 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 @@ -23,15 +23,13 @@ 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.MicrofrontendTypePSV1; -import gen.org.tkit.onecx.product.store.client.model.ProductPSV1; +import gen.org.tkit.onecx.product.store.client.model.*; 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.Workspace; -import gen.org.tkit.onecx.workspace.client.model.WorkspaceLoad; +import gen.org.tkit.onecx.workspace.client.model.*; @ApplicationScoped @Transactional(value = Transactional.TxType.NOT_SUPPORTED) @@ -123,6 +121,40 @@ public Response getWorkspaceConfig(GetWorkspaceConfigRequestDTO getWorkspaceConf } } + @Override + public Response loadWorkspaceConfig(LoadWorkspaceConfigRequestDTO loadWorkspaceConfigRequestDTO) { + + WorkspaceWrapper wrapper; + try (Response response = workspaceClient.loadWorkspaceByRequest(mapper.createRequest(loadWorkspaceConfigRequestDTO))) { + wrapper = response.readEntity(WorkspaceWrapper.class); + } + + if (wrapper == null) { + return Response.status(Response.Status.NOT_FOUND).build(); + } + + var result = mapper.createResponse(wrapper); + + // load products and create corresponding module and components + if (wrapper.getProducts() != null) { + try (Response psResponse = productStoreClient.loadProductsByNames(mapper.create(wrapper))) { + var productResponse = psResponse.readEntity(LoadProductResponsePSV1.class); + mapper.createMfeAndComponents(result, wrapper, productResponse); + } + } + + // create slots + result.setSlots(mapper.createSlots(wrapper.getSlots())); + + //get theme info + try (Response themeResponse = themeClient.getThemeByName(wrapper.getTheme())) { + var theme = themeResponse.readEntity(Theme.class); + result.setTheme(mapper.createTheme(theme, uriInfo.getPath())); + } + + return Response.ok(result).build(); + } + @Override public Response getThemeFaviconByName(String name) { Response.ResponseBuilder responseBuilder; 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 index c114eba..0d2e3f7 100644 --- 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 @@ -1,19 +1,20 @@ package org.tkit.onecx.shell.bff.rs.mappers; +import static gen.org.tkit.onecx.product.store.client.model.MicrofrontendTypePSV1.MODULE; + import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import org.mapstruct.AfterMapping; import org.mapstruct.Mapper; import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; -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.product.store.client.model.*; 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.GetWorkspaceByUrlRequest; -import gen.org.tkit.onecx.workspace.client.model.Microfrontend; -import gen.org.tkit.onecx.workspace.client.model.Workspace; -import gen.org.tkit.onecx.workspace.client.model.WorkspaceAbstract; +import gen.org.tkit.onecx.workspace.client.model.*; @Mapper public interface WorkspaceConfigMapper { @@ -25,11 +26,6 @@ public interface WorkspaceConfigMapper { @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, String workspaceUrl) { RouteDTO route = new RouteDTO(); @@ -48,4 +44,125 @@ default RouteDTO mapRoute(MicrofrontendPSV1 mfe, ProductPSV1 product, } return route; } + + WorkspaceLoadRequest createRequest(LoadWorkspaceConfigRequestDTO dto); + + default LoadWorkspaceConfigResponseDTO createResponse(WorkspaceWrapper workspaceWrapper) { + return new LoadWorkspaceConfigResponseDTO().workspace(createWorkspace(workspaceWrapper)); + } + + WorkspaceDTO createWorkspace(WorkspaceWrapper workspaceWrapper); + + @Mapping(target = "name", ignore = true) + @Mapping(target = "baseUrl", source = "mfe.remoteBaseUrl") + @Mapping(target = "remoteEntryUrl", source = "mfe.remoteEntry") + @Mapping(target = "productName", source = "product.name") + RemoteComponentDTO createComponent(LoadProductItemPSV1 product, LoadProductMicrofrontendPSV1 mfe); + + @AfterMapping + default void componentName(@MappingTarget RemoteComponentDTO target, LoadProductItemPSV1 product, + LoadProductMicrofrontendPSV1 mfe) { + target.setName(componentName(product, mfe)); + } + + default String componentName(LoadProductItemPSV1 product, LoadProductMicrofrontendPSV1 mfe) { + String name = ""; + if (product != null) { + name = product.getName(); + } + if (mfe == null) { + return name; + } + return componentName(name, mfe.getAppId(), mfe.getExposedModule()); + } + + default String componentName(String productName, String appId, String exposedModule) { + return productName + "#" + appId + "#" + exposedModule; + } + + @Mapping(target = "technology", constant = "ANGULAR") + @Mapping(target = "url", source = "mfe.remoteBaseUrl") + @Mapping(target = "pathMatch", constant = "PREFIX") + @Mapping(target = "baseUrl", ignore = true) + @Mapping(target = "remoteEntryUrl", source = "mfe.remoteEntry") + @Mapping(target = "displayName", source = "product.displayName") + @Mapping(target = "productName", source = "product.name") + @Mapping(target = "appId", source = "mfe.appId") + @Mapping(target = "exposedModule", source = "mfe.exposedModule") + @Mapping(target = "remoteName", source = "mfe.remoteName") + RouteDTO createRoute(LoadProductItemPSV1 product, LoadProductMicrofrontendPSV1 mfe, Map pathMapping, + WorkspaceWrapper workspace); + + @AfterMapping + default void createRouteAfter(@MappingTarget RouteDTO target, Map pathMapping, WorkspaceWrapper workspace) { + var modulePath = pathMapping.get(target.getAppId()); + if (modulePath != null) { + target.setBaseUrl(workspace.getBaseUrl() + modulePath); + } + } + + List createSlots(List slots); + + default SlotDTO createSlot(WorkspaceWrapperSlot slot) { + if (slot == null) { + return null; + } + + SlotDTO result = new SlotDTO().name(slot.getName()); + if (slot.getComponents() != null) { + slot.getComponents() + .forEach(c -> result.addComponentsItem(componentName(c.getProductName(), c.getAppId(), c.getName()))); + } + return result; + } + + @Mapping(target = "properties", ignore = true) + ThemeDTO createTheme(Theme themeInfo, String path); + + @AfterMapping + default void createThemeAfter(@MappingTarget ThemeDTO target, Theme themeInfo, String path) { + if (themeInfo != null) { + target.setProperties(String.valueOf(themeInfo.getProperties())); + } + if (target.getFaviconUrl() == null) { + target.setFaviconUrl(path + "/themes/" + target.getName() + "/favicon"); + } + if (target.getLogoUrl() == null) { + target.setLogoUrl(path + "/themes/" + target.getName() + "/logo"); + } + } + + default LoadProductRequestPSV1 create(WorkspaceWrapper wrapper) { + return new LoadProductRequestPSV1().productNames(wrapper.getProducts().stream().map(Product::getProductName).toList()); + } + + default void createMfeAndComponents(LoadWorkspaceConfigResponseDTO result, WorkspaceWrapper wrapper, + LoadProductResponsePSV1 loadProducts) { + if (loadProducts == null || loadProducts.getProducts() == null) { + return; + } + + var workspaceProducts = wrapper.getProducts().stream().collect(Collectors.toMap(Product::getProductName, p -> p)); + + loadProducts.getProducts().forEach(product -> { + + var workspaceProduct = workspaceProducts.get(product.getName()); + + // create mapping APP_ID -> PATH + var pathMapping = workspaceProduct.getMicrofrontends().stream() + .collect(Collectors.toMap(Microfrontend::getMfeId, Microfrontend::getBasePath)); + + if (product.getMicrofrontends() != null) { + product.getMicrofrontends().forEach(mfe -> { + if (mfe.getType() == MODULE) { + result.addRoutesItem(createRoute(product, mfe, pathMapping, wrapper)); + } else if (mfe.getType() == MicrofrontendTypePSV1.COMPONENT) { + result.addComponentsItem(createComponent(product, mfe)); + } + }); + } + + }); + } + } diff --git a/src/main/openapi/openapi-bff.yaml b/src/main/openapi/openapi-bff.yaml index e6ffc06..989a612 100644 --- a/src/main/openapi/openapi-bff.yaml +++ b/src/main/openapi/openapi-bff.yaml @@ -8,6 +8,32 @@ servers: - url: http://onecx-shell-bff:8080/ paths: + /workspaceConfig/load: + post: + tags: + - "WorkspaceConfig" + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/LoadWorkspaceConfigRequest' + operationId: loadWorkspaceConfig + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/LoadWorkspaceConfigResponse' + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetailResponse' + '404': + description: 'Not Found' /workspaceConfig: post: tags: @@ -135,6 +161,50 @@ paths: components: schemas: + LoadWorkspaceConfigRequest: + type: object + required: + - path + properties: + path: + type: string + LoadWorkspaceConfigResponse: + type: object + required: + - 'routes' + - 'theme' + - 'workspace' + - 'components' + - 'slots' + properties: + routes: + type: array + items: + $ref: '#/components/schemas/Route' + theme: + $ref: '#/components/schemas/Theme' + workspace: + $ref: '#/components/schemas/Workspace' + components: + type: array + items: + $ref: '#/components/schemas/RemoteComponent' + slots: + type: array + items: + $ref: '#/components/schemas/Slot' + Slot: + type: object + required: + - 'name' + - 'components' + properties: + name: + type: string + components: + type: array + items: + type: string GetWorkspaceConfigResponse: type: object required: 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 index 05f7ad4..5c7cb9a 100644 --- a/src/test/java/org/tkit/onecx/shell/bff/rs/WorkspaceConfigRestControllerTest.java +++ b/src/test/java/org/tkit/onecx/shell/bff/rs/WorkspaceConfigRestControllerTest.java @@ -20,12 +20,8 @@ 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.MicrofrontendTypePSV1; -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.product.store.client.model.*; +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.*; import io.quarkiverse.mockserver.test.InjectMockServerClient; @@ -618,4 +614,299 @@ void getImage_shouldReturnBadRequest_whenAllEmpty() { mockServerClient.clear("mockFavicon"); mockServerClient.clear("mockLogo"); } + + @Test + void loadWorkspaceConfigByBaseUrlTest() { + + var workspace = new WorkspaceWrapper(); + workspace.name("w1").theme("theme1") + .addProductsItem( + new Product().productName("product1").baseUrl("/product1") + .addMicrofrontendsItem(new Microfrontend().basePath("/app1").mfeId("app1"))) + .addSlotsItem( + new WorkspaceWrapperSlot().name("slot1") + .addComponentsItem( + new WorkspaceWrapperComponent().productName("product1").appId("app1") + .name("App1Component")) + .addComponentsItem( + new WorkspaceWrapperComponent().productName("product1").appId("app1") + .name("App2Component")) + .addComponentsItem( + new WorkspaceWrapperComponent().productName("product1").appId("app1") + .name("App3Component"))); + + // create mock rest endpoint for workspace search + mockServerClient.when(request().withPath("/v1/workspaces/load").withMethod(HttpMethod.POST) + .withContentType(MediaType.APPLICATION_JSON) + .withBody(JsonBody.json(new WorkspaceLoadRequest().path("/w1Url")))) + .withId("mockWS") + .respond(httpRequest -> response().withStatusCode(Response.Status.OK.getStatusCode()) + .withContentType(MediaType.APPLICATION_JSON) + .withBody(JsonBody.json(workspace))); + + var productResponse = new LoadProductResponsePSV1(); + productResponse.addProductsItem(new LoadProductItemPSV1().name("product1") + .addMicrofrontendsItem(new LoadProductMicrofrontendPSV1() + .exposedModule("App1Module") + .appId("app1") + .remoteBaseUrl("/remoteBaseUrl") + .remoteEntry("/remoteEntry.js") + .technology("ANGULAR") + .type(MicrofrontendTypePSV1.MODULE)) + .addMicrofrontendsItem(new LoadProductMicrofrontendPSV1() + .exposedModule("App2Component") + .appId("app1") + .remoteBaseUrl("/remoteBaseUrl") + .remoteEntry("/remoteEntry.js") + .technology("ANGULAR") + .type(MicrofrontendTypePSV1.COMPONENT))); + + // create mock rest endpoint for get product by name from product-store + mockServerClient.when(request().withPath("/v1/products/load/shell").withMethod(HttpMethod.POST)) + .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()).logoUrl("someLogoUrl") + .faviconUrl("someFavIconUrl"); + // 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 output = given() + .when() + .auth().oauth2(keycloakClient.getAccessToken(ADMIN)) + .header(APM_HEADER_PARAM, ADMIN) + .contentType(APPLICATION_JSON) + .body(new LoadWorkspaceConfigRequestDTO().path("/w1Url")) + .post("load") + .then() + .statusCode(Response.Status.OK.getStatusCode()) + .contentType(APPLICATION_JSON) + .extract().as(LoadWorkspaceConfigResponseDTO.class); + + Assertions.assertNotNull(output); + Assertions.assertEquals("w1", output.getWorkspace().getName()); + Assertions.assertEquals("theme1", output.getTheme().getName()); + Assertions.assertEquals(1, output.getRoutes().size()); + + Assertions.assertEquals("product1#app1#App2Component", output.getComponents().get(0).getName()); + Assertions.assertEquals("app1", output.getComponents().get(0).getAppId()); + Assertions.assertEquals("slot1", output.getSlots().get(0).getName()); + + mockServerClient.clear("mockWS"); + mockServerClient.clear("mockPS"); + mockServerClient.clear("mockTheme"); + mockServerClient.clear("mockWSLoad"); + } + + @Test + void loadWorkspaceConfig_MissingBaseUrlTest() { + var output = given() + .when() + .auth().oauth2(keycloakClient.getAccessToken(ADMIN)) + .header(APM_HEADER_PARAM, ADMIN) + .contentType(APPLICATION_JSON) + .body(new LoadWorkspaceConfigRequestDTO()) + .post("load") + .then() + .statusCode(BAD_REQUEST.getStatusCode()) + .contentType(APPLICATION_JSON) + .extract().as(ProblemDetailResponseDTO.class); + + Assertions.assertNotNull(output); + } + + @Test + void loadWorkspaceConfig_WorkspaceNotFoundTest() { + + // create mock rest endpoint for workspace search + mockServerClient.when(request().withPath("/v1/workspaces/load").withMethod(HttpMethod.POST) + .withContentType(MediaType.APPLICATION_JSON) + .withBody(JsonBody.json(new WorkspaceLoadRequest().path("/w1Url")))) + .withId("mockWS") + .respond(httpRequest -> response().withStatusCode(Response.Status.OK.getStatusCode()) + .withContentType(MediaType.APPLICATION_JSON)); + + var output = given() + .when() + .auth().oauth2(keycloakClient.getAccessToken(ADMIN)) + .header(APM_HEADER_PARAM, ADMIN) + .contentType(APPLICATION_JSON) + .body(new LoadWorkspaceConfigRequestDTO().path("/w1Url")) + .post("load") + .then() + .statusCode(NOT_FOUND.getStatusCode()); + Assertions.assertNotNull(output); + mockServerClient.clear("mockWS"); + } + + @Test + void loadWorkspaceConfigByEmptyProductsTest() { + + var workspace = new WorkspaceWrapper(); + workspace.name("w1").theme("theme1") + .addSlotsItem( + new WorkspaceWrapperSlot().name("slot1") + .addComponentsItem( + new WorkspaceWrapperComponent().productName("product1").appId("app1") + .name("App1Component"))); + + // create mock rest endpoint for workspace search + mockServerClient.when(request().withPath("/v1/workspaces/load").withMethod(HttpMethod.POST) + .withContentType(MediaType.APPLICATION_JSON) + .withBody(JsonBody.json(new WorkspaceLoadRequest().path("/w1Url")))) + .withId("mockWS") + .respond(httpRequest -> response().withStatusCode(Response.Status.OK.getStatusCode()) + .withContentType(MediaType.APPLICATION_JSON) + .withBody(JsonBody.json(workspace))); + + Theme themeResponse = new Theme(); + themeResponse.name("theme1").cssFile("cssfile").properties(new Object()).logoUrl("someLogoUrl") + .faviconUrl("someFavIconUrl"); + // 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 output = given() + .when() + .auth().oauth2(keycloakClient.getAccessToken(ADMIN)) + .header(APM_HEADER_PARAM, ADMIN) + .contentType(APPLICATION_JSON) + .body(new LoadWorkspaceConfigRequestDTO().path("/w1Url")) + .post("load") + .then() + .statusCode(Response.Status.OK.getStatusCode()) + .contentType(APPLICATION_JSON) + .extract().as(LoadWorkspaceConfigResponseDTO.class); + + Assertions.assertNotNull(output); + Assertions.assertEquals("w1", output.getWorkspace().getName()); + Assertions.assertEquals("theme1", output.getTheme().getName()); + Assertions.assertEquals(0, output.getRoutes().size()); + + mockServerClient.clear("mockWS"); + mockServerClient.clear("mockTheme"); + } + + @Test + void loadWorkspaceConfig_ErrorTest() { + + // create mock rest endpoint for workspace search + mockServerClient.when(request().withPath("/v1/workspaces/load").withMethod(HttpMethod.POST) + .withContentType(MediaType.APPLICATION_JSON) + .withBody(JsonBody.json(new WorkspaceLoadRequest().path("/w1Url")))) + .withId("mockWS") + .respond(httpRequest -> response().withStatusCode(INTERNAL_SERVER_ERROR.getStatusCode()) + .withContentType(MediaType.APPLICATION_JSON)); + + var output = given() + .when() + .auth().oauth2(keycloakClient.getAccessToken(ADMIN)) + .header(APM_HEADER_PARAM, ADMIN) + .contentType(APPLICATION_JSON) + .body(new LoadWorkspaceConfigRequestDTO().path("/w1Url")) + .post("load") + .then() + .statusCode(BAD_REQUEST.getStatusCode()); + Assertions.assertNotNull(output); + mockServerClient.clear("mockWS"); + } + + @Test + void loadWorkspaceConfig_errorThemeTest() { + + var workspace = new WorkspaceWrapper(); + workspace.name("w1").theme("theme1") + .addSlotsItem( + new WorkspaceWrapperSlot().name("slot1") + .addComponentsItem( + new WorkspaceWrapperComponent().productName("product1").appId("app1") + .name("App1Component"))); + + // create mock rest endpoint for workspace search + mockServerClient.when(request().withPath("/v1/workspaces/load").withMethod(HttpMethod.POST) + .withContentType(MediaType.APPLICATION_JSON) + .withBody(JsonBody.json(new WorkspaceLoadRequest().path("/w1Url")))) + .withId("mockWS") + .respond(httpRequest -> response().withStatusCode(Response.Status.OK.getStatusCode()) + .withContentType(MediaType.APPLICATION_JSON) + .withBody(JsonBody.json(workspace))); + + // 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(INTERNAL_SERVER_ERROR.getStatusCode()) + .withContentType(MediaType.APPLICATION_JSON)); + + given() + .when() + .auth().oauth2(keycloakClient.getAccessToken(ADMIN)) + .header(APM_HEADER_PARAM, ADMIN) + .contentType(APPLICATION_JSON) + .body(new LoadWorkspaceConfigRequestDTO().path("/w1Url")) + .post("load") + .then() + .statusCode(BAD_REQUEST.getStatusCode()); + + mockServerClient.clear("mockWS"); + mockServerClient.clear("mockTheme"); + } + + @Test + void loadWorkspace_errorProductTest() { + + var workspace = new WorkspaceWrapper(); + workspace.name("w1").theme("theme1") + .addProductsItem( + new Product().productName("product1").baseUrl("/product1") + .addMicrofrontendsItem(new Microfrontend().basePath("/app1").mfeId("app1"))) + .addSlotsItem( + new WorkspaceWrapperSlot().name("slot1") + .addComponentsItem( + new WorkspaceWrapperComponent().productName("product1").appId("app1") + .name("App1Component")) + .addComponentsItem( + new WorkspaceWrapperComponent().productName("product1").appId("app1") + .name("App2Component")) + .addComponentsItem( + new WorkspaceWrapperComponent().productName("product1").appId("app1") + .name("App3Component"))); + + // create mock rest endpoint for workspace search + mockServerClient.when(request().withPath("/v1/workspaces/load").withMethod(HttpMethod.POST) + .withContentType(MediaType.APPLICATION_JSON) + .withBody(JsonBody.json(new WorkspaceLoadRequest().path("/w1Url")))) + .withId("mockWS") + .respond(httpRequest -> response().withStatusCode(Response.Status.OK.getStatusCode()) + .withContentType(MediaType.APPLICATION_JSON) + .withBody(JsonBody.json(workspace))); + + // create mock rest endpoint for get product by name from product-store + mockServerClient.when(request().withPath("/v1/products/load/shell").withMethod(HttpMethod.POST)) + .withId("mockPS") + .respond(httpRequest -> response().withStatusCode(INTERNAL_SERVER_ERROR.getStatusCode()) + .withContentType(MediaType.APPLICATION_JSON)); + + given() + .when() + .auth().oauth2(keycloakClient.getAccessToken(ADMIN)) + .header(APM_HEADER_PARAM, ADMIN) + .contentType(APPLICATION_JSON) + .body(new LoadWorkspaceConfigRequestDTO().path("/w1Url")) + .post("load") + .then() + .statusCode(BAD_REQUEST.getStatusCode()); + + mockServerClient.clear("mockWS"); + mockServerClient.clear("mockPS"); + } }