aboutsummaryrefslogtreecommitdiffstats
path: root/app/src
diff options
context:
space:
mode:
authorFiete Ostkamp <Fiete.Ostkamp@telekom.de>2023-04-14 11:44:19 +0000
committerFiete Ostkamp <Fiete.Ostkamp@telekom.de>2023-04-14 11:44:19 +0000
commitcdc670c5a1c25b0b0ab460b1711a0a42f270b1f3 (patch)
tree41ac6c0e7a52505fd1d0de057df6d5328a853cd0 /app/src
parent1a9b563662e9a9dd1f89e04ce0026e2cc5c4771d (diff)
Upload bff
Issue-ID: PORTAL-1083 Signed-off-by: Fiete Ostkamp <Fiete.Ostkamp@telekom.de> Change-Id: I50f0a2db2dab28354c32c1ebf5a5e22afb0faade
Diffstat (limited to 'app/src')
-rw-r--r--app/src/main/java/org/onap/portal/bff/Application.java36
-rw-r--r--app/src/main/resources/application-access-control.yml23
-rw-r--r--app/src/main/resources/application-development.yml30
-rw-r--r--app/src/main/resources/application-local.yml34
-rw-r--r--app/src/main/resources/application.yml49
-rw-r--r--app/src/main/resources/logback-spring.xml13
-rw-r--r--app/src/test/java/org/onap/portal/bff/ApiDocsIntegrationTest.java40
-rw-r--r--app/src/test/java/org/onap/portal/bff/BaseIntegrationTest.java228
-rw-r--r--app/src/test/java/org/onap/portal/bff/HealthCheckIntegrationTest.java48
-rw-r--r--app/src/test/java/org/onap/portal/bff/TokenGenerator.java123
-rw-r--r--app/src/test/java/org/onap/portal/bff/actions/ActionDto.java39
-rw-r--r--app/src/test/java/org/onap/portal/bff/actions/ActionFixtures.java106
-rw-r--r--app/src/test/java/org/onap/portal/bff/actions/ActionsMocks.java228
-rw-r--r--app/src/test/java/org/onap/portal/bff/actions/CreateActionsIntegrationTest.java85
-rw-r--r--app/src/test/java/org/onap/portal/bff/actions/GetActionsIntegrationTest.java79
-rw-r--r--app/src/test/java/org/onap/portal/bff/actions/ListActionsIntegrationTest.java78
-rw-r--r--app/src/test/java/org/onap/portal/bff/headers/XRequestIdHeaderTest.java76
-rw-r--r--app/src/test/java/org/onap/portal/bff/idtoken/IdTokenExchangeFilterFunctionTest.java89
-rw-r--r--app/src/test/java/org/onap/portal/bff/preferences/CreatePreferencesIntegrationTest.java115
-rw-r--r--app/src/test/java/org/onap/portal/bff/preferences/GetPreferencesIntegrationTest.java77
-rw-r--r--app/src/test/java/org/onap/portal/bff/preferences/PreferencesMocks.java182
-rw-r--r--app/src/test/java/org/onap/portal/bff/preferences/UpdatePreferencesIntegrationTest.java114
-rw-r--r--app/src/test/java/org/onap/portal/bff/roles/ListRealmRolesIntegrationTest.java88
-rw-r--r--app/src/test/java/org/onap/portal/bff/roles/RolesMocks.java57
-rw-r--r--app/src/test/java/org/onap/portal/bff/users/CreateUserIntegrationTest.java308
-rw-r--r--app/src/test/java/org/onap/portal/bff/users/DeleteUserIntegrationTest.java83
-rw-r--r--app/src/test/java/org/onap/portal/bff/users/GetUserDetailIntegrationTest.java126
-rw-r--r--app/src/test/java/org/onap/portal/bff/users/ListAssignedRolesIntegrationTest.java111
-rw-r--r--app/src/test/java/org/onap/portal/bff/users/ListAvailableRolesIntegrationTest.java113
-rw-r--r--app/src/test/java/org/onap/portal/bff/users/ListUsersIntegrationTest.java238
-rw-r--r--app/src/test/java/org/onap/portal/bff/users/UpdateAssignedRolesIntegrationTest.java452
-rw-r--r--app/src/test/java/org/onap/portal/bff/users/UpdateUserIntegrationTest.java134
-rw-r--r--app/src/test/java/org/onap/portal/bff/users/UpdateUserPasswordIntegrationTest.java107
-rw-r--r--app/src/test/java/org/onap/portal/bff/utils/SortingChainResolverTest.java141
-rw-r--r--app/src/test/java/org/onap/portal/bff/utils/SortingParserTest.java50
-rw-r--r--app/src/test/java/org/onap/portal/bff/utils/VersionComparatorTest.java41
-rw-r--r--app/src/test/resources/application-access-control.yml22
-rw-r--r--app/src/test/resources/application-development.yml33
-rw-r--r--app/src/test/resources/application.yml34
-rw-r--r--app/src/test/resources/logback-spring.xml18
-rw-r--r--app/src/test/resources/preferences/preferencesProperties.json80
41 files changed, 4128 insertions, 0 deletions
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 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration scan="true">
+ <appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
+ <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
+ <level>${LOGBACK_LEVEL:-info}</level>
+ </filter>
+ <encoder class="net.logstash.logback.encoder.LogstashEncoder"/>
+ </appender>
+
+ <root level="all">
+ <appender-ref ref="stdout"/>
+ </root>
+</configuration>
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 <code>onap_admin</code> 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<String> 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<String> 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<Integer> page, Option<Integer> pageSize) {
+ return adjustPath(basePath, page, pageSize, None());
+ }
+
+ public static String adjustPath(
+ String basePath, Option<Integer> page, Option<Integer> pageSize, Option<String> 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<String> 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> T getFixture(final String fileName, Class<T> 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<RoleKeycloakDto> 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<RoleKeycloakDto> 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<RoleKeycloakDto> 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<RequiredActionsKeycloakDto> 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<RoleKeycloakDto> 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<RequiredActionsKeycloakDto> 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<RoleKeycloakDto> 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<RoleKeycloakDto> 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<RoleKeycloakDto> 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<RoleKeycloakDto> 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<UserKeycloakDto> 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<RoleKeycloakDto> 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<UserKeycloakDto> 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<Integer> page, Option<Integer> 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<RoleKeycloakDto> keycloakAvailableRoles = List.of(keycloakRole1, keycloakRole2);
+ final List<RoleKeycloakDto> keycloakInitialAssignedRoles = List.of();
+ final List<RoleKeycloakDto> keycloakUpdatedAssignedRoles = List.of(keycloakRole1);
+
+ final List<RoleKeycloakDto> 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<RoleApiDto> 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<RoleKeycloakDto> keycloakAvailableRoles =
+ List.of(keycloakRole1, keycloakRole2, keycloakRole3);
+ final List<RoleKeycloakDto> keycloakInitialAssignedRoles =
+ List.of(keycloakRole1, keycloakRole2);
+ final List<RoleKeycloakDto> keycloakUpdatedAssignedRoles = List.of(keycloakRole1);
+
+ final List<RoleKeycloakDto> keycloakRolesToRemove = List.of(keycloakRole1, keycloakRole2);
+ final List<RoleKeycloakDto> 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<RoleApiDto> 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<RoleKeycloakDto> keycloakAvailableRoles = List.of(keycloakRole1, keycloakRole2);
+ final List<RoleKeycloakDto> keycloakAssignedRoles = List.empty();
+ final List<RoleKeycloakDto> 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<RoleApiDto> 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<RoleKeycloakDto> keycloakAvailableRoles = List.of(keycloakRole1, keycloakRole2);
+ final List<RoleKeycloakDto> keycloakInitialAssignedRoles = List.of();
+
+ final List<RoleKeycloakDto> 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<RoleApiDto> 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<RoleKeycloakDto> keycloakAvailableRoles =
+ List.of(keycloakRole1, keycloakRole2, keycloakRole3);
+ final List<RoleKeycloakDto> keycloakUpdatedAssignedRoles = List.of(keycloakRole1);
+
+ final List<RoleKeycloakDto> keycloakRolesToRemove = List.of(keycloakRole1, keycloakRole2);
+ final List<RoleKeycloakDto> 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<RoleApiDto> 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<RoleKeycloakDto> 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<DummyPerson> resolver =
+ new SortingChainResolver<>(HashMap.of("age", Comparator.comparing(DummyPerson::getAge)));
+
+ final Option<Comparator<DummyPerson>> comparatorOption =
+ resolver.resolve(SortingParser.parse(""));
+ assertThat(comparatorOption.isEmpty()).isTrue();
+ }
+
+ @Test
+ void sortWithUnknownPropertyIsCorrectlyResolved() {
+ final SortingChainResolver<DummyPerson> resolver =
+ new SortingChainResolver<>(HashMap.of("age", Comparator.comparing(DummyPerson::getAge)));
+
+ final Option<Comparator<DummyPerson>> comparatorOption =
+ resolver.resolve(SortingParser.parse("unknown"));
+ assertThat(comparatorOption.isEmpty()).isTrue();
+ }
+
+ @Test
+ void sortWithSingleAscendingPropertyIsCorrectlyResolved() {
+ final SortingChainResolver<DummyPerson> resolver =
+ new SortingChainResolver<>(HashMap.of("age", Comparator.comparing(DummyPerson::getAge)));
+
+ final Option<Comparator<DummyPerson>> comparatorOption =
+ resolver.resolve(SortingParser.parse("age"));
+ assertThat(comparatorOption.isDefined()).isTrue();
+
+ final List<DummyPerson> list =
+ List.of(new DummyPerson("Albert", 10), new DummyPerson("Bernard", 7));
+ final List<DummyPerson> expectedList =
+ List.of(new DummyPerson("Bernard", 7), new DummyPerson("Albert", 10));
+ assertThat(list.sorted(comparatorOption.get())).containsExactlyElementsOf(expectedList);
+ }
+
+ @Test
+ void sortWithSingleDescendingPropertyIsCorrectlyResolved() {
+ final SortingChainResolver<DummyPerson> resolver =
+ new SortingChainResolver<>(HashMap.of("age", Comparator.comparing(DummyPerson::getAge)));
+
+ final Option<Comparator<DummyPerson>> comparatorOption =
+ resolver.resolve(SortingParser.parse("-age"));
+ assertThat(comparatorOption.isDefined()).isTrue();
+
+ final List<DummyPerson> list =
+ List.of(new DummyPerson("Charles", 23), new DummyPerson("Dominick", 31));
+ final List<DummyPerson> expectedList =
+ List.of(new DummyPerson("Dominick", 31), new DummyPerson("Charles", 23));
+ assertThat(list.sorted(comparatorOption.get())).containsExactlyElementsOf(expectedList);
+ }
+
+ @Test
+ void sortWithMultiplePropertiesIsCorrectlyResolved() {
+ final SortingChainResolver<DummyPerson> resolver =
+ new SortingChainResolver<>(
+ HashMap.of("age", Comparator.comparing(DummyPerson::getAge))
+ .put("name", Comparator.comparing(DummyPerson::getName)));
+
+ final Option<Comparator<DummyPerson>> comparatorOption =
+ resolver.resolve(SortingParser.parse("age,name"));
+ assertThat(comparatorOption.isDefined()).isTrue();
+
+ final List<DummyPerson> list =
+ List.of(
+ new DummyPerson("Harold", 27),
+ new DummyPerson("Diego", 70),
+ new DummyPerson("David", 27));
+ final List<DummyPerson> 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<DummyPerson> resolver =
+ new SortingChainResolver<>(
+ HashMap.of("age", Comparator.comparing(DummyPerson::getAge))
+ .put("name", Comparator.comparing(DummyPerson::getName)));
+
+ final Option<Comparator<DummyPerson>> comparatorOption =
+ resolver.resolve(SortingParser.parse("name,age"));
+ assertThat(comparatorOption.isDefined()).isTrue();
+
+ final List<DummyPerson> list =
+ List.of(
+ new DummyPerson("Harold", 27),
+ new DummyPerson("Diego", 70),
+ new DummyPerson("David", 27));
+ final List<DummyPerson> 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<String> expectedVersions =
+ List.of("1.0", "1.0.1", "1.1", "1.1.1", "1.2", "1.2.1", "1.10", "2.0");
+ final List<String> 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 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration scan="true">
+ <include resource="org/springframework/boot/logging/logback/defaults.xml"/>
+
+ <appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
+ <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
+ <level>${LOGBACK_LEVEL:-info}</level>
+ </filter>
+ <encoder>
+ <pattern>${CONSOLE_LOG_PATTERN}</pattern>
+ <charset>utf8</charset>
+ </encoder>
+ </appender>
+
+ <root level="all">
+ <appender-ref ref="stdout"/>
+ </root>
+</configuration> \ 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