From cdc670c5a1c25b0b0ab460b1711a0a42f270b1f3 Mon Sep 17 00:00:00 2001 From: Fiete Ostkamp Date: Fri, 14 Apr 2023 11:44:19 +0000 Subject: Upload bff Issue-ID: PORTAL-1083 Signed-off-by: Fiete Ostkamp Change-Id: I50f0a2db2dab28354c32c1ebf5a5e22afb0faade --- .../main/java/org/onap/portal/bff/Application.java | 36 ++ .../main/resources/application-access-control.yml | 23 ++ app/src/main/resources/application-development.yml | 30 ++ app/src/main/resources/application-local.yml | 34 ++ app/src/main/resources/application.yml | 49 +++ app/src/main/resources/logback-spring.xml | 13 + .../onap/portal/bff/ApiDocsIntegrationTest.java | 40 ++ .../org/onap/portal/bff/BaseIntegrationTest.java | 228 +++++++++++ .../portal/bff/HealthCheckIntegrationTest.java | 48 +++ .../java/org/onap/portal/bff/TokenGenerator.java | 123 ++++++ .../org/onap/portal/bff/actions/ActionDto.java | 39 ++ .../onap/portal/bff/actions/ActionFixtures.java | 106 +++++ .../org/onap/portal/bff/actions/ActionsMocks.java | 228 +++++++++++ .../bff/actions/CreateActionsIntegrationTest.java | 85 ++++ .../bff/actions/GetActionsIntegrationTest.java | 79 ++++ .../bff/actions/ListActionsIntegrationTest.java | 78 ++++ .../portal/bff/headers/XRequestIdHeaderTest.java | 76 ++++ .../idtoken/IdTokenExchangeFilterFunctionTest.java | 89 ++++ .../CreatePreferencesIntegrationTest.java | 115 ++++++ .../preferences/GetPreferencesIntegrationTest.java | 77 ++++ .../portal/bff/preferences/PreferencesMocks.java | 182 +++++++++ .../UpdatePreferencesIntegrationTest.java | 114 ++++++ .../bff/roles/ListRealmRolesIntegrationTest.java | 88 ++++ .../java/org/onap/portal/bff/roles/RolesMocks.java | 57 +++ .../bff/users/CreateUserIntegrationTest.java | 308 ++++++++++++++ .../bff/users/DeleteUserIntegrationTest.java | 83 ++++ .../bff/users/GetUserDetailIntegrationTest.java | 126 ++++++ .../users/ListAssignedRolesIntegrationTest.java | 111 +++++ .../users/ListAvailableRolesIntegrationTest.java | 113 ++++++ .../portal/bff/users/ListUsersIntegrationTest.java | 238 +++++++++++ .../users/UpdateAssignedRolesIntegrationTest.java | 452 +++++++++++++++++++++ .../bff/users/UpdateUserIntegrationTest.java | 134 ++++++ .../users/UpdateUserPasswordIntegrationTest.java | 107 +++++ .../portal/bff/utils/SortingChainResolverTest.java | 141 +++++++ .../onap/portal/bff/utils/SortingParserTest.java | 50 +++ .../portal/bff/utils/VersionComparatorTest.java | 41 ++ .../test/resources/application-access-control.yml | 22 + app/src/test/resources/application-development.yml | 33 ++ app/src/test/resources/application.yml | 34 ++ app/src/test/resources/logback-spring.xml | 18 + .../preferences/preferencesProperties.json | 80 ++++ 41 files changed, 4128 insertions(+) create mode 100644 app/src/main/java/org/onap/portal/bff/Application.java create mode 100644 app/src/main/resources/application-access-control.yml create mode 100644 app/src/main/resources/application-development.yml create mode 100644 app/src/main/resources/application-local.yml create mode 100644 app/src/main/resources/application.yml create mode 100644 app/src/main/resources/logback-spring.xml create mode 100644 app/src/test/java/org/onap/portal/bff/ApiDocsIntegrationTest.java create mode 100644 app/src/test/java/org/onap/portal/bff/BaseIntegrationTest.java create mode 100644 app/src/test/java/org/onap/portal/bff/HealthCheckIntegrationTest.java create mode 100644 app/src/test/java/org/onap/portal/bff/TokenGenerator.java create mode 100644 app/src/test/java/org/onap/portal/bff/actions/ActionDto.java create mode 100644 app/src/test/java/org/onap/portal/bff/actions/ActionFixtures.java create mode 100644 app/src/test/java/org/onap/portal/bff/actions/ActionsMocks.java create mode 100644 app/src/test/java/org/onap/portal/bff/actions/CreateActionsIntegrationTest.java create mode 100644 app/src/test/java/org/onap/portal/bff/actions/GetActionsIntegrationTest.java create mode 100644 app/src/test/java/org/onap/portal/bff/actions/ListActionsIntegrationTest.java create mode 100644 app/src/test/java/org/onap/portal/bff/headers/XRequestIdHeaderTest.java create mode 100644 app/src/test/java/org/onap/portal/bff/idtoken/IdTokenExchangeFilterFunctionTest.java create mode 100644 app/src/test/java/org/onap/portal/bff/preferences/CreatePreferencesIntegrationTest.java create mode 100644 app/src/test/java/org/onap/portal/bff/preferences/GetPreferencesIntegrationTest.java create mode 100644 app/src/test/java/org/onap/portal/bff/preferences/PreferencesMocks.java create mode 100644 app/src/test/java/org/onap/portal/bff/preferences/UpdatePreferencesIntegrationTest.java create mode 100644 app/src/test/java/org/onap/portal/bff/roles/ListRealmRolesIntegrationTest.java create mode 100644 app/src/test/java/org/onap/portal/bff/roles/RolesMocks.java create mode 100644 app/src/test/java/org/onap/portal/bff/users/CreateUserIntegrationTest.java create mode 100644 app/src/test/java/org/onap/portal/bff/users/DeleteUserIntegrationTest.java create mode 100644 app/src/test/java/org/onap/portal/bff/users/GetUserDetailIntegrationTest.java create mode 100644 app/src/test/java/org/onap/portal/bff/users/ListAssignedRolesIntegrationTest.java create mode 100644 app/src/test/java/org/onap/portal/bff/users/ListAvailableRolesIntegrationTest.java create mode 100644 app/src/test/java/org/onap/portal/bff/users/ListUsersIntegrationTest.java create mode 100644 app/src/test/java/org/onap/portal/bff/users/UpdateAssignedRolesIntegrationTest.java create mode 100644 app/src/test/java/org/onap/portal/bff/users/UpdateUserIntegrationTest.java create mode 100644 app/src/test/java/org/onap/portal/bff/users/UpdateUserPasswordIntegrationTest.java create mode 100644 app/src/test/java/org/onap/portal/bff/utils/SortingChainResolverTest.java create mode 100644 app/src/test/java/org/onap/portal/bff/utils/SortingParserTest.java create mode 100644 app/src/test/java/org/onap/portal/bff/utils/VersionComparatorTest.java create mode 100644 app/src/test/resources/application-access-control.yml create mode 100644 app/src/test/resources/application-development.yml create mode 100644 app/src/test/resources/application.yml create mode 100644 app/src/test/resources/logback-spring.xml create mode 100644 app/src/test/resources/preferences/preferencesProperties.json (limited to 'app/src') diff --git a/app/src/main/java/org/onap/portal/bff/Application.java b/app/src/main/java/org/onap/portal/bff/Application.java new file mode 100644 index 0000000..32a07e8 --- /dev/null +++ b/app/src/main/java/org/onap/portal/bff/Application.java @@ -0,0 +1,36 @@ +/* + * + * Copyright (c) 2022. Deutsche Telekom AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * + * + */ + +package org.onap.portal.bff; + +import org.onap.portal.bff.config.PortalBffConfig; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.EnableConfigurationProperties; + +@EnableConfigurationProperties(PortalBffConfig.class) +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } +} diff --git a/app/src/main/resources/application-access-control.yml b/app/src/main/resources/application-access-control.yml new file mode 100644 index 0000000..d967c53 --- /dev/null +++ b/app/src/main/resources/application-access-control.yml @@ -0,0 +1,23 @@ +portal-bff.access-control: + ACTIONS_CREATE: [ onap_admin, onap_designer, onap_operator ] + ACTIONS_GET: [ onap_admin, onap_designer, onap_operator ] + ACTIONS_LIST: [ onap_admin, onap_designer, onap_operator ] + ACTIVE_ALARM_LIST: [onap_admin, onap_designer, onap_operator] + KEY_ENCRYPT_BY_USER: [onap_admin, onap_designer, onap_operator] + KEY_ENCRYPT_BY_VALUE: [onap_admin, onap_designer, onap_operator] + PREFERENCES_CREATE: [onap_admin, onap_designer, onap_operator] + PREFERENCES_GET: [onap_admin, onap_designer, onap_operator] + PREFERENCES_UPDATE: [onap_admin, onap_designer, onap_operator] + ROLE_LIST: ["*"] + TILE_GET: [onap_admin, onap_designer, onap_operator] + TILE_LIST: [onap_admin, onap_designer, onap_operator] + USER_CREATE: [onap_admin, onap_designer, onap_operator] + USER_DELETE: [onap_admin, onap_designer, onap_operator] + USER_GET: [onap_admin, onap_designer, onap_operator] + USER_LIST_AVAILABLE_ROLES: [onap_admin, onap_designer, onap_operator] + USER_LIST_ROLES: [onap_admin, onap_designer, onap_operator] + USER_LIST: [onap_admin, onap_designer, onap_operator] + USER_UPDATE_PASSWORD: [onap_admin, onap_designer, onap_operator] + USER_UPDATE_ROLES: [onap_admin, onap_designer, onap_operator] + USER_UPDATE: [onap_admin, onap_designer, onap_operator] + diff --git a/app/src/main/resources/application-development.yml b/app/src/main/resources/application-development.yml new file mode 100644 index 0000000..50dfb51 --- /dev/null +++ b/app/src/main/resources/application-development.yml @@ -0,0 +1,30 @@ +spring: + security: + oauth2: + client: + provider: + keycloak: + token-uri: http://localhost:8080/auth/realms/ONAP/protocol/openid-connect/token + jwk-set-uri: http://localhost:8080/auth/realms/ONAP/protocol/openid-connect/certs + registration: + keycloak: + provider: keycloak + client-id: portal-bff + client-secret: 5933482a-9f4c-44e0-9814-dca17e0a9137 + authorization-grant-type: client_credentials + resourceserver: + jwt: + jwk-set-uri: http://localhost:8080/auth/realms/ONAP/protocol/openid-connect/certs + +management: + endpoints: + web: + exposure: + include: "*" + +portal-bff: + realm: ONAP + portal-prefs-url: ${PORTAL_PREFS_URL} + portal-history-url: ${PORTAL_HISTORY_URL} + keycloak-url: ${KEYCLOAK_URL} + instance-id: PORTAL diff --git a/app/src/main/resources/application-local.yml b/app/src/main/resources/application-local.yml new file mode 100644 index 0000000..e90a13b --- /dev/null +++ b/app/src/main/resources/application-local.yml @@ -0,0 +1,34 @@ +spring: + security: + oauth2: + client: + provider: + keycloak: + token-uri: http://localhost:8080/auth/realms/ONAP/protocol/openid-connect/token + jwk-set-uri: http://localhost:8080/auth/realms/ONAP/protocol/openid-connect/certs + registration: + keycloak: + provider: keycloak + client-id: portal-bff + client-secret: pKOuVH1bwRZoNzp5P5t4GV8CqcCJYVtr + authorization-grant-type: client_credentials + resourceserver: + jwt: + jwk-set-uri: http://localhost:8080/auth/realms/ONAP/protocol/openid-connect/certs + +management: + endpoints: + web: + exposure: + include: "*" + +portal-bff: + realm: ONAP + portal-prefs-url: http://localhost:9001 + portal-history-url: http://localhost:9002 + keycloak-url: http://localhost:8080/ + instance-id: PORTAL + +logging: + level: + root: debug diff --git a/app/src/main/resources/application.yml b/app/src/main/resources/application.yml new file mode 100644 index 0000000..83686b5 --- /dev/null +++ b/app/src/main/resources/application.yml @@ -0,0 +1,49 @@ +# List of common application properties: +# https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#common-application-properties +server: + port: 9080 + address: 0.0.0.0 + +logging: + level: + org.springframework.web: TRACE + +management: + endpoints: + web: + exposure: + include: "*" + +spring: + application: + name: portal-bff + profiles: + include: + - access-control + security: + oauth2: + client: + provider: + keycloak: + token-uri: ${KEYCLOAK_URL}/auth/realms/${KEYCLOAK_REALM}/protocol/openid-connect/token + jwk-set-uri: ${KEYCLOAK_URL}/auth/realms/${KEYCLOAK_REALM}/protocol/openid-connect/certs + registration: + keycloak: + provider: keycloak + client-id: ${KEYCLOAK_CLIENT_ID} + client-secret: ${KEYCLOAK_CLIENT_SECRET} + authorization-grant-type: client_credentials + resourceserver: + jwt: + jwk-set-uri: ${KEYCLOAK_URL}/auth/realms/${KEYCLOAK_REALM}/protocol/openid-connect/certs + jackson: + serialization: + FAIL_ON_EMPTY_BEANS: false + +portal-bff: + realm: ${KEYCLOAK_REALM} + portal-prefs-url: ${PORTAL_PREFS_URL} + portal-history-url: ${PORTAL_HISTORY_URL} + keycloak-url: ${KEYCLOAK_URL} + instance-id: PORTAL + diff --git a/app/src/main/resources/logback-spring.xml b/app/src/main/resources/logback-spring.xml new file mode 100644 index 0000000..05503bc --- /dev/null +++ b/app/src/main/resources/logback-spring.xml @@ -0,0 +1,13 @@ + + + + + ${LOGBACK_LEVEL:-info} + + + + + + + + diff --git a/app/src/test/java/org/onap/portal/bff/ApiDocsIntegrationTest.java b/app/src/test/java/org/onap/portal/bff/ApiDocsIntegrationTest.java new file mode 100644 index 0000000..66edfee --- /dev/null +++ b/app/src/test/java/org/onap/portal/bff/ApiDocsIntegrationTest.java @@ -0,0 +1,40 @@ +/* + * + * Copyright (c) 2022. Deutsche Telekom AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * + * + */ + +package org.onap.portal.bff; + +import org.junit.jupiter.api.Test; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; + +class ApiDocsIntegrationTest extends BaseIntegrationTest { + + @Test + void apiDocsAreAvailable() { + unauthenticatedRequestSpecification() + .given() + .accept(MediaType.TEXT_HTML_VALUE) + .when() + .get("/api-docs.html") + .then() + .statusCode(HttpStatus.OK.value()); + } +} diff --git a/app/src/test/java/org/onap/portal/bff/BaseIntegrationTest.java b/app/src/test/java/org/onap/portal/bff/BaseIntegrationTest.java new file mode 100644 index 0000000..f310850 --- /dev/null +++ b/app/src/test/java/org/onap/portal/bff/BaseIntegrationTest.java @@ -0,0 +1,228 @@ +/* + * + * Copyright (c) 2022. Deutsche Telekom AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * + * + */ + +package org.onap.portal.bff; + +import static io.vavr.API.None; +import static io.vavr.API.Some; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.tomakehurst.wiremock.client.WireMock; +import com.github.tomakehurst.wiremock.extension.responsetemplating.ResponseTemplateTransformer; +import com.nimbusds.jose.jwk.JWKSet; +import io.restassured.RestAssured; +import io.restassured.filter.log.RequestLoggingFilter; +import io.restassured.filter.log.ResponseLoggingFilter; +import io.restassured.specification.RequestSpecification; +import io.vavr.collection.List; +import io.vavr.control.Option; +import java.time.Clock; +import java.time.OffsetDateTime; +import java.util.UUID; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.onap.portal.bff.config.IdTokenExchangeFilterFunction; +import org.onap.portal.bff.config.PortalBffConfig; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.boot.web.server.LocalServerPort; +import org.springframework.cloud.contract.wiremock.AutoConfigureWireMock; +import org.springframework.cloud.contract.wiremock.WireMockConfigurationCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.http.MediaType; + +/** Base class for all tests that has the common config including port, realm, logging and auth. */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@AutoConfigureWireMock(port = 0) +public abstract class BaseIntegrationTest { + + @TestConfiguration + public static class Config { + @Bean + WireMockConfigurationCustomizer optionsCustomizer() { + return options -> options.extensions(new ResponseTemplateTransformer(true)); + } + } + + @LocalServerPort protected int port; + + @Value("${portal-bff.realm}") + protected String realm; + + @Autowired protected ObjectMapper objectMapper; + @Autowired private TokenGenerator tokenGenerator; + + @Autowired protected PortalBffConfig portalBffConfig; + + @BeforeAll + public static void setup() { + RestAssured.filters(new RequestLoggingFilter(), new ResponseLoggingFilter()); + } + + /** Mocks the OIDC auth flow. */ + @BeforeEach + public void mockAuth() { + WireMock.reset(); + + WireMock.stubFor( + WireMock.get( + WireMock.urlMatching( + String.format("/auth/realms/%s/protocol/openid-connect/certs", realm))) + .willReturn( + WireMock.aResponse() + .withHeader("Content-Type", JWKSet.MIME_TYPE) + .withBody(tokenGenerator.getJwkSet().toString()))); + + final TokenGenerator.TokenGeneratorConfig config = + TokenGenerator.TokenGeneratorConfig.builder().port(port).realm(realm).build(); + + WireMock.stubFor( + WireMock.post( + WireMock.urlMatching( + String.format("/auth/realms/%s/protocol/openid-connect/token", realm))) + .withBasicAuth("test", "test") + .withRequestBody(WireMock.containing("grant_type=client_credentials")) + .willReturn( + WireMock.aResponse() + .withHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE) + .withBody( + objectMapper + .createObjectNode() + .put("token_type", "bearer") + .put("access_token", tokenGenerator.generateToken(config)) + .put("expires_in", config.getExpireIn().getSeconds()) + .put("refresh_token", tokenGenerator.generateToken(config)) + .put("refresh_expires_in", config.getExpireIn().getSeconds()) + .put("not-before-policy", 0) + .put("session_state", UUID.randomUUID().toString()) + .put("scope", "email profile") + .toString()))); + } + + /** + * Object to store common attributes of requests that are going to be made. Adds an Identity + * header for the onap_admin role to the request. + */ + protected RequestSpecification requestSpecification() { + final String idToken = tokenGenerator.generateToken(getTokenGeneratorConfig("onap_admin")); + + return unauthenticatedRequestSpecification() + .auth() + .preemptive() + .oauth2(idToken) + .header(IdTokenExchangeFilterFunction.X_AUTH_IDENTITY_HEADER, "Bearer " + idToken); + } + + /** + * Object to store common attributes of requests that are going to be made. Adds an Identity + * header for the given role to the request. + * + * @param role the role used for RBAC + * @return the templated request + */ + protected RequestSpecification requestSpecification(String role) { + final String idToken = tokenGenerator.generateToken(getTokenGeneratorConfig(role)); + + return unauthenticatedRequestSpecification() + .auth() + .preemptive() + .oauth2(idToken) + .header(IdTokenExchangeFilterFunction.X_AUTH_IDENTITY_HEADER, "Bearer " + idToken); + } + + /** + * Object to store common attributes of requests that are going to be made. Adds an Identity + * header for the given roles to the request. + * + * @param roles the roles used for RBAC + * @return the templated request + */ + protected RequestSpecification requestSpecification(List roles) { + final String idToken = tokenGenerator.generateToken(getTokenGeneratorConfig(roles)); + + return unauthenticatedRequestSpecification() + .auth() + .preemptive() + .oauth2(idToken) + .header(IdTokenExchangeFilterFunction.X_AUTH_IDENTITY_HEADER, "Bearer " + idToken); + } + + /** Get a RequestSpecification that does not have an Identity header. */ + protected RequestSpecification unauthenticatedRequestSpecification() { + return RestAssured.given().port(port); + } + + /** + * Builds an OAuth2 configuration including the role, port and realm. This config can be used to + * generate OAuth2 access tokens. + * + * @param role the role used for RBAC + * @return the OAuth2 configuration + */ + protected TokenGenerator.TokenGeneratorConfig getTokenGeneratorConfig(String role) { + return TokenGenerator.TokenGeneratorConfig.builder() + .port(port) + .realm(realm) + .roles(List.of(role)) + .build(); + } + + /** + * Builds an OAuth2 configuration including the roles, port and realm. This config can be used to + * generate OAuth2 access tokens. + * + * @param roles the roles used for RBAC + * @return the OAuth2 configuration + */ + protected TokenGenerator.TokenGeneratorConfig getTokenGeneratorConfig(List roles) { + return TokenGenerator.TokenGeneratorConfig.builder() + .port(port) + .realm(realm) + .roles(roles) + .build(); + } + + public static OffsetDateTime offsetNow() { + return OffsetDateTime.now(Clock.systemUTC()); + } + + public static String randomUUID() { + return UUID.randomUUID().toString(); + } + + public static String adjustPath(String basePath, Option page, Option pageSize) { + return adjustPath(basePath, page, pageSize, None()); + } + + public static String adjustPath( + String basePath, Option page, Option pageSize, Option filter) { + return page.map(pg -> basePath + "?page=" + pg) + .fold( + () -> pageSize.map(pgs -> basePath + "?pageSize=" + pgs), + pth -> pageSize.map(pgs -> pth + "&pageSize=" + pgs).orElse(Some(pth))) + .fold( + () -> filter.map(f -> basePath + "?filter=" + f), + pth -> filter.map(f -> pth + "&filter=" + f).orElse(Some(pth))) + .getOrElse(basePath); + } +} diff --git a/app/src/test/java/org/onap/portal/bff/HealthCheckIntegrationTest.java b/app/src/test/java/org/onap/portal/bff/HealthCheckIntegrationTest.java new file mode 100644 index 0000000..cef85e1 --- /dev/null +++ b/app/src/test/java/org/onap/portal/bff/HealthCheckIntegrationTest.java @@ -0,0 +1,48 @@ +/* + * + * Copyright (c) 2022. Deutsche Telekom AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * + * + */ + +package org.onap.portal.bff; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.availability.ApplicationAvailability; +import org.springframework.boot.availability.LivenessState; +import org.springframework.boot.availability.ReadinessState; + +class HealthCheckIntegrationTest extends BaseIntegrationTest { + + @Autowired private ApplicationAvailability applicationAvailability; + + @Test + void livenessProbeIsAvailable() { + + assertThat(applicationAvailability.getLivenessState()).isEqualTo(LivenessState.CORRECT); + } + + @Test + void readinessProbeIsAvailable() { + + assertThat(applicationAvailability.getReadinessState()) + .isEqualTo(ReadinessState.ACCEPTING_TRAFFIC); + } +} diff --git a/app/src/test/java/org/onap/portal/bff/TokenGenerator.java b/app/src/test/java/org/onap/portal/bff/TokenGenerator.java new file mode 100644 index 0000000..d438d95 --- /dev/null +++ b/app/src/test/java/org/onap/portal/bff/TokenGenerator.java @@ -0,0 +1,123 @@ +/* + * + * Copyright (c) 2022. Deutsche Telekom AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * + * + */ + +package org.onap.portal.bff; + +import com.nimbusds.jose.JOSEObjectType; +import com.nimbusds.jose.JWSAlgorithm; +import com.nimbusds.jose.JWSHeader; +import com.nimbusds.jose.JWSSigner; +import com.nimbusds.jose.crypto.RSASSASigner; +import com.nimbusds.jose.jwk.JWKSet; +import com.nimbusds.jose.jwk.KeyUse; +import com.nimbusds.jose.jwk.RSAKey; +import com.nimbusds.jose.jwk.gen.RSAKeyGenerator; +import com.nimbusds.jwt.JWTClaimsSet; +import com.nimbusds.jwt.SignedJWT; +import io.vavr.collection.List; +import java.time.Clock; +import java.time.Duration; +import java.time.Instant; +import java.util.Date; +import java.util.UUID; +import lombok.Builder; +import lombok.Getter; +import lombok.NonNull; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +public class TokenGenerator { + + private static final String ROLES_CLAIM = "roles"; + + private final Clock clock; + private final RSAKey jwk; + private final JWKSet jwkSet; + private final JWSSigner signer; + + @Autowired + public TokenGenerator(Clock clock) { + try { + this.clock = clock; + jwk = + new RSAKeyGenerator(2048) + .keyUse(KeyUse.SIGNATURE) + .keyID(UUID.randomUUID().toString()) + .generate(); + jwkSet = new JWKSet(jwk); + signer = new RSASSASigner(jwk); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public JWKSet getJwkSet() { + return jwkSet; + } + + public String generateToken(TokenGeneratorConfig config) { + final Instant iat = clock.instant(); + final Instant exp = iat.plus(config.expireIn); + + final JWTClaimsSet claims = + new JWTClaimsSet.Builder() + .jwtID(UUID.randomUUID().toString()) + .subject(UUID.randomUUID().toString()) + .issuer(config.issuer()) + .issueTime(Date.from(iat)) + .expirationTime(Date.from(exp)) + .claim(ROLES_CLAIM, config.getRoles()) + .build(); + + final SignedJWT jwt = + new SignedJWT( + new JWSHeader.Builder(JWSAlgorithm.RS256) + .keyID(jwk.getKeyID()) + .type(JOSEObjectType.JWT) + .build(), + claims); + + try { + jwt.sign(signer); + } catch (Exception e) { + throw new RuntimeException(e); + } + + return jwt.serialize(); + } + + @Getter + @Builder + public static class TokenGeneratorConfig { + private final int port; + + @NonNull private final String realm; + + @NonNull @Builder.Default private final Duration expireIn = Duration.ofMinutes(5); + + @Builder.Default private final List roles = List.empty(); + + public String issuer() { + return String.format("http://localhost:%d/auth/realms/%s", port, realm); + } + } +} diff --git a/app/src/test/java/org/onap/portal/bff/actions/ActionDto.java b/app/src/test/java/org/onap/portal/bff/actions/ActionDto.java new file mode 100644 index 0000000..c88a0db --- /dev/null +++ b/app/src/test/java/org/onap/portal/bff/actions/ActionDto.java @@ -0,0 +1,39 @@ +/* + * + * Copyright (c) 2022. Deutsche Telekom AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * + * + */ + +package org.onap.portal.bff.actions; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +public class ActionDto { + String type; + String action; + String message; + String downStreamSystem; + String downStreamId; +} diff --git a/app/src/test/java/org/onap/portal/bff/actions/ActionFixtures.java b/app/src/test/java/org/onap/portal/bff/actions/ActionFixtures.java new file mode 100644 index 0000000..3e7c917 --- /dev/null +++ b/app/src/test/java/org/onap/portal/bff/actions/ActionFixtures.java @@ -0,0 +1,106 @@ +/* + * + * Copyright (c) 2022. Deutsche Telekom AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * + * + */ + +package org.onap.portal.bff.actions; + +import java.time.OffsetDateTime; +import java.time.temporal.ChronoUnit; +import org.onap.portal.bff.openapi.client_portal_history.model.ActionResponsePortalHistoryDto; +import org.onap.portal.bff.openapi.client_portal_history.model.ActionsListResponsePortalHistoryDto; +import org.onap.portal.bff.openapi.client_portal_history.model.CreateActionRequestPortalHistoryDto; +import org.onap.portal.bff.openapi.server.model.CreateActionRequestApiDto; + +public class ActionFixtures { + + public static ActionsListResponsePortalHistoryDto generateActionsListResponse( + Integer numberOfActions, Integer totalCount, OffsetDateTime createdAt) { + ActionsListResponsePortalHistoryDto actionsListResponsePortalHistoryDto = + new ActionsListResponsePortalHistoryDto(); + for (Integer i = 0; i < numberOfActions; i++) { + actionsListResponsePortalHistoryDto.addActionsListItem( + generateActionResponse( + "Instantiation", "create", null, i.toString(), "SO", i, createdAt)); + } + actionsListResponsePortalHistoryDto.setTotalCount(totalCount); + return actionsListResponsePortalHistoryDto; + } + + public static ActionResponsePortalHistoryDto generateActionResponse( + String type, + String action, + String message, + String id, + String downStreamSystem, + Integer deltaHours, + OffsetDateTime createdAt) { + ActionDto actionDto = new ActionDto(); + actionDto.setType(type); + actionDto.setAction(action); + actionDto.setMessage(message); + actionDto.setDownStreamSystem(downStreamSystem); + actionDto.setDownStreamId(id); + + return new ActionResponsePortalHistoryDto() + .action(actionDto) + .actionCreatedAt(createdAt.minus(deltaHours, ChronoUnit.HOURS)); + } + + public static CreateActionRequestPortalHistoryDto generateActionRequestPortalHistoryDto( + String type, + String action, + String message, + String id, + String downStreamSystem, + String userId, + OffsetDateTime createdAt) { + ActionDto actionDto = new ActionDto(); + actionDto.setType(type); + actionDto.setAction(action); + actionDto.setMessage(message); + actionDto.setDownStreamSystem(downStreamSystem); + actionDto.setDownStreamId(id); + return new CreateActionRequestPortalHistoryDto() + .action(actionDto) + .actionCreatedAt(createdAt) + .userId(userId); + } + + public static CreateActionRequestApiDto generateCreateActionRequestApiDto( + String type, + String action, + String message, + String id, + String downStreamSystem, + String userId, + OffsetDateTime createdAt) { + ActionDto actionDto = new ActionDto(); + actionDto.setType(type); + actionDto.setAction(action); + actionDto.setMessage(message); + actionDto.setDownStreamSystem(downStreamSystem); + actionDto.setDownStreamId(id); + + return new CreateActionRequestApiDto() + .action(actionDto) + .actionCreatedAt(createdAt) + .userId(userId); + } +} diff --git a/app/src/test/java/org/onap/portal/bff/actions/ActionsMocks.java b/app/src/test/java/org/onap/portal/bff/actions/ActionsMocks.java new file mode 100644 index 0000000..0bb27d7 --- /dev/null +++ b/app/src/test/java/org/onap/portal/bff/actions/ActionsMocks.java @@ -0,0 +1,228 @@ +/* + * + * Copyright (c) 2022. Deutsche Telekom AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * + * + */ + +package org.onap.portal.bff.actions; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.github.tomakehurst.wiremock.client.WireMock; +import com.github.tomakehurst.wiremock.matching.EqualToPattern; +import io.restassured.http.Header; +import org.apache.http.HttpHeaders; +import org.onap.portal.bff.BaseIntegrationTest; +import org.onap.portal.bff.openapi.client_portal_history.model.ActionResponsePortalHistoryDto; +import org.onap.portal.bff.openapi.client_portal_history.model.ActionsListResponsePortalHistoryDto; +import org.onap.portal.bff.openapi.client_portal_history.model.ProblemPortalHistoryDto; +import org.onap.portal.bff.openapi.server.model.ActionsListResponseApiDto; +import org.onap.portal.bff.openapi.server.model.ActionsResponseApiDto; +import org.onap.portal.bff.openapi.server.model.CreateActionRequestApiDto; +import org.onap.portal.bff.openapi.server.model.ProblemApiDto; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; + +public class ActionsMocks extends BaseIntegrationTest { + protected static final String X_REQUEST_ID = "addf6005-3075-4c80-b7bc-2c70b7d42b00"; + + // used for test thatActionsListCanBeRetrieved + protected ActionsListResponseApiDto listActions() { + return requestSpecification() + .given() + .accept(MediaType.APPLICATION_JSON_VALUE) + .header(new Header("X-Request-Id", X_REQUEST_ID)) + .when() + .get("/actions") + .then() + .statusCode(HttpStatus.OK.value()) + .extract() + .body() + .as(ActionsListResponseApiDto.class); + } + + // used for test thatActionsListCanBeRetrieved + protected void mockListActions( + ActionsListResponsePortalHistoryDto actionsListResponsePortalHistoryDto) throws Exception { + WireMock.stubFor( + WireMock.get(WireMock.urlEqualTo("/v1/actions?page=1&pageSize=10")) + .withHeader("X-Request-Id", new EqualToPattern(X_REQUEST_ID)) + .willReturn( + WireMock.aResponse() + .withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .withBody( + objectMapper.writeValueAsString(actionsListResponsePortalHistoryDto)))); + } + + // used for test thatActionsListCanNotBeRetrieved + protected ProblemApiDto listActionsProblem() { + return requestSpecification() + .given() + .accept(MediaType.APPLICATION_JSON_VALUE) + .header(new Header("X-Request-Id", X_REQUEST_ID)) + .when() + .get("/actions") + .then() + .statusCode(HttpStatus.BAD_GATEWAY.value()) + .extract() + .body() + .as(ProblemApiDto.class); + } + + // used for test thatActionsListCanNotBeRetrieved + protected void mockListActionsProblem(ProblemPortalHistoryDto problemPortalHistoryDto) + throws Exception { + WireMock.stubFor( + WireMock.get(WireMock.urlEqualTo("/v1/actions?page=1&pageSize=10")) + .withHeader("X-Request-Id", new EqualToPattern(X_REQUEST_ID)) + .willReturn( + WireMock.aResponse() + .withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_PROBLEM_JSON_VALUE) + .withStatus(HttpStatus.INTERNAL_SERVER_ERROR.value()) + .withBody(objectMapper.writeValueAsString(problemPortalHistoryDto)))); + } + + // used for test thatActionCanBeRetrieved + protected void mockGetActions( + ActionsListResponsePortalHistoryDto actionsListResponsePortalHistoryDto, + String userId, + Integer showLastHours) + throws Exception { + WireMock.stubFor( + WireMock.get( + WireMock.urlEqualTo( + "/v1/actions/" + + userId + + "?page=1&pageSize=10" + + "&showLastHours=" + + showLastHours)) + .withHeader("X-Request-Id", new EqualToPattern(X_REQUEST_ID)) + .willReturn( + WireMock.aResponse() + .withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .withBody( + objectMapper.writeValueAsString(actionsListResponsePortalHistoryDto)))); + } + // used for test thatActionCanBeRetrieved + protected ActionsListResponseApiDto getActions(String userId) { + return requestSpecification() + .given() + .accept(MediaType.APPLICATION_JSON_VALUE) + .header(new Header("X-Request-Id", X_REQUEST_ID)) + .when() + .get("/actions/" + userId + "?page=1&pageSize=10&showLastHours=2") + .then() + .statusCode(HttpStatus.OK.value()) + .extract() + .body() + .as(ActionsListResponseApiDto.class); + } + + // used for test thatActionCanBeRetrievedWithoutParameterShowLastHours + protected void mockGetActionsWithoutParameterShowLastHours( + ActionsListResponsePortalHistoryDto actionsListResponsePortalHistoryDto, String userId) + throws Exception { + WireMock.stubFor( + WireMock.get(WireMock.urlEqualTo("/v1/actions/" + userId + "?page=1&pageSize=10")) + .withHeader("X-Request-Id", new EqualToPattern(X_REQUEST_ID)) + .willReturn( + WireMock.aResponse() + .withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .withBody( + objectMapper.writeValueAsString(actionsListResponsePortalHistoryDto)))); + } + // used for test thatActionCanBeRetrievedWithoutParameterShowLastHours + protected ActionsListResponseApiDto getActionsWithoutParameterShowLastHours(String userId) { + return requestSpecification() + .given() + .accept(MediaType.APPLICATION_JSON_VALUE) + .header(new Header("X-Request-Id", X_REQUEST_ID)) + .when() + .get("/actions/" + userId + "?page=1&pageSize=10") + .then() + .statusCode(HttpStatus.OK.value()) + .extract() + .body() + .as(ActionsListResponseApiDto.class); + } + + // Used for thatActionCanBeCreated + protected void mockCreateActions( + String userId, ActionResponsePortalHistoryDto actionResponsePortalHistoryDto) + throws Exception { + WireMock.stubFor( + WireMock.post(WireMock.urlEqualTo("/v1/actions/" + userId)) + .withHeader("X-Request-Id", new EqualToPattern(X_REQUEST_ID)) + .withRequestBody(WireMock.matchingJsonPath("$.action")) + .willReturn( + WireMock.aResponse() + .withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .withStatus(200) + .withBody(objectMapper.writeValueAsString(actionResponsePortalHistoryDto)))); + } + + // Used for thatActionCanBeCreated + protected ActionsResponseApiDto createAction( + CreateActionRequestApiDto createActionRequestApiDto, String userId) throws Exception { + return requestSpecification() + .given() + .accept(MediaType.APPLICATION_JSON_VALUE) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .header(new Header("X-Request-Id", X_REQUEST_ID)) + .body(objectMapper.writeValueAsString(createActionRequestApiDto)) + .when() + .post("/actions/" + userId) + .then() + .statusCode(HttpStatus.OK.value()) + .extract() + .body() + .as(ActionsResponseApiDto.class); + } + + // Used for thatActionCanNotBeCreated + protected void mockCreateActionsProblem( + String userId, ProblemPortalHistoryDto problemPortalHistoryDto) + throws JsonProcessingException { + WireMock.stubFor( + WireMock.post(WireMock.urlEqualTo("/v1/actions/" + userId)) + .withHeader("X-Request-Id", new EqualToPattern(X_REQUEST_ID)) + .withRequestBody(WireMock.matchingJsonPath("$.action")) + .willReturn( + WireMock.aResponse() + .withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_PROBLEM_JSON_VALUE) + .withStatus(500) + .withBody(objectMapper.writeValueAsString(problemPortalHistoryDto)))); + } + // Used for thatActionCanNotBeCreated + protected ProblemApiDto createActionProblem( + CreateActionRequestApiDto createActionRequestApiDto, String userId) + throws JsonProcessingException { + return requestSpecification() + .given() + .accept(MediaType.APPLICATION_JSON_VALUE) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .header(new Header("X-Request-Id", X_REQUEST_ID)) + .body(objectMapper.writeValueAsString(createActionRequestApiDto)) + .when() + .post("/actions/" + userId) + .then() + .statusCode(HttpStatus.BAD_GATEWAY.value()) + .extract() + .body() + .as(ProblemApiDto.class); + } +} diff --git a/app/src/test/java/org/onap/portal/bff/actions/CreateActionsIntegrationTest.java b/app/src/test/java/org/onap/portal/bff/actions/CreateActionsIntegrationTest.java new file mode 100644 index 0000000..0b6ec57 --- /dev/null +++ b/app/src/test/java/org/onap/portal/bff/actions/CreateActionsIntegrationTest.java @@ -0,0 +1,85 @@ +/* + * + * Copyright (c) 2022. Deutsche Telekom AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * + * + */ + +package org.onap.portal.bff.actions; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.OffsetDateTime; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; +import org.onap.portal.bff.openapi.client_portal_history.model.ActionResponsePortalHistoryDto; +import org.onap.portal.bff.openapi.client_portal_history.model.ProblemPortalHistoryDto; +import org.onap.portal.bff.openapi.server.model.ActionsResponseApiDto; +import org.onap.portal.bff.openapi.server.model.CreateActionRequestApiDto; +import org.onap.portal.bff.openapi.server.model.ProblemApiDto; +import org.springframework.http.HttpStatus; + +class CreateActionsIntegrationTest extends ActionsMocks { + + @Test + void thatActionCanBeCreated() throws Exception { + String userId = "22-33-44-55"; + OffsetDateTime createdAt = OffsetDateTime.now(); + ActionResponsePortalHistoryDto actionResponsePortalHistoryDto = + ActionFixtures.generateActionResponse( + "Instantiation", "create", "no detail message", "223344", "SO", 0, createdAt); + CreateActionRequestApiDto createActionDto = + ActionFixtures.generateCreateActionRequestApiDto( + "Instantiation", "create", "no detail message", "223344", "SO", userId, createdAt); + + mockCreateActions(userId, actionResponsePortalHistoryDto); + + final ActionsResponseApiDto response = createAction(createActionDto, userId); + + assertThat(response.getActionCreatedAt()) + .isEqualTo(actionResponsePortalHistoryDto.getActionCreatedAt()); + Assertions.assertThat(objectMapper.writeValueAsString(response.getAction())) + .isEqualTo(objectMapper.writeValueAsString(actionResponsePortalHistoryDto.getAction())); + } + + @Test + void thatActionCanNotBeCreated() throws Exception { + String userId = "22-33-44-55"; + OffsetDateTime createdAt = OffsetDateTime.now(); + + ProblemPortalHistoryDto problemPortalHistoryDto = + new ProblemPortalHistoryDto() + .status(HttpStatus.INTERNAL_SERVER_ERROR.value()) + .detail("Internal database error") + .title("Internal Server Error") + .instance("portal-history"); + + CreateActionRequestApiDto createActionDto = + ActionFixtures.generateCreateActionRequestApiDto( + "Instantiation", "create", "no detail message", "223344", "SO", userId, createdAt); + + mockCreateActionsProblem(userId, problemPortalHistoryDto); + + final ProblemApiDto response = createActionProblem(createActionDto, userId); + + assertThat(response.getDownstreamSystem()) + .isEqualTo(ProblemApiDto.DownstreamSystemEnum.PORTAL_HISTORY); + + assertThat(response.getDownstreamStatus()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR.value()); + assertThat(response.getDetail()).isEqualTo(problemPortalHistoryDto.getDetail()); + } +} diff --git a/app/src/test/java/org/onap/portal/bff/actions/GetActionsIntegrationTest.java b/app/src/test/java/org/onap/portal/bff/actions/GetActionsIntegrationTest.java new file mode 100644 index 0000000..1f77036 --- /dev/null +++ b/app/src/test/java/org/onap/portal/bff/actions/GetActionsIntegrationTest.java @@ -0,0 +1,79 @@ +/* + * + * Copyright (c) 2022. Deutsche Telekom AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * + * + */ + +package org.onap.portal.bff.actions; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.OffsetDateTime; +import org.junit.jupiter.api.Test; +import org.onap.portal.bff.openapi.client_portal_history.model.ActionsListResponsePortalHistoryDto; +import org.onap.portal.bff.openapi.server.model.ActionsListResponseApiDto; + +class GetActionsIntegrationTest extends ActionsMocks { + + @Test + void thatActionCanBeRetrievedWithParameterShowLastHours() throws Exception { + int numberOfActions = 10; + Integer showLastHours = 2; + String userId = "22-33-44-55"; + OffsetDateTime createdAt = OffsetDateTime.now(); + ActionsListResponsePortalHistoryDto actionsListResponsePortalHistoryDto = + ActionFixtures.generateActionsListResponse(numberOfActions, 30, createdAt); + + mockGetActions(actionsListResponsePortalHistoryDto, userId, showLastHours); + + final ActionsListResponseApiDto response = getActions(userId); + + assertThat(response.getTotalCount()).isEqualTo(30); + assertThat(response.getItems()).hasSize(numberOfActions); + assertThat(response.getItems().get(0).getActionCreatedAt()) + .isEqualTo( + actionsListResponsePortalHistoryDto.getActionsList().get(0).getActionCreatedAt()); + assertThat(objectMapper.writeValueAsString(response.getItems().get(0).getAction())) + .isEqualTo( + objectMapper.writeValueAsString( + actionsListResponsePortalHistoryDto.getActionsList().get(0).getAction())); + } + + @Test + void thatActionCanBeRetrievedWithoutParameterShowLastHours() throws Exception { + int numberOfActions = 10; + String userId = "22-33-44-55"; + OffsetDateTime createdAt = OffsetDateTime.now(); + ActionsListResponsePortalHistoryDto actionsListResponsePortalHistoryDto = + ActionFixtures.generateActionsListResponse(numberOfActions, 30, createdAt); + + mockGetActionsWithoutParameterShowLastHours(actionsListResponsePortalHistoryDto, userId); + + final ActionsListResponseApiDto response = getActionsWithoutParameterShowLastHours(userId); + + assertThat(response.getTotalCount()).isEqualTo(30); + assertThat(response.getItems()).hasSize(numberOfActions); + assertThat(response.getItems().get(0).getActionCreatedAt()) + .isEqualTo( + actionsListResponsePortalHistoryDto.getActionsList().get(0).getActionCreatedAt()); + assertThat(objectMapper.writeValueAsString(response.getItems().get(0).getAction())) + .isEqualTo( + objectMapper.writeValueAsString( + actionsListResponsePortalHistoryDto.getActionsList().get(0).getAction())); + } +} diff --git a/app/src/test/java/org/onap/portal/bff/actions/ListActionsIntegrationTest.java b/app/src/test/java/org/onap/portal/bff/actions/ListActionsIntegrationTest.java new file mode 100644 index 0000000..7eb7078 --- /dev/null +++ b/app/src/test/java/org/onap/portal/bff/actions/ListActionsIntegrationTest.java @@ -0,0 +1,78 @@ +/* + * + * Copyright (c) 2022. Deutsche Telekom AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * + * + */ + +package org.onap.portal.bff.actions; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.OffsetDateTime; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; +import org.onap.portal.bff.openapi.client_portal_history.model.ActionsListResponsePortalHistoryDto; +import org.onap.portal.bff.openapi.client_portal_history.model.ProblemPortalHistoryDto; +import org.onap.portal.bff.openapi.server.model.ActionsListResponseApiDto; +import org.onap.portal.bff.openapi.server.model.ProblemApiDto; +import org.springframework.http.HttpStatus; + +class ListActionsIntegrationTest extends ActionsMocks { + + @Test + void thatActionsListCanBeRetrieved() throws Exception { + int numberOfActions = 10; + OffsetDateTime createdAt = OffsetDateTime.now(); + ActionsListResponsePortalHistoryDto actionsListResponsePortalHistoryDto = + ActionFixtures.generateActionsListResponse(numberOfActions, 1000, createdAt); + + mockListActions(actionsListResponsePortalHistoryDto); + + final ActionsListResponseApiDto response = listActions(); + + assertThat(response.getTotalCount()).isEqualTo(1000); + assertThat(response.getItems()).hasSize(numberOfActions); + assertThat(response.getItems().get(0).getActionCreatedAt()) + .isEqualTo( + actionsListResponsePortalHistoryDto.getActionsList().get(0).getActionCreatedAt()); + Assertions.assertThat(objectMapper.writeValueAsString(response.getItems().get(0).getAction())) + .isEqualTo( + objectMapper.writeValueAsString( + actionsListResponsePortalHistoryDto.getActionsList().get(0).getAction())); + } + + @Test + void thatActionsListCanNotBeRetrieved() throws Exception { + + ProblemPortalHistoryDto problemPortalHistoryDto = + new ProblemPortalHistoryDto() + .status(HttpStatus.INTERNAL_SERVER_ERROR.value()) + .detail("Internal database error") + .title("Internal Server Error") + .instance("portal-history"); + + mockListActionsProblem(problemPortalHistoryDto); + + final ProblemApiDto response = listActionsProblem(); + + assertThat(response.getDownstreamSystem()) + .isEqualTo(ProblemApiDto.DownstreamSystemEnum.PORTAL_HISTORY); + assertThat(response.getDownstreamStatus()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR.value()); + assertThat(response.getDetail()).isEqualTo(problemPortalHistoryDto.getDetail()); + } +} diff --git a/app/src/test/java/org/onap/portal/bff/headers/XRequestIdHeaderTest.java b/app/src/test/java/org/onap/portal/bff/headers/XRequestIdHeaderTest.java new file mode 100644 index 0000000..ad54c82 --- /dev/null +++ b/app/src/test/java/org/onap/portal/bff/headers/XRequestIdHeaderTest.java @@ -0,0 +1,76 @@ +/* + * + * Copyright (c) 2022. Deutsche Telekom AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * + * + */ + +package org.onap.portal.bff.headers; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.github.tomakehurst.wiremock.client.WireMock; +import com.github.tomakehurst.wiremock.matching.EqualToPattern; +import io.restassured.http.Header; +import org.junit.jupiter.api.Test; +import org.onap.portal.bff.BaseIntegrationTest; +import org.onap.portal.bff.openapi.client_portal_prefs.model.PreferencesPortalPrefsDto; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; + +class XRequestIdHeaderTest extends BaseIntegrationTest { + protected static final String X_REQUEST_ID = "addf6005-3075-4c80-b7bc-2c70b7d42b57"; + + @Test + void xRequestIdHeaderIsCorrectlySetInResponse() throws Exception { + // use preferences endpoint for testing the header + final PreferencesPortalPrefsDto preferencesPortalPrefsDto = + new PreferencesPortalPrefsDto(); + + //mockGetTile(tileDetailResponsePortalServiceDto, X_REQUEST_ID); + mockGetPreferences(preferencesPortalPrefsDto, X_REQUEST_ID); + + final String response = getPreferencesExtractHeader(X_REQUEST_ID); + assertThat(response).isEqualTo(X_REQUEST_ID); + } + + protected void mockGetPreferences(PreferencesPortalPrefsDto preferencesPortalPrefsDto, String xRequestId) + throws Exception { + WireMock.stubFor( + WireMock.get(WireMock.urlEqualTo("/v1/preferences")) + .withHeader("X-Request-Id", new EqualToPattern(X_REQUEST_ID)) + .willReturn( + WireMock.aResponse() + .withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .withHeader("X-Request-Id", xRequestId) + .withBody(objectMapper.writeValueAsString(preferencesPortalPrefsDto)))); + } + + protected String getPreferencesExtractHeader(String xRequestId) { + return requestSpecification() + .given() + .accept(MediaType.APPLICATION_JSON_VALUE) + .header(new Header("X-Request-Id", xRequestId)) + .when() + .get("/preferences") + .then() + .statusCode(HttpStatus.OK.value()) + .extract() + .header("X-Request-Id"); + } +} diff --git a/app/src/test/java/org/onap/portal/bff/idtoken/IdTokenExchangeFilterFunctionTest.java b/app/src/test/java/org/onap/portal/bff/idtoken/IdTokenExchangeFilterFunctionTest.java new file mode 100644 index 0000000..7d65849 --- /dev/null +++ b/app/src/test/java/org/onap/portal/bff/idtoken/IdTokenExchangeFilterFunctionTest.java @@ -0,0 +1,89 @@ +/* + * + * Copyright (c) 2022. Deutsche Telekom AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * + * + */ + +package org.onap.portal.bff.idtoken; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.mock; + +import java.net.URI; +import java.util.UUID; +import org.junit.jupiter.api.Test; +import org.onap.portal.bff.BaseIntegrationTest; +import org.onap.portal.bff.config.IdTokenExchangeFilterFunction; +import org.springframework.http.HttpMethod; +import org.springframework.mock.http.server.reactive.MockServerHttpRequest; +import org.springframework.mock.web.server.MockServerWebExchange; +import org.springframework.web.reactive.function.client.ClientRequest; +import org.springframework.web.reactive.function.client.ClientResponse; +import org.springframework.web.reactive.function.client.ExchangeFunction; +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Mono; + +class IdTokenExchangeFilterFunctionTest extends BaseIntegrationTest { + + @Test + void idTokenIsCorrectlyPropagated() { + final IdTokenExchangeFilterFunction filterFunction = new IdTokenExchangeFilterFunction(); + + final String idToken = UUID.randomUUID().toString(); + final ServerWebExchange serverWebExchange = + MockServerWebExchange.builder( + MockServerHttpRequest.get("http://localhost:8000") + .header(IdTokenExchangeFilterFunction.X_AUTH_IDENTITY_HEADER, idToken)) + .build(); + + final ClientRequest request = + ClientRequest.create(HttpMethod.GET, URI.create("http://api-server:9000")) + .attribute(ServerWebExchange.class.getName(), serverWebExchange) + .build(); + final ClientResponse response = mock(ClientResponse.class); + + final ExchangeFunction exchange = + r -> { + assertThat(r.headers().getOrEmpty(IdTokenExchangeFilterFunction.X_AUTH_IDENTITY_HEADER)) + .containsExactly(idToken); + + return Mono.just(response); + }; + + final ClientResponse result = filterFunction.filter(request, exchange).block(); + assertThat(result).isEqualTo(response); + } + + @Test + void exceptionIsThrownWhenIdTokenIsMissingInRequest() { + final IdTokenExchangeFilterFunction filterFunction = new IdTokenExchangeFilterFunction(); + + final ServerWebExchange serverWebExchange = + MockServerWebExchange.builder(MockServerHttpRequest.get("http://localhost:8000")).build(); + + final ClientRequest request = + ClientRequest.create(HttpMethod.GET, URI.create("http://api-server:9000")) + .attribute(ServerWebExchange.class.getName(), serverWebExchange) + .build(); + final ExchangeFunction exchange = r -> Mono.just(mock(ClientResponse.class)); + + assertThatThrownBy(() -> filterFunction.filter(request, exchange).block()) + .hasMessage("Forbidden: ID token is missing"); + } +} diff --git a/app/src/test/java/org/onap/portal/bff/preferences/CreatePreferencesIntegrationTest.java b/app/src/test/java/org/onap/portal/bff/preferences/CreatePreferencesIntegrationTest.java new file mode 100644 index 0000000..5259de8 --- /dev/null +++ b/app/src/test/java/org/onap/portal/bff/preferences/CreatePreferencesIntegrationTest.java @@ -0,0 +1,115 @@ +/* + * + * Copyright (c) 2022. Deutsche Telekom AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * + * + */ + +package org.onap.portal.bff.preferences; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +import io.restassured.http.Header; +import org.junit.jupiter.api.Test; +import org.onap.portal.bff.openapi.client_portal_prefs.model.PreferencesPortalPrefsDto; +import org.onap.portal.bff.openapi.client_portal_prefs.model.ProblemPortalPrefsDto; +import org.onap.portal.bff.openapi.server.model.CreatePreferencesRequestApiDto; +import org.onap.portal.bff.openapi.server.model.PreferencesResponseApiDto; +import org.onap.portal.bff.openapi.server.model.ProblemApiDto; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; + +class CreatePreferencesIntegrationTest extends PreferencesMocks { + @Test + void thatPreferencesCanBeCreated() throws Exception { + PreferencesPortalPrefsDto preferencesPortalPrefsDto = new PreferencesPortalPrefsDto(); + preferencesPortalPrefsDto.setProperties( + "{\n" + + "\"properties\": {\n" + + "\"appStarter\": \"value1\",\n" + + "\"dashboard\": {\"key1:\" : \"value2\"}\n" + + "}\n" + + "\n" + + "}"); + mockCreatePreferences(preferencesPortalPrefsDto); + + final CreatePreferencesRequestApiDto request = + new CreatePreferencesRequestApiDto() + .properties( + "{\n" + + "\"properties\": {\n" + + "\"appStarter\": \"value1\",\n" + + "\"dashboard\": {\"key1:\" : \"value2\"}\n" + + "}\n" + + "\n" + + "}"); + final PreferencesResponseApiDto response = createPreferences(request); + assertThat(response).isNotNull(); + assertThat(response.getProperties()).isEqualTo(preferencesPortalPrefsDto.getProperties()); + } + + @Test + void thatPreferencesCanNotBeCreated() throws Exception { + final var problemPortalPrefsDto = new ProblemPortalPrefsDto(); + problemPortalPrefsDto.setStatus(HttpStatus.BAD_REQUEST.value()); + problemPortalPrefsDto.setTitle(HttpStatus.BAD_REQUEST.toString()); + problemPortalPrefsDto.setDetail("Some details"); + + final PreferencesPortalPrefsDto preferencesPortalPrefsDto = + new PreferencesPortalPrefsDto() + .properties( + "{\n" + + "\"properties\": {\n" + + "\"appStarter\": \"value1\",\n" + + "\"dashboard\": {\"key1:\" : \"value2\"}\n" + + "}\n" + + "\n" + + "}"); + mockCreatePreferencesError(preferencesPortalPrefsDto, problemPortalPrefsDto); + + CreatePreferencesRequestApiDto responseBody = + new CreatePreferencesRequestApiDto() + .properties( + "{\n" + + "\"properties\": {\n" + + "\"appStarter\": \"value1\",\n" + + "\"dashboard\": {\"key1:\" : \"value2\"}\n" + + "}\n" + + "\n" + + "}"); + final ProblemApiDto response = + requestSpecification() + .given() + .accept(MediaType.APPLICATION_PROBLEM_JSON_VALUE) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .header(new Header("X-Request-Id", X_REQUEST_ID)) + .body(responseBody) + .when() + .post("/preferences") + .then() + .statusCode(HttpStatus.BAD_GATEWAY.value()) + .extract() + .body() + .as(ProblemApiDto.class); + + assertThat(response.getStatus()).isEqualTo(HttpStatus.BAD_GATEWAY.value()); + assertThat(response.getDetail()).isEqualTo(problemPortalPrefsDto.getDetail()); + assertThat(response.getDownstreamSystem()) + .isEqualTo(ProblemApiDto.DownstreamSystemEnum.PORTAL_PREFS); + assertThat(response.getDownstreamStatus()).isEqualTo(HttpStatus.BAD_REQUEST.value()); + } +} diff --git a/app/src/test/java/org/onap/portal/bff/preferences/GetPreferencesIntegrationTest.java b/app/src/test/java/org/onap/portal/bff/preferences/GetPreferencesIntegrationTest.java new file mode 100644 index 0000000..1e1317b --- /dev/null +++ b/app/src/test/java/org/onap/portal/bff/preferences/GetPreferencesIntegrationTest.java @@ -0,0 +1,77 @@ +/* + * + * Copyright (c) 2022. Deutsche Telekom AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * + * + */ + +package org.onap.portal.bff.preferences; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.restassured.http.Header; +import org.junit.jupiter.api.Test; +import org.onap.portal.bff.openapi.client_portal_prefs.model.PreferencesPortalPrefsDto; +import org.onap.portal.bff.openapi.client_portal_prefs.model.ProblemPortalPrefsDto; +import org.onap.portal.bff.openapi.server.model.PreferencesResponseApiDto; +import org.onap.portal.bff.openapi.server.model.ProblemApiDto; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; + +class GetPreferencesIntegrationTest extends PreferencesMocks { + + @Test + void thatPreferencesCanBeRetrieved() throws Exception { + PreferencesPortalPrefsDto preferencesPortalPrefsDto = new PreferencesPortalPrefsDto(); + preferencesPortalPrefsDto.setProperties(getFixture(PREF_PROPERTIES_FILE, Object.class)); + mockGetPreferences(preferencesPortalPrefsDto); + + final PreferencesResponseApiDto response = getPreferences(); + assertThat(response).isNotNull(); + } + + @Test + void thatPreferencesCanNotBeRetrieved() throws Exception { + final ProblemPortalPrefsDto problemResponse = + new ProblemPortalPrefsDto() + .title("Unauthorized") + .status(HttpStatus.UNAUTHORIZED.value()) + .detail("Unauthorized error detail") + .instance("instance") + .type("type"); + + mockGetPreferencesError(problemResponse); + + final ProblemApiDto errorResponse = + requestSpecification() + .given() + .accept(MediaType.APPLICATION_JSON_VALUE) + .header(new Header("X-Request-Id", "addf6005-3075-4c80-b7bc-2c70b7d42b57")) + .when() + .get("/preferences") + .then() + .statusCode(HttpStatus.BAD_GATEWAY.value()) + .contentType(MediaType.APPLICATION_PROBLEM_JSON_VALUE) + .extract() + .body() + .as(ProblemApiDto.class); + + assertThat(errorResponse.getStatus()).isEqualTo(HttpStatus.BAD_GATEWAY.value()); + assertThat(errorResponse.getTitle()).isEqualTo(HttpStatus.UNAUTHORIZED.toString()); + assertThat(errorResponse.getDetail()).isEqualTo("Unauthorized error detail"); + } +} diff --git a/app/src/test/java/org/onap/portal/bff/preferences/PreferencesMocks.java b/app/src/test/java/org/onap/portal/bff/preferences/PreferencesMocks.java new file mode 100644 index 0000000..e08c690 --- /dev/null +++ b/app/src/test/java/org/onap/portal/bff/preferences/PreferencesMocks.java @@ -0,0 +1,182 @@ +/* + * + * Copyright (c) 2022. Deutsche Telekom AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * + * + */ + +package org.onap.portal.bff.preferences; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.tomakehurst.wiremock.client.WireMock; +import com.github.tomakehurst.wiremock.matching.EqualToPattern; +import io.restassured.http.Header; +import java.io.File; +import java.io.IOException; +import org.apache.http.HttpHeaders; +import org.onap.portal.bff.BaseIntegrationTest; +import org.onap.portal.bff.openapi.client_portal_prefs.model.PreferencesPortalPrefsDto; +import org.onap.portal.bff.openapi.client_portal_prefs.model.ProblemPortalPrefsDto; +import org.onap.portal.bff.openapi.server.model.CreatePreferencesRequestApiDto; +import org.onap.portal.bff.openapi.server.model.PreferencesResponseApiDto; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; + +public class PreferencesMocks extends BaseIntegrationTest { + protected static final String X_REQUEST_ID = "addf6005-3075-4c80-b7bc-2c70b7d42b57"; + + protected static final String PREF_PROPERTIES_FILE = + "src/test/resources/preferences/preferencesProperties.json"; + + protected static final ObjectMapper objectMapper = + new ObjectMapper().setSerializationInclusion(JsonInclude.Include.NON_NULL); + + protected static T getFixture(final String fileName, Class type) throws IOException { + return objectMapper.readValue(new File(fileName), type); + } + + protected PreferencesResponseApiDto getPreferences() { + + return requestSpecification() + .given() + .accept(MediaType.APPLICATION_JSON_VALUE) + .header(new Header("X-Request-Id", X_REQUEST_ID)) + .when() + .get("/preferences") + .then() + .statusCode(HttpStatus.OK.value()) + .extract() + .body() + .as(PreferencesResponseApiDto.class); + } + + protected void mockGetPreferences(PreferencesPortalPrefsDto preferencesPortalPrefsDto) + throws Exception { + WireMock.stubFor( + WireMock.get(WireMock.urlEqualTo("/v1/preferences")) + .withHeader("X-Request-Id", new EqualToPattern(X_REQUEST_ID)) + .willReturn( + WireMock.aResponse() + .withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .withBody(objectMapper.writeValueAsString(preferencesPortalPrefsDto)))); + } + + protected void mockGetPreferencesError(ProblemPortalPrefsDto problem) throws Exception { + WireMock.stubFor( + WireMock.get(WireMock.urlEqualTo("/v1/preferences")) + .withHeader("X-Request-Id", new EqualToPattern(X_REQUEST_ID)) + .willReturn( + WireMock.aResponse() + .withHeader( + org.springframework.http.HttpHeaders.CONTENT_TYPE, + MediaType.APPLICATION_PROBLEM_JSON_VALUE) + .withBody(objectMapper.writeValueAsString(problem)) + .withStatus(HttpStatus.UNAUTHORIZED.value()))); + } + + protected PreferencesResponseApiDto createPreferences(CreatePreferencesRequestApiDto request) { + return requestSpecification() + .given() + .contentType(MediaType.APPLICATION_JSON_VALUE) + .header(new Header("X-Request-Id", X_REQUEST_ID)) + .body(request) + .when() + .post("/preferences") + .then() + .statusCode(HttpStatus.OK.value()) + .extract() + .body() + .as(PreferencesResponseApiDto.class); + } + + protected void mockCreatePreferences(PreferencesPortalPrefsDto preferencesPortalPrefsDto) + throws Exception { + WireMock.stubFor( + WireMock.post(WireMock.urlEqualTo("/v1/preferences")) + .withHeader("X-Request-Id", new EqualToPattern(X_REQUEST_ID)) + .withRequestBody( + WireMock.equalToJson( + objectMapper.writeValueAsString(preferencesPortalPrefsDto), true, false)) + .willReturn( + WireMock.aResponse() + .withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .withBody(objectMapper.writeValueAsString(preferencesPortalPrefsDto)))); + } + + protected void mockCreatePreferencesError( + PreferencesPortalPrefsDto preferencesPortalPrefsDto, + ProblemPortalPrefsDto problemPortalPrefsDto) + throws Exception { + WireMock.stubFor( + WireMock.post(WireMock.urlEqualTo("/v1/preferences")) + .withHeader("X-Request-Id", new EqualToPattern(X_REQUEST_ID)) + .withRequestBody( + WireMock.equalToJson( + objectMapper.writeValueAsString(preferencesPortalPrefsDto), true, false)) + .willReturn( + WireMock.aResponse() + .withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .withStatus(HttpStatus.BAD_REQUEST.value()) + .withBody(objectMapper.writeValueAsString(problemPortalPrefsDto)))); + } + + protected PreferencesResponseApiDto updatePreferences(CreatePreferencesRequestApiDto request) { + return requestSpecification() + .given() + .contentType(MediaType.APPLICATION_JSON_VALUE) + .header(new Header("X-Request-Id", X_REQUEST_ID)) + .body(request) + .when() + .put("/preferences") + .then() + .statusCode(HttpStatus.OK.value()) + .extract() + .body() + .as(PreferencesResponseApiDto.class); + } + + protected void mockUpdatePreferences(PreferencesPortalPrefsDto preferencesPortalPrefsDto) + throws Exception { + WireMock.stubFor( + WireMock.put(WireMock.urlEqualTo("/v1/preferences")) + .withHeader("X-Request-Id", new EqualToPattern(X_REQUEST_ID)) + .withRequestBody( + WireMock.equalToJson( + objectMapper.writeValueAsString(preferencesPortalPrefsDto), true, false)) + .willReturn( + WireMock.aResponse() + .withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .withBody(objectMapper.writeValueAsString(preferencesPortalPrefsDto)))); + } + + protected void mockUpdatePreferencesError( + PreferencesPortalPrefsDto preferencesPortalPrefsDto, + ProblemPortalPrefsDto problemPortalPrefsDto) + throws Exception { + WireMock.stubFor( + WireMock.put(WireMock.urlEqualTo("/v1/preferences")) + .withRequestBody( + WireMock.equalToJson( + objectMapper.writeValueAsString(preferencesPortalPrefsDto), true, false)) + .willReturn( + WireMock.aResponse() + .withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .withStatus(HttpStatus.BAD_REQUEST.value()) + .withBody(objectMapper.writeValueAsString(problemPortalPrefsDto)))); + } +} diff --git a/app/src/test/java/org/onap/portal/bff/preferences/UpdatePreferencesIntegrationTest.java b/app/src/test/java/org/onap/portal/bff/preferences/UpdatePreferencesIntegrationTest.java new file mode 100644 index 0000000..74d902c --- /dev/null +++ b/app/src/test/java/org/onap/portal/bff/preferences/UpdatePreferencesIntegrationTest.java @@ -0,0 +1,114 @@ +/* + * + * Copyright (c) 2022. Deutsche Telekom AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * + * + */ + +package org.onap.portal.bff.preferences; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +import io.restassured.http.Header; +import org.junit.jupiter.api.Test; +import org.onap.portal.bff.openapi.client_portal_prefs.model.PreferencesPortalPrefsDto; +import org.onap.portal.bff.openapi.client_portal_prefs.model.ProblemPortalPrefsDto; +import org.onap.portal.bff.openapi.server.model.CreatePreferencesRequestApiDto; +import org.onap.portal.bff.openapi.server.model.PreferencesResponseApiDto; +import org.onap.portal.bff.openapi.server.model.ProblemApiDto; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; + +class UpdatePreferencesIntegrationTest extends PreferencesMocks { + @Test + void thatPreferencesCanBeUpdated() throws Exception { + PreferencesPortalPrefsDto preferencesPortalPrefsDto = new PreferencesPortalPrefsDto(); + preferencesPortalPrefsDto.setProperties( + "{\n" + + "\"properties\": {\n" + + "\"appStarter\": \"value1\",\n" + + "\"dashboard\": {\"key1:\" : \"value2\"}\n" + + "}\n" + + "\n" + + "}"); + mockUpdatePreferences(preferencesPortalPrefsDto); + + final CreatePreferencesRequestApiDto requestApiDto = + new CreatePreferencesRequestApiDto() + .properties( + "{\n" + + "\"properties\": {\n" + + "\"appStarter\": \"value1\",\n" + + "\"dashboard\": {\"key1:\" : \"value2\"}\n" + + "}\n" + + "\n" + + "}"); + final PreferencesResponseApiDto response = updatePreferences(requestApiDto); + assertThat(response).isNotNull(); + assertThat(response.getProperties()).isEqualTo(preferencesPortalPrefsDto.getProperties()); + } + + @Test + void thatPreferencesCanNotBeUpdated() throws Exception { + final var problemPortalPrefsDto = new ProblemPortalPrefsDto(); + problemPortalPrefsDto.setStatus(HttpStatus.BAD_REQUEST.value()); + problemPortalPrefsDto.setTitle(HttpStatus.BAD_REQUEST.toString()); + problemPortalPrefsDto.setDetail("Some details"); + + final PreferencesPortalPrefsDto preferencesPortalPrefsDto = + new PreferencesPortalPrefsDto() + .properties( + "{\n" + + "\"properties\": {\n" + + "\"appStarter\": \"value1\",\n" + + "\"dashboard\": {\"key1:\" : \"value2\"}\n" + + "}\n" + + "\n" + + "}"); + mockUpdatePreferencesError(preferencesPortalPrefsDto, problemPortalPrefsDto); + + CreatePreferencesRequestApiDto requestApiDto = + new CreatePreferencesRequestApiDto() + .properties( + "{\n" + + "\"properties\": {\n" + + "\"appStarter\": \"value1\",\n" + + "\"dashboard\": {\"key1:\" : \"value2\"}\n" + + "}\n" + + "\n" + + "}"); + final ProblemApiDto response = + requestSpecification() + .given() + .accept(MediaType.APPLICATION_PROBLEM_JSON_VALUE) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .header(new Header("X-Request-Id", X_REQUEST_ID)) + .body(requestApiDto) + .when() + .put("/preferences") + .then() + .statusCode(HttpStatus.BAD_GATEWAY.value()) + .extract() + .body() + .as(ProblemApiDto.class); + + assertThat(response.getStatus()).isEqualTo(HttpStatus.BAD_GATEWAY.value()); + assertThat(response.getDownstreamSystem()) + .isEqualTo(ProblemApiDto.DownstreamSystemEnum.PORTAL_PREFS); + assertThat(response.getDownstreamStatus()).isEqualTo(HttpStatus.BAD_REQUEST.value()); + } +} diff --git a/app/src/test/java/org/onap/portal/bff/roles/ListRealmRolesIntegrationTest.java b/app/src/test/java/org/onap/portal/bff/roles/ListRealmRolesIntegrationTest.java new file mode 100644 index 0000000..03f39db --- /dev/null +++ b/app/src/test/java/org/onap/portal/bff/roles/ListRealmRolesIntegrationTest.java @@ -0,0 +1,88 @@ +/* + * + * Copyright (c) 2022. Deutsche Telekom AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * + * + */ + +package org.onap.portal.bff.roles; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.github.tomakehurst.wiremock.client.WireMock; +import io.restassured.http.Header; +import io.vavr.collection.List; +import org.junit.jupiter.api.Test; +import org.onap.portal.bff.openapi.client_portal_keycloak.model.ErrorResponseKeycloakDto; +import org.onap.portal.bff.openapi.client_portal_keycloak.model.RoleKeycloakDto; +import org.onap.portal.bff.openapi.server.model.ProblemApiDto; +import org.onap.portal.bff.openapi.server.model.RoleApiDto; +import org.onap.portal.bff.openapi.server.model.RoleListResponseApiDto; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; + +class ListRealmRolesIntegrationTest extends RolesMocks { + + @Test + void thatListOfRealmRolesCanBeProvided() throws Exception { + final RoleKeycloakDto keycloakRole1 = new RoleKeycloakDto().id("1").name("role1"); + final RoleKeycloakDto keycloakRole2 = new RoleKeycloakDto().id("2").name("role2"); + final List keycloakRoles = List.of(keycloakRole1, keycloakRole2); + mockListRealmRoles(keycloakRoles); + + final RoleApiDto role1 = new RoleApiDto().id("1").name("role1"); + final RoleApiDto role2 = new RoleApiDto().id("2").name("role2"); + + final RoleListResponseApiDto response = listRoles(); + assertThat(response).isNotNull(); + assertThat(response.getTotalCount()).isEqualTo(response.getItems().size()); + assertThat(response.getItems()).containsExactly(role1, role2); + } + + @Test + void thatListOfRealmRolesCanNotBeProvided() throws Exception { + final ErrorResponseKeycloakDto keycloakErrorResponse = + new ErrorResponseKeycloakDto().errorMessage("Some error message"); + + WireMock.stubFor( + WireMock.get(WireMock.urlMatching(String.format("/auth/admin/realms/%s/roles", realm))) + .willReturn( + WireMock.aResponse() + .withHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE) + .withStatus(400) + .withBody(objectMapper.writeValueAsString(keycloakErrorResponse)))); + + final ProblemApiDto response = + requestSpecification() + .given() + .accept(MediaType.APPLICATION_JSON_VALUE) + .header(new Header("X-Request-Id", "addf6005-3075-4c80-b7bc-2c70b7d42b57")) + .when() + .get("/roles") + .then() + .statusCode(HttpStatus.BAD_GATEWAY.value()) + .extract() + .body() + .as(ProblemApiDto.class); + + assertThat(response).isNotNull(); + assertThat(response.getTitle()).isEqualTo(HttpStatus.BAD_REQUEST.toString()); + assertThat(response.getStatus()).isEqualTo(HttpStatus.BAD_GATEWAY.value()); + assertThat(response.getDownstreamSystem()) + .isEqualTo(ProblemApiDto.DownstreamSystemEnum.KEYCLOAK); + } +} diff --git a/app/src/test/java/org/onap/portal/bff/roles/RolesMocks.java b/app/src/test/java/org/onap/portal/bff/roles/RolesMocks.java new file mode 100644 index 0000000..fa43302 --- /dev/null +++ b/app/src/test/java/org/onap/portal/bff/roles/RolesMocks.java @@ -0,0 +1,57 @@ +/* + * + * Copyright (c) 2022. Deutsche Telekom AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * + * + */ + +package org.onap.portal.bff.roles; + +import com.github.tomakehurst.wiremock.client.WireMock; +import io.restassured.http.Header; +import io.vavr.collection.List; +import org.onap.portal.bff.BaseIntegrationTest; +import org.onap.portal.bff.openapi.client_portal_keycloak.model.RoleKeycloakDto; +import org.onap.portal.bff.openapi.server.model.RoleListResponseApiDto; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; + +public class RolesMocks extends BaseIntegrationTest { + + protected RoleListResponseApiDto listRoles() { + return requestSpecification() + .given() + .accept(MediaType.APPLICATION_JSON_VALUE) + .header(new Header("X-Request-Id", "addf6005-3075-4c80-b7bc-2c70b7d42b57")) + .when() + .get("/roles") + .then() + .statusCode(HttpStatus.OK.value()) + .extract() + .body() + .as(RoleListResponseApiDto.class); + } + + protected void mockListRealmRoles(List roles) throws Exception { + WireMock.stubFor( + WireMock.get(WireMock.urlMatching(String.format("/auth/admin/realms/%s/roles", realm))) + .willReturn( + WireMock.aResponse() + .withHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE) + .withBody(objectMapper.writeValueAsString(roles)))); + } +} diff --git a/app/src/test/java/org/onap/portal/bff/users/CreateUserIntegrationTest.java b/app/src/test/java/org/onap/portal/bff/users/CreateUserIntegrationTest.java new file mode 100644 index 0000000..641724e --- /dev/null +++ b/app/src/test/java/org/onap/portal/bff/users/CreateUserIntegrationTest.java @@ -0,0 +1,308 @@ +/* + * + * Copyright (c) 2022. Deutsche Telekom AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * + * + */ + +package org.onap.portal.bff.users; + +import static io.vavr.API.List; +import static org.assertj.core.api.Assertions.assertThat; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.github.tomakehurst.wiremock.client.WireMock; +import io.restassured.http.Header; +import io.vavr.API; +import io.vavr.collection.List; +import org.junit.jupiter.api.Test; +import org.onap.portal.bff.BaseIntegrationTest; +import org.onap.portal.bff.openapi.client_portal_keycloak.model.ErrorResponseKeycloakDto; +import org.onap.portal.bff.openapi.client_portal_keycloak.model.RequiredActionsKeycloakDto; +import org.onap.portal.bff.openapi.client_portal_keycloak.model.RoleKeycloakDto; +import org.onap.portal.bff.openapi.client_portal_keycloak.model.UserKeycloakDto; +import org.onap.portal.bff.openapi.server.model.CreateUserRequestApiDto; +import org.onap.portal.bff.openapi.server.model.ProblemApiDto; +import org.onap.portal.bff.openapi.server.model.RoleApiDto; +import org.onap.portal.bff.openapi.server.model.RoleListResponseApiDto; +import org.onap.portal.bff.openapi.server.model.UserResponseApiDto; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; + +class CreateUserIntegrationTest extends BaseIntegrationTest { + + @Test + void userCanBeCreated() throws Exception { + String xRequestID = "addf6005-3075-4c80-b7bc-2c70b7d42b57"; + + final UserKeycloakDto keycloakRequest = + new UserKeycloakDto() + .username("user1") + .email("user1@localhost.com") + .enabled(true) + .requiredActions(List(RequiredActionsKeycloakDto.UPDATE_PASSWORD).toJavaList()); + final String userId = randomUUID(); + mockCreateUser(keycloakRequest, userId); + + final UserKeycloakDto keycloakResponse = + new UserKeycloakDto() + .id(userId) + .username(keycloakRequest.getUsername()) + .email(keycloakRequest.getEmail()) + .firstName(keycloakRequest.getFirstName()) + .lastName(keycloakRequest.getLastName()) + .enabled(keycloakRequest.getEnabled()); + mockGetUser(userId, keycloakResponse); + + final RoleKeycloakDto onapAdmin = new RoleKeycloakDto().id(randomUUID()).name("onap_admin"); + mockAddRoles(userId, List(onapAdmin)); + mockAssignedRoles(userId, List(onapAdmin)); + mockListRealmRoles(List(onapAdmin)); + + requestSpecification() + .given() + .accept(MediaType.APPLICATION_JSON_VALUE) + .header(new Header("X-Request-Id", xRequestID)) + .when() + .get(String.format("/users/%s/roles", userId)) + .then() + .statusCode(HttpStatus.OK.value()) + .extract() + .body() + .as(RoleListResponseApiDto.class); + mockSendUpdateEmail(userId, API.List(RequiredActionsKeycloakDto.UPDATE_PASSWORD)); + + final CreateUserRequestApiDto request = + new CreateUserRequestApiDto() + .username("user1") + .email("user1@localhost.com") + .firstName(null) + .lastName(null) + .enabled(true) + .addRolesItem(new RoleApiDto().id(onapAdmin.getId()).name("onap_admin")); + + final UserResponseApiDto response = + requestSpecification() + .given() + .accept(MediaType.APPLICATION_JSON_VALUE) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .header(new Header("X-Request-Id", xRequestID)) + .body(request) + .when() + .post("/users") + .then() + .statusCode(HttpStatus.OK.value()) + .extract() + .body() + .as(UserResponseApiDto.class); + + assertThat(response).isNotNull(); + assertThat(response.getId()).isEqualTo(userId); + assertThat(response.getUsername()).isEqualTo(request.getUsername()); + assertThat(response.getEmail()).isEqualTo(request.getEmail()); + assertThat(response.getFirstName()).isEqualTo(request.getFirstName()); + assertThat(response.getLastName()).isEqualTo(request.getLastName()); + assertThat(response.getEnabled()).isEqualTo(request.getEnabled()); + assertThat(response.getRealmRoles()).containsExactly("onap_admin"); + } + + @Test + void userCanNotBeCreated() throws Exception { + final UserKeycloakDto keycloakRequest = + new UserKeycloakDto() + .username("user1") + .email("user1@localhost.com") + .enabled(true) + .requiredActions(List(RequiredActionsKeycloakDto.UPDATE_PASSWORD).toJavaList()); + final String userId = randomUUID(); + mockCreateUser(keycloakRequest, userId); + + final UserKeycloakDto keycloakResponse = + new UserKeycloakDto() + .id(userId) + .username(keycloakRequest.getUsername()) + .email(keycloakRequest.getEmail()) + .firstName(keycloakRequest.getFirstName()) + .lastName(keycloakRequest.getLastName()) + .enabled(keycloakRequest.getEnabled()); + mockGetUser(userId, keycloakResponse); + + final RoleKeycloakDto onapAdmin = new RoleKeycloakDto().id(randomUUID()).name("onap_admin"); + mockAddRoles(userId, List(onapAdmin)); + mockListRealmRoles(List(onapAdmin)); + + final ErrorResponseKeycloakDto keycloakErrorResponse = + new ErrorResponseKeycloakDto().errorMessage("Some error message"); + + mockSendUpdateEmailWithProblem( + userId, API.List(RequiredActionsKeycloakDto.UPDATE_PASSWORD), keycloakErrorResponse); + + final CreateUserRequestApiDto request = + new CreateUserRequestApiDto() + .username("user1") + .email("user1@localhost.com") + .firstName(null) + .lastName(null) + .enabled(true) + .addRolesItem(new RoleApiDto().id(onapAdmin.getId()).name("onap_admin")); + + final ProblemApiDto response = + requestSpecification() + .given() + .accept(MediaType.APPLICATION_JSON_VALUE) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .header(new Header("X-Request-Id", "addf6005-3075-4c80-b7bc-2c70b7d42b57")) + .body(request) + .when() + .post("/users") + .then() + .statusCode(HttpStatus.BAD_GATEWAY.value()) + .extract() + .body() + .as(ProblemApiDto.class); + + assertThat(response).isNotNull(); + assertThat(response.getTitle()).isEqualTo(HttpStatus.BAD_REQUEST.toString()); + assertThat(response.getStatus()).isEqualTo(HttpStatus.BAD_GATEWAY.value()); + assertThat(response.getDownstreamSystem()) + .isEqualTo(ProblemApiDto.DownstreamSystemEnum.KEYCLOAK); + } + + @Test + void userCanNotBeCreatedWithNonexistentRoles() throws Exception { + String xRequestID = "addf6005-3075-4c80-b7bc-2c70b7d42b57"; + + mockListRealmRoles(List()); + + final CreateUserRequestApiDto request = + new CreateUserRequestApiDto() + .username("user1") + .email("user1@localhost.com") + .firstName(null) + .lastName(null) + .enabled(true) + .addRolesItem(new RoleApiDto().id("nonexistent_id").name("nonexistent_role")); + + final ProblemApiDto response = + requestSpecification() + .given() + .accept(MediaType.APPLICATION_JSON_VALUE) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .header(new Header("X-Request-Id", xRequestID)) + .body(request) + .when() + .post("/users") + .then() + .statusCode(HttpStatus.NOT_FOUND.value()) + .extract() + .body() + .as(ProblemApiDto.class); + + assertThat(response).isNotNull(); + assertThat(response.getTitle()).isEqualTo(HttpStatus.NOT_FOUND.toString()); + assertThat(response.getStatus()).isEqualTo(HttpStatus.NOT_FOUND.value()); + assertThat(response.getDownstreamSystem()) + .isEqualTo(ProblemApiDto.DownstreamSystemEnum.KEYCLOAK); + } + + protected void mockCreateUser(UserKeycloakDto request, String userId) throws Exception { + WireMock.stubFor( + WireMock.post(WireMock.urlMatching(String.format("/auth/admin/realms/%s/users", realm))) + .withRequestBody(WireMock.equalToJson(objectMapper.writeValueAsString(request))) + .willReturn( + WireMock.aResponse() + .withHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE) + .withHeader( + "location", + String.format("/auth/admin/realms/%s/users/%s", realm, userId)))); + } + + protected void mockGetUser(String userId, UserKeycloakDto response) throws Exception { + WireMock.stubFor( + WireMock.get( + WireMock.urlMatching( + String.format("/auth/admin/realms/%s/users/%s", realm, userId))) + .willReturn( + WireMock.aResponse() + .withHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE) + .withBody(objectMapper.writeValueAsString(response)))); + } + + protected void mockAddRoles(String userId, List response) throws Exception { + WireMock.stubFor( + WireMock.post( + WireMock.urlMatching( + String.format( + "/auth/admin/realms/%s/users/%s/role-mappings/realm", realm, userId))) + .willReturn( + WireMock.aResponse() + .withHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE) + .withBody(objectMapper.writeValueAsString(response)))); + } + + protected void mockSendUpdateEmailWithProblem( + String userId, + List request, + ErrorResponseKeycloakDto keycloakErrorResponse) + throws Exception { + WireMock.stubFor( + WireMock.put( + WireMock.urlMatching( + String.format( + "/auth/admin/realms/%s/users/%s/execute-actions-email", realm, userId))) + .withRequestBody(WireMock.equalTo(objectMapper.writeValueAsString(request))) + .willReturn( + WireMock.aResponse() + .withHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE) + .withStatus(400) + .withBody(objectMapper.writeValueAsString(keycloakErrorResponse)))); + } + + protected void mockAssignedRoles(String userID, List keycloakRoles) + throws JsonProcessingException { + WireMock.stubFor( + WireMock.get( + WireMock.urlMatching( + String.format( + "/auth/admin/realms/%s/users/%s/role-mappings/realm", realm, userID))) + .willReturn( + WireMock.aResponse() + .withHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE) + .withBody(objectMapper.writeValueAsString(keycloakRoles)))); + } + + protected void mockSendUpdateEmail(String userId, List request) + throws Exception { + WireMock.stubFor( + WireMock.put( + WireMock.urlMatching( + String.format( + "/auth/admin/realms/%s/users/%s/execute-actions-email", realm, userId))) + .withRequestBody(WireMock.equalTo(objectMapper.writeValueAsString(request))) + .willReturn( + WireMock.aResponse().withHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE))); + } + + protected void mockListRealmRoles(List roles) throws Exception { + WireMock.stubFor( + WireMock.get(WireMock.urlMatching(String.format("/auth/admin/realms/%s/roles", realm))) + .willReturn( + WireMock.aResponse() + .withHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE) + .withBody(objectMapper.writeValueAsString(roles)))); + } +} diff --git a/app/src/test/java/org/onap/portal/bff/users/DeleteUserIntegrationTest.java b/app/src/test/java/org/onap/portal/bff/users/DeleteUserIntegrationTest.java new file mode 100644 index 0000000..69f6906 --- /dev/null +++ b/app/src/test/java/org/onap/portal/bff/users/DeleteUserIntegrationTest.java @@ -0,0 +1,83 @@ +/* + * + * Copyright (c) 2022. Deutsche Telekom AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * + * + */ + +package org.onap.portal.bff.users; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.github.tomakehurst.wiremock.client.WireMock; +import io.restassured.http.Header; +import org.junit.jupiter.api.Test; +import org.onap.portal.bff.BaseIntegrationTest; +import org.onap.portal.bff.openapi.client_portal_keycloak.model.ErrorResponseKeycloakDto; +import org.onap.portal.bff.openapi.server.model.ProblemApiDto; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; + +class DeleteUserIntegrationTest extends BaseIntegrationTest { + + @Test + void userCanBeDeleted() { + WireMock.stubFor( + WireMock.delete(WireMock.urlMatching(String.format("/auth/admin/realms/%s/users/1", realm))) + .willReturn(WireMock.aResponse().withStatus(204))); + + requestSpecification() + .given() + .header(new Header("X-Request-Id", "addf6005-3075-4c80-b7bc-2c70b7d42b57")) + .when() + .delete("/users/1") + .then() + .statusCode(HttpStatus.NO_CONTENT.value()); + } + + @Test + void userCanNotBeDeleted() throws Exception { + final ErrorResponseKeycloakDto keycloakErrorResponse = + new ErrorResponseKeycloakDto().errorMessage("Some error message"); + + WireMock.stubFor( + WireMock.delete(WireMock.urlMatching(String.format("/auth/admin/realms/%s/users/1", realm))) + .willReturn( + WireMock.aResponse() + .withHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE) + .withStatus(400) + .withBody(objectMapper.writeValueAsString(keycloakErrorResponse)))); + + ProblemApiDto response = + requestSpecification() + .given() + .header(new Header("X-Request-Id", "addf6005-3075-4c80-b7bc-2c70b7d42b57")) + .when() + .delete("/users/1") + .then() + .statusCode(HttpStatus.BAD_GATEWAY.value()) + .extract() + .body() + .as(ProblemApiDto.class); + + assertThat(response).isNotNull(); + assertThat(response.getTitle()).isEqualTo(HttpStatus.BAD_REQUEST.toString()); + assertThat(response.getStatus()).isEqualTo(HttpStatus.BAD_GATEWAY.value()); + assertThat(response.getDownstreamSystem()) + .isEqualTo(ProblemApiDto.DownstreamSystemEnum.KEYCLOAK); + } +} diff --git a/app/src/test/java/org/onap/portal/bff/users/GetUserDetailIntegrationTest.java b/app/src/test/java/org/onap/portal/bff/users/GetUserDetailIntegrationTest.java new file mode 100644 index 0000000..1bca58c --- /dev/null +++ b/app/src/test/java/org/onap/portal/bff/users/GetUserDetailIntegrationTest.java @@ -0,0 +1,126 @@ +/* + * + * Copyright (c) 2022. Deutsche Telekom AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * + * + */ + +package org.onap.portal.bff.users; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.github.tomakehurst.wiremock.client.WireMock; +import io.restassured.http.Header; +import io.vavr.collection.List; +import org.junit.jupiter.api.Test; +import org.onap.portal.bff.BaseIntegrationTest; +import org.onap.portal.bff.openapi.client_portal_keycloak.model.ErrorResponseKeycloakDto; +import org.onap.portal.bff.openapi.client_portal_keycloak.model.RoleKeycloakDto; +import org.onap.portal.bff.openapi.client_portal_keycloak.model.UserKeycloakDto; +import org.onap.portal.bff.openapi.server.model.ProblemApiDto; +import org.onap.portal.bff.openapi.server.model.UserResponseApiDto; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; + +class GetUserDetailIntegrationTest extends BaseIntegrationTest { + + @Test + void detailOfUserCanBeProvided() throws Exception { + final UserKeycloakDto keycloakUser = + new UserKeycloakDto().id("1").username("user1").email("user1@localhost").enabled(true); + + WireMock.stubFor( + WireMock.get(WireMock.urlMatching(String.format("/auth/admin/realms/%s/users/1", realm))) + .willReturn( + WireMock.aResponse() + .withHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE) + .withBody(objectMapper.writeValueAsString(keycloakUser)))); + + final RoleKeycloakDto keycloackRole = new RoleKeycloakDto().id(randomUUID()).name("onap_admin"); + mockAssignedRoles(keycloakUser.getId(), List.of(keycloackRole)); + + final UserResponseApiDto response = + requestSpecification() + .given() + .accept(MediaType.APPLICATION_JSON_VALUE) + .header(new Header("X-Request-Id", "addf6005-3075-4c80-b7bc-2c70b7d42b57")) + .when() + .get("/users/1") + .then() + .statusCode(HttpStatus.OK.value()) + .extract() + .body() + .as(UserResponseApiDto.class); + + assertThat(response).isNotNull(); + assertThat(response.getId()).isEqualTo("1"); + assertThat(response.getUsername()).isEqualTo("user1"); + assertThat(response.getEmail()).isEqualTo("user1@localhost"); + assertThat(response.getFirstName()).isNull(); + assertThat(response.getLastName()).isNull(); + assertThat(response.getRealmRoles()).containsExactly(keycloackRole.getName()); + } + + @Test + void detailOfNonExistentUserCanNotBeProvided() throws Exception { + + ErrorResponseKeycloakDto keycloakErrorResponse = + new ErrorResponseKeycloakDto().errorMessage("Some error message"); + + WireMock.stubFor( + WireMock.get(WireMock.urlMatching(String.format("/auth/admin/realms/%s/users/1", realm))) + .willReturn( + WireMock.aResponse() + .withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .withStatus(400) + .withBody(objectMapper.writeValueAsString(keycloakErrorResponse)))); + + final ProblemApiDto response = + requestSpecification() + .given() + .accept(MediaType.APPLICATION_JSON_VALUE) + .header(new Header("X-Request-Id", "addf6005-3075-4c80-b7bc-2c70b7d42b57")) + .when() + .get("/users/1") + .then() + .statusCode(HttpStatus.BAD_GATEWAY.value()) + .extract() + .body() + .as(ProblemApiDto.class); + + assertThat(response).isNotNull(); + assertThat(response.getTitle()).isEqualTo(HttpStatus.BAD_REQUEST.toString()); + assertThat(response.getStatus()).isEqualTo(HttpStatus.BAD_GATEWAY.value()); + assertThat(response.getDownstreamSystem()) + .isEqualTo(ProblemApiDto.DownstreamSystemEnum.KEYCLOAK); + } + + protected void mockAssignedRoles(String userID, List keycloakRoles) + throws JsonProcessingException { + WireMock.stubFor( + WireMock.get( + WireMock.urlMatching( + String.format( + "/auth/admin/realms/%s/users/%s/role-mappings/realm", realm, userID))) + .willReturn( + WireMock.aResponse() + .withHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE) + .withBody(objectMapper.writeValueAsString(keycloakRoles)))); + } +} diff --git a/app/src/test/java/org/onap/portal/bff/users/ListAssignedRolesIntegrationTest.java b/app/src/test/java/org/onap/portal/bff/users/ListAssignedRolesIntegrationTest.java new file mode 100644 index 0000000..6564577 --- /dev/null +++ b/app/src/test/java/org/onap/portal/bff/users/ListAssignedRolesIntegrationTest.java @@ -0,0 +1,111 @@ +/* + * + * Copyright (c) 2022. Deutsche Telekom AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * + * + */ + +package org.onap.portal.bff.users; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.github.tomakehurst.wiremock.client.WireMock; +import io.restassured.http.Header; +import io.vavr.collection.List; +import org.junit.jupiter.api.Test; +import org.onap.portal.bff.BaseIntegrationTest; +import org.onap.portal.bff.openapi.client_portal_keycloak.model.ErrorResponseKeycloakDto; +import org.onap.portal.bff.openapi.client_portal_keycloak.model.RoleKeycloakDto; +import org.onap.portal.bff.openapi.server.model.ProblemApiDto; +import org.onap.portal.bff.openapi.server.model.RoleApiDto; +import org.onap.portal.bff.openapi.server.model.RoleListResponseApiDto; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; + +class ListAssignedRolesIntegrationTest extends BaseIntegrationTest { + + @Test + void listOfAssignedRolesCanBeProvided() throws Exception { + final RoleKeycloakDto keycloakRole1 = new RoleKeycloakDto().id("1").name("role1"); + final RoleKeycloakDto keycloakRole2 = new RoleKeycloakDto().id("2").name("role2"); + final List keycloakRoles = List.of(keycloakRole1, keycloakRole2); + + WireMock.stubFor( + WireMock.get( + WireMock.urlMatching( + String.format("/auth/admin/realms/%s/users/1/role-mappings/realm", realm))) + .willReturn( + WireMock.aResponse() + .withHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE) + .withBody(objectMapper.writeValueAsString(keycloakRoles)))); + + final RoleListResponseApiDto response = + requestSpecification() + .given() + .accept(MediaType.APPLICATION_JSON_VALUE) + .header(new Header("X-Request-Id", "addf6005-3075-4c80-b7bc-2c70b7d42b57")) + .when() + .get("/users/1/roles") + .then() + .statusCode(HttpStatus.OK.value()) + .extract() + .body() + .as(RoleListResponseApiDto.class); + + final RoleApiDto role1 = new RoleApiDto().id("1").name("role1"); + final RoleApiDto role2 = new RoleApiDto().id("2").name("role2"); + + assertThat(response).isNotNull(); + assertThat(response.getTotalCount()).isEqualTo(response.getItems().size()); + assertThat(response.getItems()).containsExactly(role1, role2); + } + + @Test + void listOfAssignedRolesCanNotBeProvided() throws Exception { + final ErrorResponseKeycloakDto keycloakErrorResponse = + new ErrorResponseKeycloakDto().errorMessage("Some error message"); + + WireMock.stubFor( + WireMock.get( + WireMock.urlMatching( + String.format("/auth/admin/realms/%s/users/1/role-mappings/realm", realm))) + .willReturn( + WireMock.aResponse() + .withHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE) + .withStatus(400) + .withBody(objectMapper.writeValueAsString(keycloakErrorResponse)))); + + final ProblemApiDto response = + requestSpecification() + .given() + .accept(MediaType.APPLICATION_JSON_VALUE) + .header(new Header("X-Request-Id", "addf6005-3075-4c80-b7bc-2c70b7d42b57")) + .when() + .get("/users/1/roles") + .then() + .statusCode(HttpStatus.BAD_GATEWAY.value()) + .extract() + .body() + .as(ProblemApiDto.class); + + assertThat(response).isNotNull(); + assertThat(response.getTitle()).isEqualTo(HttpStatus.BAD_REQUEST.toString()); + assertThat(response.getStatus()).isEqualTo(HttpStatus.BAD_GATEWAY.value()); + assertThat(response.getDownstreamSystem()) + .isEqualTo(ProblemApiDto.DownstreamSystemEnum.KEYCLOAK); + } +} diff --git a/app/src/test/java/org/onap/portal/bff/users/ListAvailableRolesIntegrationTest.java b/app/src/test/java/org/onap/portal/bff/users/ListAvailableRolesIntegrationTest.java new file mode 100644 index 0000000..b5ca3c6 --- /dev/null +++ b/app/src/test/java/org/onap/portal/bff/users/ListAvailableRolesIntegrationTest.java @@ -0,0 +1,113 @@ +/* + * + * Copyright (c) 2022. Deutsche Telekom AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * + * + */ + +package org.onap.portal.bff.users; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.github.tomakehurst.wiremock.client.WireMock; +import io.restassured.http.Header; +import io.vavr.collection.List; +import org.junit.jupiter.api.Test; +import org.onap.portal.bff.BaseIntegrationTest; +import org.onap.portal.bff.openapi.client_portal_keycloak.model.ErrorResponseKeycloakDto; +import org.onap.portal.bff.openapi.client_portal_keycloak.model.RoleKeycloakDto; +import org.onap.portal.bff.openapi.server.model.ProblemApiDto; +import org.onap.portal.bff.openapi.server.model.RoleApiDto; +import org.onap.portal.bff.openapi.server.model.RoleListResponseApiDto; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; + +class ListAvailableRolesIntegrationTest extends BaseIntegrationTest { + + @Test + void listOfAvailableRolesCanBeProvided() throws Exception { + final RoleKeycloakDto keycloakRole1 = new RoleKeycloakDto().id("1").name("role1"); + final RoleKeycloakDto keycloakRole2 = new RoleKeycloakDto().id("2").name("role2"); + final List keycloakRoles = List.of(keycloakRole1, keycloakRole2); + + WireMock.stubFor( + WireMock.get( + WireMock.urlMatching( + String.format( + "/auth/admin/realms/%s/users/1/role-mappings/realm/available", realm))) + .willReturn( + WireMock.aResponse() + .withHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE) + .withBody(objectMapper.writeValueAsString(keycloakRoles)))); + + final RoleListResponseApiDto response = + requestSpecification() + .given() + .accept(MediaType.APPLICATION_JSON_VALUE) + .header(new Header("X-Request-Id", "addf6005-3075-4c80-b7bc-2c70b7d42b57")) + .when() + .get("/users/1/roles/available") + .then() + .statusCode(HttpStatus.OK.value()) + .extract() + .body() + .as(RoleListResponseApiDto.class); + + final RoleApiDto role1 = new RoleApiDto().id("1").name("role1"); + final RoleApiDto role2 = new RoleApiDto().id("2").name("role2"); + + assertThat(response).isNotNull(); + assertThat(response.getTotalCount()).isEqualTo(response.getItems().size()); + assertThat(response.getItems()).containsExactly(role1, role2); + } + + @Test + void listOfAvailableRolesCanNotBeProvided() throws Exception { + final ErrorResponseKeycloakDto keycloakErrorResponse = + new ErrorResponseKeycloakDto().errorMessage("Some error message"); + + WireMock.stubFor( + WireMock.get( + WireMock.urlMatching( + String.format( + "/auth/admin/realms/%s/users/1/role-mappings/realm/available", realm))) + .willReturn( + WireMock.aResponse() + .withHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE) + .withStatus(400) + .withBody(objectMapper.writeValueAsString(keycloakErrorResponse)))); + + ProblemApiDto response = + requestSpecification() + .given() + .accept(MediaType.APPLICATION_JSON_VALUE) + .header(new Header("X-Request-Id", "addf6005-3075-4c80-b7bc-2c70b7d42b57")) + .when() + .get("/users/1/roles/available") + .then() + .statusCode(HttpStatus.BAD_GATEWAY.value()) + .extract() + .body() + .as(ProblemApiDto.class); + + assertThat(response).isNotNull(); + assertThat(response.getTitle()).isEqualTo(HttpStatus.BAD_REQUEST.toString()); + assertThat(response.getStatus()).isEqualTo(HttpStatus.BAD_GATEWAY.value()); + assertThat(response.getDownstreamSystem()) + .isEqualTo(ProblemApiDto.DownstreamSystemEnum.KEYCLOAK); + } +} diff --git a/app/src/test/java/org/onap/portal/bff/users/ListUsersIntegrationTest.java b/app/src/test/java/org/onap/portal/bff/users/ListUsersIntegrationTest.java new file mode 100644 index 0000000..1df7b69 --- /dev/null +++ b/app/src/test/java/org/onap/portal/bff/users/ListUsersIntegrationTest.java @@ -0,0 +1,238 @@ +/* + * + * Copyright (c) 2022. Deutsche Telekom AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * + * + */ + +package org.onap.portal.bff.users; + +import static io.vavr.API.*; +import static org.assertj.core.api.Assertions.assertThat; + +import com.github.tomakehurst.wiremock.client.WireMock; +import io.restassured.http.Header; +import io.vavr.collection.List; +import io.vavr.control.Option; +import org.junit.jupiter.api.Test; +import org.onap.portal.bff.BaseIntegrationTest; +import org.onap.portal.bff.openapi.client_portal_keycloak.model.ErrorResponseKeycloakDto; +import org.onap.portal.bff.openapi.client_portal_keycloak.model.RoleKeycloakDto; +import org.onap.portal.bff.openapi.client_portal_keycloak.model.UserKeycloakDto; +import org.onap.portal.bff.openapi.server.model.ProblemApiDto; +import org.onap.portal.bff.openapi.server.model.UserListResponseApiDto; +import org.onap.portal.bff.openapi.server.model.UserResponseApiDto; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; + +class ListUsersIntegrationTest extends BaseIntegrationTest { + private final RoleKeycloakDto ONAP_ADMIN = + new RoleKeycloakDto().id(randomUUID()).name("onap_admin"); + private final RoleKeycloakDto OFFLINE_ACCESS = + new RoleKeycloakDto().id(randomUUID()).name("offline_access"); + + @Test + void listOfUsersWithDefaultPaginationCanBeProvided() throws Exception { + final UserKeycloakDto tAdmin = + new UserKeycloakDto() + .id("8f05caaf-0e36-4bcd-b9b3-0ae3d531acc2") + .username("t-admin") + .email("t-admin@example.xyz") + .firstName("FirstName4t-admin") + .lastName("LastName4t-admin") + .enabled(true); + + final UserKeycloakDto tDesigner = + new UserKeycloakDto() + .id("04ed5525-740d-42da-bc4c-2d3fcf955ee9") + .username("t-designer") + .email("t-designer@example.xyz") + .firstName("FirstName4t-designer") + .lastName("LastName4t-designer") + .enabled(true); + + mockGetUserCount(2); + mockListUsers(List.of(tAdmin, tDesigner), 0, 10); + mockListRealmRoles(List(ONAP_ADMIN, OFFLINE_ACCESS)); + mockListRoleUsers(OFFLINE_ACCESS.getName(), List(tAdmin, tDesigner)); + mockListRoleUsers(ONAP_ADMIN.getName(), List(tAdmin)); + + final UserResponseApiDto expectedTAdmin = + new UserResponseApiDto() + .id("8f05caaf-0e36-4bcd-b9b3-0ae3d531acc2") + .username("t-admin") + .email("t-admin@example.xyz") + .firstName("FirstName4t-admin") + .lastName("LastName4t-admin") + .enabled(true) + .addRealmRolesItem("onap_admin") + .addRealmRolesItem("offline_access"); + final UserResponseApiDto expectedTDesigner = + new UserResponseApiDto() + .id("04ed5525-740d-42da-bc4c-2d3fcf955ee9") + .username("t-designer") + .email("t-designer@example.xyz") + .firstName("FirstName4t-designer") + .lastName("LastName4t-designer") + .enabled(true) + .addRealmRolesItem("offline_access"); + + final UserListResponseApiDto response = listUsers(); + assertThat(response).isNotNull(); + assertThat(response.getTotalCount()).isEqualTo(2); + assertThat(response.getItems().get(0).getRealmRoles()) + .containsExactlyInAnyOrder( + expectedTAdmin.getRealmRoles().get(0), expectedTAdmin.getRealmRoles().get(1)); + assertThat(response.getItems().get(1).getRealmRoles()) + .containsExactly(expectedTDesigner.getRealmRoles().get(0)); + } + + @Test + void listOfUsersWithSpecifiedPaginationCanBeProvided() throws Exception { + final UserKeycloakDto keycloakUser = + new UserKeycloakDto() + .id("1") + .username("user1") + .email("user1@localhost") + .firstName("User1") + .lastName("Test") + .enabled(true); + + mockGetUserCount(1); + mockListUsers(List.of(keycloakUser), 60, 30); + mockListRealmRoles(List()); + + final UserListResponseApiDto response = listUsers(Some(3), Some(30)); + assertThat(response).isNotNull(); + assertThat(response.getTotalCount()).isEqualTo(1); + assertThat(response.getItems()) + .containsExactly( + new UserResponseApiDto() + .id("1") + .username("user1") + .enabled(true) + .email("user1@localhost") + .firstName("User1") + .lastName("Test") + .realmRoles(java.util.List.of())); + } + + @Test + void listOfUsersWithSpecifiedPaginationCanNotBeProvided() throws Exception { + final ErrorResponseKeycloakDto keycloakErrorResponse = + new ErrorResponseKeycloakDto().errorMessage("Some error message"); + + mockGetUserCount(55); + mockListUsersWithProblems(keycloakErrorResponse, 60, 30); + mockListRealmRoles(List()); + + ProblemApiDto response = + requestSpecification() + .given() + .accept(MediaType.APPLICATION_JSON_VALUE) + .header(new Header("X-Request-Id", "addf6005-3075-4c80-b7bc-2c70b7d42b57")) + .when() + .get(adjustPath("/users", Some(3), Some(30))) + .then() + .statusCode(HttpStatus.BAD_GATEWAY.value()) + .extract() + .body() + .as(ProblemApiDto.class); + + assertThat(response).isNotNull(); + assertThat(response.getTitle()).isEqualTo(HttpStatus.BAD_REQUEST.toString()); + assertThat(response.getStatus()).isEqualTo(HttpStatus.BAD_GATEWAY.value()); + assertThat(response.getDownstreamSystem()) + .isEqualTo(ProblemApiDto.DownstreamSystemEnum.KEYCLOAK); + } + + protected void mockGetUserCount(Integer userCount) { + WireMock.stubFor( + WireMock.get( + WireMock.urlMatching(String.format("/auth/admin/realms/%s/users/count", realm))) + .willReturn( + WireMock.aResponse() + .withHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE) + .withBody(userCount.toString()))); + } + + protected void mockListUsers(List keycloakUsers, Integer first, Integer max) + throws Exception { + WireMock.stubFor( + WireMock.get( + WireMock.urlMatching( + String.format( + "/auth/admin/realms/%s/users\\?first=%s&max=%s", realm, first, max))) + .willReturn( + WireMock.aResponse() + .withHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE) + .withBody(objectMapper.writeValueAsString(keycloakUsers)))); + } + + protected void mockListRealmRoles(List roles) throws Exception { + WireMock.stubFor( + WireMock.get(WireMock.urlMatching(String.format("/auth/admin/realms/%s/roles", realm))) + .willReturn( + WireMock.aResponse() + .withHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE) + .withBody(objectMapper.writeValueAsString(roles)))); + } + + protected void mockListRoleUsers(String roleName, List response) + throws Exception { + WireMock.stubFor( + WireMock.get( + WireMock.urlMatching( + String.format("/auth/admin/realms/%s/roles/%s/users", realm, roleName))) + .willReturn( + WireMock.aResponse() + .withHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE) + .withBody(objectMapper.writeValueAsString(response)))); + } + + protected UserListResponseApiDto listUsers() { + return listUsers(None(), None()); + } + + protected UserListResponseApiDto listUsers(Option page, Option pageSize) { + return requestSpecification() + .given() + .accept(MediaType.APPLICATION_JSON_VALUE) + .header(new Header("X-Request-Id", "addf6005-3075-4c80-b7bc-2c70b7d42b57")) + .when() + .get(adjustPath("/users", page, pageSize)) + .then() + .statusCode(HttpStatus.OK.value()) + .extract() + .body() + .as(UserListResponseApiDto.class); + } + + protected void mockListUsersWithProblems( + ErrorResponseKeycloakDto keycloakErrorResponse, Integer first, Integer max) throws Exception { + WireMock.stubFor( + WireMock.get( + WireMock.urlMatching( + String.format( + "/auth/admin/realms/%s/users\\?first=%s&max=%s", realm, first, max))) + .willReturn( + WireMock.aResponse() + .withHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE) + .withStatus(400) + .withBody(objectMapper.writeValueAsString(keycloakErrorResponse)))); + } +} diff --git a/app/src/test/java/org/onap/portal/bff/users/UpdateAssignedRolesIntegrationTest.java b/app/src/test/java/org/onap/portal/bff/users/UpdateAssignedRolesIntegrationTest.java new file mode 100644 index 0000000..54afdcd --- /dev/null +++ b/app/src/test/java/org/onap/portal/bff/users/UpdateAssignedRolesIntegrationTest.java @@ -0,0 +1,452 @@ +/* + * + * Copyright (c) 2022. Deutsche Telekom AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * + * + */ + +package org.onap.portal.bff.users; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.github.tomakehurst.wiremock.client.WireMock; +import com.github.tomakehurst.wiremock.stubbing.Scenario; +import io.restassured.http.Header; +import io.vavr.collection.List; +import org.junit.jupiter.api.Test; +import org.onap.portal.bff.BaseIntegrationTest; +import org.onap.portal.bff.openapi.client_portal_keycloak.model.ErrorResponseKeycloakDto; +import org.onap.portal.bff.openapi.client_portal_keycloak.model.RoleKeycloakDto; +import org.onap.portal.bff.openapi.server.model.ProblemApiDto; +import org.onap.portal.bff.openapi.server.model.RoleApiDto; +import org.onap.portal.bff.openapi.server.model.RoleListResponseApiDto; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; + +class UpdateAssignedRolesIntegrationTest extends BaseIntegrationTest { + + @Test + void listOfAssignedRolesCanBeUpdatedWhenPreviousAssignedRolesAreEmpty() throws Exception { + final RoleKeycloakDto keycloakRole1 = new RoleKeycloakDto().id("1").name("role1"); + final RoleKeycloakDto keycloakRole2 = new RoleKeycloakDto().id("2").name("role2"); + + final List keycloakAvailableRoles = List.of(keycloakRole1, keycloakRole2); + final List keycloakInitialAssignedRoles = List.of(); + final List keycloakUpdatedAssignedRoles = List.of(keycloakRole1); + + final List keycloakRolesToAdd = List.of(keycloakRole1); + + WireMock.stubFor( + WireMock.get( + WireMock.urlMatching( + String.format( + "/auth/admin/realms/%s/users/1/role-mappings/realm/available", realm))) + .inScenario("test") + .whenScenarioStateIs(Scenario.STARTED) + .willReturn( + WireMock.aResponse() + .withHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE) + .withBody(objectMapper.writeValueAsString(keycloakAvailableRoles)))); + + WireMock.stubFor( + WireMock.get( + WireMock.urlMatching( + String.format("/auth/admin/realms/%s/users/1/role-mappings/realm", realm))) + .inScenario("test") + .whenScenarioStateIs(Scenario.STARTED) + .willReturn( + WireMock.aResponse() + .withHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE) + .withBody(objectMapper.writeValueAsString(keycloakInitialAssignedRoles)))); + + WireMock.stubFor( + WireMock.post( + WireMock.urlMatching( + String.format("/auth/admin/realms/%s/users/1/role-mappings/realm", realm))) + .inScenario("test") + .whenScenarioStateIs(Scenario.STARTED) + .withRequestBody(WireMock.equalTo(objectMapper.writeValueAsString(keycloakRolesToAdd))) + .willReturn(WireMock.aResponse().withStatus(HttpStatus.NO_CONTENT.value())) + .willSetStateTo("rolesUpdated")); + + WireMock.stubFor( + WireMock.get( + WireMock.urlMatching( + String.format("/auth/admin/realms/%s/users/1/role-mappings/realm", realm))) + .inScenario("test") + .whenScenarioStateIs("rolesUpdated") + .willReturn( + WireMock.aResponse() + .withHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE) + .withBody(objectMapper.writeValueAsString(keycloakUpdatedAssignedRoles)))); + + final RoleApiDto roleToAssign = new RoleApiDto().id("1").name("role1"); + final List rolesToAssign = List.of(roleToAssign); + + final RoleListResponseApiDto response = + requestSpecification() + .given() + .accept(MediaType.APPLICATION_JSON_VALUE) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .header(new Header("X-Request-Id", "addf6005-3075-4c80-b7bc-2c70b7d42b57")) + .body(rolesToAssign) + .when() + .put("/users/1/roles") + .then() + .statusCode(HttpStatus.OK.value()) + .extract() + .body() + .as(RoleListResponseApiDto.class); + + final RoleApiDto role = new RoleApiDto().id("1").name("role1"); + + assertThat(response).isNotNull(); + assertThat(response.getTotalCount()).isEqualTo(response.getItems().size()); + assertThat(response.getItems()).containsExactly(role); + } + + @Test + void listOfAssignedRolesCanBeUpdatedWhenPreviousAssignedRolesAreNotEmpty() throws Exception { + final RoleKeycloakDto keycloakRole1 = new RoleKeycloakDto().id("1").name("role1"); + final RoleKeycloakDto keycloakRole2 = new RoleKeycloakDto().id("2").name("role2"); + final RoleKeycloakDto keycloakRole3 = new RoleKeycloakDto().id("3").name("role3"); + + final List keycloakAvailableRoles = + List.of(keycloakRole1, keycloakRole2, keycloakRole3); + final List keycloakInitialAssignedRoles = + List.of(keycloakRole1, keycloakRole2); + final List keycloakUpdatedAssignedRoles = List.of(keycloakRole1); + + final List keycloakRolesToRemove = List.of(keycloakRole1, keycloakRole2); + final List keycloakRolesToAdd = List.of(keycloakRole1); + + WireMock.stubFor( + WireMock.get( + WireMock.urlMatching( + String.format( + "/auth/admin/realms/%s/users/1/role-mappings/realm/available", realm))) + .inScenario("test") + .whenScenarioStateIs(Scenario.STARTED) + .willReturn( + WireMock.aResponse() + .withHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE) + .withBody(objectMapper.writeValueAsString(keycloakAvailableRoles)))); + + WireMock.stubFor( + WireMock.get( + WireMock.urlMatching( + String.format("/auth/admin/realms/%s/users/1/role-mappings/realm", realm))) + .inScenario("test") + .whenScenarioStateIs(Scenario.STARTED) + .willReturn( + WireMock.aResponse() + .withHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE) + .withBody(objectMapper.writeValueAsString(keycloakInitialAssignedRoles)))); + + WireMock.stubFor( + WireMock.delete( + WireMock.urlMatching( + String.format("/auth/admin/realms/%s/users/1/role-mappings/realm", realm))) + .inScenario("test") + .whenScenarioStateIs(Scenario.STARTED) + .withRequestBody( + WireMock.equalTo(objectMapper.writeValueAsString(keycloakRolesToRemove))) + .willReturn(WireMock.aResponse().withStatus(HttpStatus.NO_CONTENT.value()))); + + WireMock.stubFor( + WireMock.post( + WireMock.urlMatching( + String.format("/auth/admin/realms/%s/users/1/role-mappings/realm", realm))) + .inScenario("test") + .whenScenarioStateIs(Scenario.STARTED) + .withRequestBody(WireMock.equalTo(objectMapper.writeValueAsString(keycloakRolesToAdd))) + .willReturn(WireMock.aResponse().withStatus(HttpStatus.NO_CONTENT.value())) + .willSetStateTo("rolesUpdated")); + + WireMock.stubFor( + WireMock.get( + WireMock.urlMatching( + String.format("/auth/admin/realms/%s/users/1/role-mappings/realm", realm))) + .inScenario("test") + .whenScenarioStateIs("rolesUpdated") + .willReturn( + WireMock.aResponse() + .withHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE) + .withBody(objectMapper.writeValueAsString(keycloakUpdatedAssignedRoles)))); + + final RoleApiDto roleToAssign = new RoleApiDto().id("1").name("role1"); + final List rolesToAssign = List.of(roleToAssign); + + final RoleListResponseApiDto response = + requestSpecification() + .given() + .accept(MediaType.APPLICATION_JSON_VALUE) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .header(new Header("X-Request-Id", "addf6005-3075-4c80-b7bc-2c70b7d42b57")) + .body(rolesToAssign) + .when() + .put("/users/1/roles") + .then() + .statusCode(HttpStatus.OK.value()) + .extract() + .body() + .as(RoleListResponseApiDto.class); + + final RoleApiDto role = new RoleApiDto().id("1").name("role1"); + + assertThat(response).isNotNull(); + assertThat(response.getTotalCount()).isEqualTo(response.getItems().size()); + assertThat(response.getItems()).containsExactly(role); + } + + @Test + void listOfAssignedRolesCanBeCleared() throws Exception { + final RoleKeycloakDto keycloakRole1 = new RoleKeycloakDto().id("1").name("role1"); + final RoleKeycloakDto keycloakRole2 = new RoleKeycloakDto().id("2").name("role2"); + + final List keycloakAvailableRoles = List.of(keycloakRole1, keycloakRole2); + final List keycloakAssignedRoles = List.empty(); + final List keycloakRolesToRemove = List.of(keycloakRole1, keycloakRole2); + + WireMock.stubFor( + WireMock.get( + WireMock.urlMatching( + String.format( + "/auth/admin/realms/%s/users/1/role-mappings/realm/available", realm))) + .willReturn( + WireMock.aResponse() + .withHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE) + .withBody(objectMapper.writeValueAsString(keycloakAvailableRoles)))); + + WireMock.stubFor( + WireMock.delete( + WireMock.urlMatching( + String.format("/auth/admin/realms/%s/users/1/role-mappings/realm", realm))) + .withRequestBody( + WireMock.equalTo(objectMapper.writeValueAsString(keycloakRolesToRemove))) + .willReturn(WireMock.aResponse().withStatus(HttpStatus.NO_CONTENT.value()))); + + WireMock.stubFor( + WireMock.get( + WireMock.urlMatching( + String.format("/auth/admin/realms/%s/users/1/role-mappings/realm", realm))) + .willReturn( + WireMock.aResponse() + .withHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE) + .withBody(objectMapper.writeValueAsString(keycloakAssignedRoles)))); + + final List rolesToAssign = List.empty(); + + final RoleListResponseApiDto response = + requestSpecification() + .given() + .accept(MediaType.APPLICATION_JSON_VALUE) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .header(new Header("X-Request-Id", "addf6005-3075-4c80-b7bc-2c70b7d42b57")) + .body(rolesToAssign) + .when() + .put("/users/1/roles") + .then() + .statusCode(HttpStatus.OK.value()) + .extract() + .body() + .as(RoleListResponseApiDto.class); + + assertThat(response).isNotNull(); + assertThat(response.getTotalCount()).isEqualTo(response.getItems().size()); + assertThat(response.getItems()).isEmpty(); + } + + @Test + void listOfAssignedRolesCanNotBeUpdatedWhenPreviousAssignedRolesAreEmpty() throws Exception { + final ErrorResponseKeycloakDto keycloakErrorResponse = + new ErrorResponseKeycloakDto().errorMessage("Some error message"); + + final RoleKeycloakDto keycloakRole1 = new RoleKeycloakDto().id("1").name("role1"); + final RoleKeycloakDto keycloakRole2 = new RoleKeycloakDto().id("2").name("role2"); + + final List keycloakAvailableRoles = List.of(keycloakRole1, keycloakRole2); + final List keycloakInitialAssignedRoles = List.of(); + + final List keycloakRolesToAdd = List.of(keycloakRole1); + + WireMock.stubFor( + WireMock.get( + WireMock.urlMatching( + String.format( + "/auth/admin/realms/%s/users/1/role-mappings/realm/available", realm))) + .inScenario("test") + .whenScenarioStateIs(Scenario.STARTED) + .willReturn( + WireMock.aResponse() + .withHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE) + .withBody(objectMapper.writeValueAsString(keycloakAvailableRoles)))); + + WireMock.stubFor( + WireMock.get( + WireMock.urlMatching( + String.format("/auth/admin/realms/%s/users/1/role-mappings/realm", realm))) + .inScenario("test") + .whenScenarioStateIs(Scenario.STARTED) + .willReturn( + WireMock.aResponse() + .withHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE) + .withBody(objectMapper.writeValueAsString(keycloakInitialAssignedRoles)))); + + WireMock.stubFor( + WireMock.post( + WireMock.urlMatching( + String.format("/auth/admin/realms/%s/users/1/role-mappings/realm", realm))) + .inScenario("test") + .whenScenarioStateIs(Scenario.STARTED) + .withRequestBody(WireMock.equalTo(objectMapper.writeValueAsString(keycloakRolesToAdd))) + .willReturn(WireMock.aResponse().withStatus(HttpStatus.NO_CONTENT.value())) + .willSetStateTo("rolesUpdated")); + + WireMock.stubFor( + WireMock.get( + WireMock.urlMatching( + String.format("/auth/admin/realms/%s/users/1/role-mappings/realm", realm))) + .inScenario("test") + .whenScenarioStateIs("rolesUpdated") + .willReturn( + WireMock.aResponse() + .withHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE) + .withStatus(400) + .withBody(objectMapper.writeValueAsString(keycloakErrorResponse)))); + + final RoleApiDto roleToAssign = new RoleApiDto().id("1").name("role1"); + final List rolesToAssign = List.of(roleToAssign); + + ProblemApiDto response = + requestSpecification() + .given() + .accept(MediaType.APPLICATION_JSON_VALUE) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .header(new Header("X-Request-Id", "addf6005-3075-4c80-b7bc-2c70b7d42b57")) + .body(rolesToAssign) + .when() + .put("/users/1/roles") + .then() + .statusCode(HttpStatus.BAD_GATEWAY.value()) + .extract() + .body() + .as(ProblemApiDto.class); + + assertThat(response).isNotNull(); + assertThat(response.getTitle()).isEqualTo(HttpStatus.BAD_REQUEST.toString()); + assertThat(response.getStatus()).isEqualTo(HttpStatus.BAD_GATEWAY.value()); + assertThat(response.getDownstreamSystem()) + .isEqualTo(ProblemApiDto.DownstreamSystemEnum.KEYCLOAK); + } + + @Test + void listOfAssignedRolesCanNotBeUpdatedWhenPreviousAssignedRolesAreNotEmpty() throws Exception { + final ErrorResponseKeycloakDto keycloakErrorResponse = + new ErrorResponseKeycloakDto().errorMessage("Some error message"); + + final RoleKeycloakDto keycloakRole1 = new RoleKeycloakDto().id("1").name("role1"); + final RoleKeycloakDto keycloakRole2 = new RoleKeycloakDto().id("2").name("role2"); + final RoleKeycloakDto keycloakRole3 = new RoleKeycloakDto().id("3").name("role3"); + + final List keycloakAvailableRoles = + List.of(keycloakRole1, keycloakRole2, keycloakRole3); + final List keycloakUpdatedAssignedRoles = List.of(keycloakRole1); + + final List keycloakRolesToRemove = List.of(keycloakRole1, keycloakRole2); + final List keycloakRolesToAdd = List.of(keycloakRole1); + + WireMock.stubFor( + WireMock.get( + WireMock.urlMatching( + String.format( + "/auth/admin/realms/%s/users/1/role-mappings/realm/available", realm))) + .inScenario("test") + .whenScenarioStateIs(Scenario.STARTED) + .willReturn( + WireMock.aResponse() + .withHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE) + .withBody(objectMapper.writeValueAsString(keycloakAvailableRoles)))); + + WireMock.stubFor( + WireMock.get( + WireMock.urlMatching( + String.format("/auth/admin/realms/%s/users/1/role-mappings/realm", realm))) + .inScenario("test") + .whenScenarioStateIs(Scenario.STARTED) + .willReturn( + WireMock.aResponse() + .withHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE) + .withStatus(400) + .withBody(objectMapper.writeValueAsString(keycloakErrorResponse)))); + + WireMock.stubFor( + WireMock.delete( + WireMock.urlMatching( + String.format("/auth/admin/realms/%s/users/1/role-mappings/realm", realm))) + .inScenario("test") + .whenScenarioStateIs(Scenario.STARTED) + .withRequestBody( + WireMock.equalTo(objectMapper.writeValueAsString(keycloakRolesToRemove))) + .willReturn(WireMock.aResponse().withStatus(HttpStatus.NO_CONTENT.value()))); + + WireMock.stubFor( + WireMock.post( + WireMock.urlMatching( + String.format("/auth/admin/realms/%s/users/1/role-mappings/realm", realm))) + .inScenario("test") + .whenScenarioStateIs(Scenario.STARTED) + .withRequestBody(WireMock.equalTo(objectMapper.writeValueAsString(keycloakRolesToAdd))) + .willReturn(WireMock.aResponse().withStatus(HttpStatus.NO_CONTENT.value())) + .willSetStateTo("rolesUpdated")); + + WireMock.stubFor( + WireMock.get( + WireMock.urlMatching( + String.format("/auth/admin/realms/%s/users/1/role-mappings/realm", realm))) + .inScenario("test") + .whenScenarioStateIs("rolesUpdated") + .willReturn( + WireMock.aResponse() + .withHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE) + .withBody(objectMapper.writeValueAsString(keycloakUpdatedAssignedRoles)))); + + final RoleApiDto roleToAssign = new RoleApiDto().id("1").name("role1"); + final List rolesToAssign = List.of(roleToAssign); + + final ProblemApiDto response = + requestSpecification() + .given() + .accept(MediaType.APPLICATION_JSON_VALUE) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .header(new Header("X-Request-Id", "addf6005-3075-4c80-b7bc-2c70b7d42b57")) + .body(rolesToAssign) + .when() + .put("/users/1/roles") + .then() + .statusCode(HttpStatus.BAD_GATEWAY.value()) + .extract() + .body() + .as(ProblemApiDto.class); + + assertThat(response).isNotNull(); + assertThat(response.getTitle()).isEqualTo(HttpStatus.BAD_REQUEST.toString()); + assertThat(response.getStatus()).isEqualTo(HttpStatus.BAD_GATEWAY.value()); + assertThat(response.getDownstreamSystem()) + .isEqualTo(ProblemApiDto.DownstreamSystemEnum.KEYCLOAK); + } +} diff --git a/app/src/test/java/org/onap/portal/bff/users/UpdateUserIntegrationTest.java b/app/src/test/java/org/onap/portal/bff/users/UpdateUserIntegrationTest.java new file mode 100644 index 0000000..74f0438 --- /dev/null +++ b/app/src/test/java/org/onap/portal/bff/users/UpdateUserIntegrationTest.java @@ -0,0 +1,134 @@ +/* + * + * Copyright (c) 2022. Deutsche Telekom AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * + * + */ + +package org.onap.portal.bff.users; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.github.tomakehurst.wiremock.client.WireMock; +import io.restassured.http.Header; +import io.vavr.collection.List; +import org.junit.jupiter.api.Test; +import org.onap.portal.bff.BaseIntegrationTest; +import org.onap.portal.bff.openapi.client_portal_keycloak.model.ErrorResponseKeycloakDto; +import org.onap.portal.bff.openapi.client_portal_keycloak.model.RoleKeycloakDto; +import org.onap.portal.bff.openapi.client_portal_keycloak.model.UserKeycloakDto; +import org.onap.portal.bff.openapi.server.model.ProblemApiDto; +import org.onap.portal.bff.openapi.server.model.UpdateUserRequestApiDto; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; + +class UpdateUserIntegrationTest extends BaseIntegrationTest { + + @Test + void userCanBeUpdated() throws Exception { + final UserKeycloakDto keycloakRequest = new UserKeycloakDto().firstName("User1").enabled(false); + mockUpdateUser(keycloakRequest, "1"); + + final UpdateUserRequestApiDto request = + new UpdateUserRequestApiDto().email(null).firstName("User1").lastName(null).enabled(false); + + requestSpecification() + .given() + .contentType(MediaType.APPLICATION_JSON_VALUE) + .header(new Header("X-Request-Id", "addf6005-3075-4c80-b7bc-2c70b7d42b57")) + .body(request) + .when() + .put("/users/1") + .then() + .statusCode(HttpStatus.OK.value()); + } + + @Test + void userCanNotBeUpdated() throws Exception { + final ErrorResponseKeycloakDto keycloakErrorResponse = + new ErrorResponseKeycloakDto().errorMessage("Some error message"); + + final UserKeycloakDto keycloakRequest = new UserKeycloakDto().firstName("User1").enabled(false); + + WireMock.stubFor( + WireMock.put(WireMock.urlMatching(String.format("/auth/admin/realms/%s/users/1", realm))) + .withRequestBody(WireMock.equalTo(objectMapper.writeValueAsString(keycloakRequest))) + .willReturn( + WireMock.aResponse() + .withHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE) + .withStatus(400) + .withBody(objectMapper.writeValueAsString(keycloakErrorResponse)))); + + final UpdateUserRequestApiDto request = + new UpdateUserRequestApiDto().email(null).firstName("User1").lastName(null).enabled(false); + + final ProblemApiDto response = + requestSpecification() + .given() + .contentType(MediaType.APPLICATION_JSON_VALUE) + .header(new Header("X-Request-Id", "addf6005-3075-4c80-b7bc-2c70b7d42b57")) + .body(request) + .when() + .put("/users/1") + .then() + .statusCode(HttpStatus.BAD_GATEWAY.value()) + .extract() + .body() + .as(ProblemApiDto.class); + + assertThat(response).isNotNull(); + assertThat(response.getTitle()).isEqualTo(HttpStatus.BAD_REQUEST.toString()); + assertThat(response.getStatus()).isEqualTo(HttpStatus.BAD_GATEWAY.value()); + assertThat(response.getDownstreamSystem()) + .isEqualTo(ProblemApiDto.DownstreamSystemEnum.KEYCLOAK); + } + + protected void mockUpdateUser(UserKeycloakDto request, String userId) throws Exception { + WireMock.stubFor( + WireMock.put( + WireMock.urlMatching( + String.format("/auth/admin/realms/%s/users/%s", realm, userId))) + .withRequestBody(WireMock.equalTo(objectMapper.writeValueAsString(request))) + .willReturn( + WireMock.aResponse().withHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE))); + } + + protected void mockGetUser(String userId, UserKeycloakDto response) throws Exception { + WireMock.stubFor( + WireMock.get( + WireMock.urlMatching( + String.format("/auth/admin/realms/%s/users/%s", realm, userId))) + .willReturn( + WireMock.aResponse() + .withHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE) + .withBody(objectMapper.writeValueAsString(response)))); + } + + protected void mockAssignedRoles(String userID, List keycloakRoles) + throws JsonProcessingException { + WireMock.stubFor( + WireMock.get( + WireMock.urlMatching( + String.format( + "/auth/admin/realms/%s/users/%s/role-mappings/realm", realm, userID))) + .willReturn( + WireMock.aResponse() + .withHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE) + .withBody(objectMapper.writeValueAsString(keycloakRoles)))); + } +} diff --git a/app/src/test/java/org/onap/portal/bff/users/UpdateUserPasswordIntegrationTest.java b/app/src/test/java/org/onap/portal/bff/users/UpdateUserPasswordIntegrationTest.java new file mode 100644 index 0000000..200ad69 --- /dev/null +++ b/app/src/test/java/org/onap/portal/bff/users/UpdateUserPasswordIntegrationTest.java @@ -0,0 +1,107 @@ +/* + * + * Copyright (c) 2022. Deutsche Telekom AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * + * + */ + +package org.onap.portal.bff.users; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.github.tomakehurst.wiremock.client.WireMock; +import io.restassured.http.Header; +import org.junit.jupiter.api.Test; +import org.onap.portal.bff.BaseIntegrationTest; +import org.onap.portal.bff.openapi.client_portal_keycloak.model.CredentialKeycloakDto; +import org.onap.portal.bff.openapi.client_portal_keycloak.model.ErrorResponseKeycloakDto; +import org.onap.portal.bff.openapi.server.model.ProblemApiDto; +import org.onap.portal.bff.openapi.server.model.UpdateUserPasswordRequestApiDto; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; + +class UpdateUserPasswordIntegrationTest extends BaseIntegrationTest { + + @Test + void userPasswordCanBeUpdated() throws Exception { + final CredentialKeycloakDto keycloakRequest = + new CredentialKeycloakDto().temporary(true).value("pswd"); + + WireMock.stubFor( + WireMock.put( + WireMock.urlMatching( + String.format("/auth/admin/realms/%s/users/1/reset-password", realm))) + .withRequestBody(WireMock.equalTo(objectMapper.writeValueAsString(keycloakRequest))) + .willReturn(WireMock.aResponse().withStatus(204))); + + final UpdateUserPasswordRequestApiDto request = + new UpdateUserPasswordRequestApiDto().temporary(true).value("pswd"); + + requestSpecification() + .given() + .contentType(MediaType.APPLICATION_JSON_VALUE) + .header(new Header("X-Request-Id", "addf6005-3075-4c80-b7bc-2c70b7d42b57")) + .body(request) + .when() + .put("/users/1/password") + .then() + .statusCode(HttpStatus.NO_CONTENT.value()); + } + + @Test + void userPasswordCanNotBeUpdated() throws Exception { + final ErrorResponseKeycloakDto keycloakErrorResponse = + new ErrorResponseKeycloakDto().errorMessage("Some error message"); + + final CredentialKeycloakDto keycloakRequest = + new CredentialKeycloakDto().temporary(true).value("pswd"); + + WireMock.stubFor( + WireMock.put( + WireMock.urlMatching( + String.format("/auth/admin/realms/%s/users/1/reset-password", realm))) + .withRequestBody(WireMock.equalTo(objectMapper.writeValueAsString(keycloakRequest))) + .willReturn( + WireMock.aResponse() + .withHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE) + .withStatus(400) + .withBody(objectMapper.writeValueAsString(keycloakErrorResponse)))); + + final UpdateUserPasswordRequestApiDto request = + new UpdateUserPasswordRequestApiDto().temporary(true).value("pswd"); + + final ProblemApiDto response = + requestSpecification() + .given() + .contentType(MediaType.APPLICATION_JSON_VALUE) + .header(new Header("X-Request-Id", "addf6005-3075-4c80-b7bc-2c70b7d42b57")) + .body(request) + .when() + .put("/users/1/password") + .then() + .statusCode(HttpStatus.BAD_GATEWAY.value()) + .extract() + .body() + .as(ProblemApiDto.class); + + assertThat(response).isNotNull(); + assertThat(response.getTitle()).isEqualTo(HttpStatus.BAD_REQUEST.toString()); + assertThat(response.getStatus()).isEqualTo(HttpStatus.BAD_GATEWAY.value()); + assertThat(response.getDownstreamSystem()) + .isEqualTo(ProblemApiDto.DownstreamSystemEnum.KEYCLOAK); + } +} diff --git a/app/src/test/java/org/onap/portal/bff/utils/SortingChainResolverTest.java b/app/src/test/java/org/onap/portal/bff/utils/SortingChainResolverTest.java new file mode 100644 index 0000000..c12b01f --- /dev/null +++ b/app/src/test/java/org/onap/portal/bff/utils/SortingChainResolverTest.java @@ -0,0 +1,141 @@ +/* + * + * Copyright (c) 2022. Deutsche Telekom AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * + * + */ + +package org.onap.portal.bff.utils; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.vavr.collection.HashMap; +import io.vavr.collection.List; +import io.vavr.control.Option; +import java.util.Comparator; +import lombok.Data; +import lombok.NonNull; +import org.junit.jupiter.api.Test; + +class SortingChainResolverTest { + + @Test + void emptySortIsCorrectlyResolved() { + final SortingChainResolver resolver = + new SortingChainResolver<>(HashMap.of("age", Comparator.comparing(DummyPerson::getAge))); + + final Option> comparatorOption = + resolver.resolve(SortingParser.parse("")); + assertThat(comparatorOption.isEmpty()).isTrue(); + } + + @Test + void sortWithUnknownPropertyIsCorrectlyResolved() { + final SortingChainResolver resolver = + new SortingChainResolver<>(HashMap.of("age", Comparator.comparing(DummyPerson::getAge))); + + final Option> comparatorOption = + resolver.resolve(SortingParser.parse("unknown")); + assertThat(comparatorOption.isEmpty()).isTrue(); + } + + @Test + void sortWithSingleAscendingPropertyIsCorrectlyResolved() { + final SortingChainResolver resolver = + new SortingChainResolver<>(HashMap.of("age", Comparator.comparing(DummyPerson::getAge))); + + final Option> comparatorOption = + resolver.resolve(SortingParser.parse("age")); + assertThat(comparatorOption.isDefined()).isTrue(); + + final List list = + List.of(new DummyPerson("Albert", 10), new DummyPerson("Bernard", 7)); + final List expectedList = + List.of(new DummyPerson("Bernard", 7), new DummyPerson("Albert", 10)); + assertThat(list.sorted(comparatorOption.get())).containsExactlyElementsOf(expectedList); + } + + @Test + void sortWithSingleDescendingPropertyIsCorrectlyResolved() { + final SortingChainResolver resolver = + new SortingChainResolver<>(HashMap.of("age", Comparator.comparing(DummyPerson::getAge))); + + final Option> comparatorOption = + resolver.resolve(SortingParser.parse("-age")); + assertThat(comparatorOption.isDefined()).isTrue(); + + final List list = + List.of(new DummyPerson("Charles", 23), new DummyPerson("Dominick", 31)); + final List expectedList = + List.of(new DummyPerson("Dominick", 31), new DummyPerson("Charles", 23)); + assertThat(list.sorted(comparatorOption.get())).containsExactlyElementsOf(expectedList); + } + + @Test + void sortWithMultiplePropertiesIsCorrectlyResolved() { + final SortingChainResolver resolver = + new SortingChainResolver<>( + HashMap.of("age", Comparator.comparing(DummyPerson::getAge)) + .put("name", Comparator.comparing(DummyPerson::getName))); + + final Option> comparatorOption = + resolver.resolve(SortingParser.parse("age,name")); + assertThat(comparatorOption.isDefined()).isTrue(); + + final List list = + List.of( + new DummyPerson("Harold", 27), + new DummyPerson("Diego", 70), + new DummyPerson("David", 27)); + final List expectedList = + List.of( + new DummyPerson("David", 27), + new DummyPerson("Harold", 27), + new DummyPerson("Diego", 70)); + assertThat(list.sorted(comparatorOption.get())).containsExactlyElementsOf(expectedList); + } + + @Test + void sortWithMultiplePropertiesInDifferentOrderIsCorrectlyResolved() { + final SortingChainResolver resolver = + new SortingChainResolver<>( + HashMap.of("age", Comparator.comparing(DummyPerson::getAge)) + .put("name", Comparator.comparing(DummyPerson::getName))); + + final Option> comparatorOption = + resolver.resolve(SortingParser.parse("name,age")); + assertThat(comparatorOption.isDefined()).isTrue(); + + final List list = + List.of( + new DummyPerson("Harold", 27), + new DummyPerson("Diego", 70), + new DummyPerson("David", 27)); + final List expectedList = + List.of( + new DummyPerson("David", 27), + new DummyPerson("Diego", 70), + new DummyPerson("Harold", 27)); + assertThat(list.sorted(comparatorOption.get())).containsExactlyElementsOf(expectedList); + } + + @Data + private static class DummyPerson { + @NonNull private final String name; + private final int age; + } +} diff --git a/app/src/test/java/org/onap/portal/bff/utils/SortingParserTest.java b/app/src/test/java/org/onap/portal/bff/utils/SortingParserTest.java new file mode 100644 index 0000000..6412a25 --- /dev/null +++ b/app/src/test/java/org/onap/portal/bff/utils/SortingParserTest.java @@ -0,0 +1,50 @@ +/* + * + * Copyright (c) 2022. Deutsche Telekom AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * + * + */ + +package org.onap.portal.bff.utils; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; + +class SortingParserTest { + + @Test + void emptySortIsCorrectlyParsed() { + assertThat(SortingParser.parse("")).isEmpty(); + } + + @Test + void sortIsCorrectlyParsed() { + assertThat(SortingParser.parse("age,-name")) + .containsExactly( + new SortingParser.SortingParam("age", false), + new SortingParser.SortingParam("name", true)); + } + + @Test + void sortWithInvalidPartsIsCorrectlyParsed() { + assertThat(SortingParser.parse("age,,name,-")) + .containsExactly( + new SortingParser.SortingParam("age", false), + new SortingParser.SortingParam("name", false)); + } +} diff --git a/app/src/test/java/org/onap/portal/bff/utils/VersionComparatorTest.java b/app/src/test/java/org/onap/portal/bff/utils/VersionComparatorTest.java new file mode 100644 index 0000000..af99f50 --- /dev/null +++ b/app/src/test/java/org/onap/portal/bff/utils/VersionComparatorTest.java @@ -0,0 +1,41 @@ +/* + * + * Copyright (c) 2022. Deutsche Telekom AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * + * + */ + +package org.onap.portal.bff.utils; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.vavr.collection.List; +import org.junit.jupiter.api.Test; + +class VersionComparatorTest { + + @Test + void versionsAreCorrectlySorted() { + final VersionComparator comparator = new VersionComparator(); + + final List expectedVersions = + List.of("1.0", "1.0.1", "1.1", "1.1.1", "1.2", "1.2.1", "1.10", "2.0"); + final List versions = expectedVersions.shuffle().shuffle(); + + assertThat(versions.sorted(comparator)).containsExactlyElementsOf(expectedVersions); + } +} diff --git a/app/src/test/resources/application-access-control.yml b/app/src/test/resources/application-access-control.yml new file mode 100644 index 0000000..5454a15 --- /dev/null +++ b/app/src/test/resources/application-access-control.yml @@ -0,0 +1,22 @@ +portal-bff.access-control: + ACTIONS_CREATE: [ onap_admin, onap_designer, onap_operator ] + ACTIONS_GET: [ onap_admin, onap_designer, onap_operator ] + ACTIONS_LIST: [ onap_admin, onap_designer, onap_operator ] + ACTIVE_ALARM_LIST: [onap_admin, onap_designer, onap_operator] + KEY_ENCRYPT_BY_USER: [onap_admin, onap_designer, onap_operator] + KEY_ENCRYPT_BY_VALUE: [onap_admin, onap_designer, onap_operator] + PREFERENCES_CREATE: [onap_admin, onap_designer, onap_operator] + PREFERENCES_GET: [onap_admin, onap_designer, onap_operator] + PREFERENCES_UPDATE: [onap_admin, onap_designer, onap_operator] + ROLE_LIST: ["*"] + TILE_GET: [onap_admin, onap_designer, onap_operator] + TILE_LIST: [onap_admin, onap_designer, onap_operator] + USER_CREATE: [onap_admin, onap_designer, onap_operator] + USER_DELETE: [onap_admin, onap_designer, onap_operator] + USER_GET: [onap_admin, onap_designer, onap_operator] + USER_LIST_AVAILABLE_ROLES: [onap_admin, onap_designer, onap_operator] + USER_LIST_ROLES: [onap_admin, onap_designer, onap_operator] + USER_LIST: [onap_admin, onap_designer, onap_operator] + USER_UPDATE_PASSWORD: [onap_admin, onap_designer, onap_operator] + USER_UPDATE_ROLES: [onap_admin, onap_designer, onap_operator] + USER_UPDATE: [onap_admin, onap_designer, onap_operator] diff --git a/app/src/test/resources/application-development.yml b/app/src/test/resources/application-development.yml new file mode 100644 index 0000000..8e97b45 --- /dev/null +++ b/app/src/test/resources/application-development.yml @@ -0,0 +1,33 @@ +logging: + level: + org.springframework.web: TRACE + +spring: + profiles: + include: access-control + security: + oauth2: + client: + provider: + keycloak: + token-uri: http://localhost:${wiremock.server.port}/auth/realms/ONAP/protocol/openid-connect/token + jwk-set-uri: http://localhost:${wiremock.server.port}/auth/realms/ONAP/protocol/openid-connect/certs + registration: + keycloak: + provider: keycloak + client-id: test + client-secret: test + authorization-grant-type: client_credentials + resourceserver: + jwt: + jwk-set-uri: http://localhost:${wiremock.server.port}/auth/realms/ONAP/protocol/openid-connect/certs + jackson: + serialization: + FAIL_ON_EMPTY_BEANS: false + +portal-bff: + realm: ONAP + portal-prefs-url: http://localhost:${wiremock.server.port} + portal-history-url: http://localhost:${wiremock.server.port} + keycloak-url: http://localhost:${wiremock.server.port} + instance-id: PORTAL diff --git a/app/src/test/resources/application.yml b/app/src/test/resources/application.yml new file mode 100644 index 0000000..f9a82d8 --- /dev/null +++ b/app/src/test/resources/application.yml @@ -0,0 +1,34 @@ +logging: + level: + org.springframework.web: TRACE + +spring: + profiles: + include: + - access-control + security: + oauth2: + client: + provider: + keycloak: + token-uri: http://localhost:${wiremock.server.port}/auth/realms/ONAP/protocol/openid-connect/token + jwk-set-uri: http://localhost:${wiremock.server.port}/auth/realms/ONAP/protocol/openid-connect/certs + registration: + keycloak: + provider: keycloak + client-id: test + client-secret: test + authorization-grant-type: client_credentials + resourceserver: + jwt: + jwk-set-uri: http://localhost:${wiremock.server.port}/auth/realms/ONAP/protocol/openid-connect/certs + jackson: + serialization: + FAIL_ON_EMPTY_BEANS: false + +portal-bff: + realm: ONAP + portal-prefs-url: http://localhost:${wiremock.server.port} + portal-history-url: http://localhost:${wiremock.server.port} + keycloak-url: http://localhost:${wiremock.server.port} + instance-id: PORTAL diff --git a/app/src/test/resources/logback-spring.xml b/app/src/test/resources/logback-spring.xml new file mode 100644 index 0000000..45bd7e2 --- /dev/null +++ b/app/src/test/resources/logback-spring.xml @@ -0,0 +1,18 @@ + + + + + + + ${LOGBACK_LEVEL:-info} + + + ${CONSOLE_LOG_PATTERN} + utf8 + + + + + + + \ No newline at end of file diff --git a/app/src/test/resources/preferences/preferencesProperties.json b/app/src/test/resources/preferences/preferencesProperties.json new file mode 100644 index 0000000..dc094ec --- /dev/null +++ b/app/src/test/resources/preferences/preferencesProperties.json @@ -0,0 +1,80 @@ +{ + "dashboard": { + "apps": { + "availableTiles": [ + { + "type": "ALARM_COUNT_TILE", + "displayed": true + }, + { + "type": "KPI_GRAPH_TILE", + "displayed": true + }, + { + "type": "K8S_RESOURCE_STATUS_TILE", + "displayed": true + }, + { + "type": "USER_LAST_ACTION_TILE", + "displayed": true + } + ], + "k8sResourceStatus": "pod", + "kpiSettings": { + "primaryGraph": "erabDropRatio", + "secondaryGraph": "erabDropData", + "expanded": false + }, + "alarmTileGraphEnabled": false, + "lastUserAction": { + "interval": "1H", + "filterType": "ALL" + } + } + }, + "alarms": { + "showEmptyProperties": false + }, + "columns": [ + "id", + "baseType", + "ackState", + "alarmedObjectType", + "sourceSystemId", + "alarmedObject", + "perceivedSeverity", + "specificProblem", + "eventCategory", + "probableCause", + "proposedRepairedActions", + "comment", + "alarmRaisedTime", + "alarmReportingTime" + ], + "columnsOrder": [ + "id", + "baseType", + "ackState", + "alarmedObjectType", + "sourceSystemId", + "alarmedObject", + "perceivedSeverity", + "specificProblem", + "eventCategory", + "probableCause", + "proposedRepairedActions", + "comment", + "alarmRaisedTime", + "alarmReportingTime" + ], + "refreshInterval": "1", + "serviceInstanceSettings": { + "showAll": false + }, + "topologySettings": { + "showAll": false + }, + "treeViewSettings": { + "showAll": false + } +} \ No newline at end of file -- cgit 1.2.3-korg