refactor(tests): use webTestClient 08/141608/7
authorBen Zelleröhr <ben.zelleroehr@telekom.de>
Wed, 30 Jul 2025 14:34:35 +0000 (16:34 +0200)
committerBen Zelleröhr <ben.zelleroehr@telekom.de>
Fri, 1 Aug 2025 15:06:08 +0000 (17:06 +0200)
Issue-ID: PORTALNG-150
Change-Id: I2c13d9ac187ee6b826eab21303242b600b078445
Signed-off-by: Ben Zelleröhr <ben.zelleroehr@telekom.de>
34 files changed:
Dockerfile
app/build.gradle
app/src/main/java/org/onap/portalng/preferences/PreferencesApplication.java
app/src/main/java/org/onap/portalng/preferences/configuration/BeansConfig.java
app/src/main/java/org/onap/portalng/preferences/configuration/LogInterceptor.java
app/src/main/java/org/onap/portalng/preferences/configuration/PreferencesConfig.java
app/src/main/java/org/onap/portalng/preferences/configuration/SecurityConfig.java
app/src/main/java/org/onap/portalng/preferences/controller/PreferencesController.java
app/src/main/java/org/onap/portalng/preferences/entities/PreferencesDto.java
app/src/main/java/org/onap/portalng/preferences/exception/ProblemException.java
app/src/main/java/org/onap/portalng/preferences/logging/LoggerProperties.java
app/src/main/java/org/onap/portalng/preferences/logging/LoggingHelper.java
app/src/main/java/org/onap/portalng/preferences/logging/ReactiveRequestLoggingFilter.java
app/src/main/java/org/onap/portalng/preferences/logging/WebExchangeUtils.java
app/src/main/java/org/onap/portalng/preferences/repository/PreferencesRepository.java
app/src/main/java/org/onap/portalng/preferences/services/PreferencesService.java
app/src/main/java/org/onap/portalng/preferences/util/IdTokenExchange.java
app/src/main/java/org/onap/portalng/preferences/util/Logger.java
app/src/main/resources/application-local.yml
app/src/main/resources/application.yml
app/src/main/resources/logback-spring.xml [deleted file]
app/src/test/java/org/onap/portalng/preferences/ActuatorIntegrationTest.java [moved from app/src/test/java/org/onap/portalng/preferences/actuator/ActuatorIntegrationTest.java with 60% similarity]
app/src/test/java/org/onap/portalng/preferences/BaseIntegrationTest.java [deleted file]
app/src/test/java/org/onap/portalng/preferences/PreferencesControllerIntegrationTest.java [new file with mode: 0644]
app/src/test/java/org/onap/portalng/preferences/TokenGenerator.java [deleted file]
app/src/test/java/org/onap/portalng/preferences/preferences/PreferencesControllerIntegrationTest.java [deleted file]
app/src/test/resources/application.yml
app/src/test/resources/logback-spring.xml [deleted file]
build.gradle
development/.env
development/config/ONAP-realm.json [moved from development/config/onap-realm.json with 100% similarity]
development/docker-compose.yml
openapi/build.gradle
settings.gradle

index e1ff0cc..b00c46c 100644 (file)
@@ -1,11 +1,11 @@
-FROM eclipse-temurin:17 as builder
+FROM eclipse-temurin:21 as builder
 COPY . ./preferences
 WORKDIR /preferences
 RUN ./gradlew assemble
 
-FROM eclipse-temurin:17-jre-alpine
+FROM eclipse-temurin:21-jre-alpine
 USER nobody
 ARG JAR_FILE=/preferences/app/build/libs/app-*.jar
 COPY --from=builder ${JAR_FILE} app.jar
-EXPOSE 9080
-ENTRYPOINT [ "java","-jar","app.jar" ]
\ No newline at end of file
+EXPOSE 9001
+ENTRYPOINT [ "java","-jar","app.jar" ]
index c0279cd..3a2fd19 100644 (file)
@@ -1,11 +1,12 @@
 plugins {
-       id 'java'
-       id 'idea'
-       id 'application'
-       id 'io.spring.dependency-management'
-       id 'org.springframework.boot'
-       id 'jacoco'
-       id 'com.gorylenko.gradle-git-properties'
+  id 'java'
+  id 'idea'
+  id 'application'
+  id 'io.spring.dependency-management'
+  id 'org.springframework.boot'
+  id 'jacoco'
+  id 'com.gorylenko.gradle-git-properties'
+  id 'com.diffplug.spotless'
 }
 
 def appVersion = getAppVersion()
@@ -13,14 +14,14 @@ group = 'org.onap'
 version = appVersion
 
 springBoot {
-       buildInfo {
-               properties {
-                       artifact = "onap-portal-ng-preferences"
-                       version = appVersion
-                       group = "org.onap.portalng"
-                       name = "Portal-ng user preferences service"
-               }
-       }
+  buildInfo {
+    properties {
+      artifact = "onap-portal-ng-preferences"
+      version = appVersion
+      group = "org.onap.portalng"
+      name = "Portal-ng user preferences service"
+    }
+  }
 }
 
 application {
@@ -28,71 +29,67 @@ application {
 }
 
 configurations {
-       compileOnly {
-               extendsFrom annotationProcessor
-       }
+  compileOnly {
+    extendsFrom annotationProcessor
+  }
 }
 
 repositories {
-       mavenCentral()
-       maven {
-               url = "https://plugins.gradle.org/m2/"
-       }
+  mavenCentral()
+  maven {
+    url = "https://plugins.gradle.org/m2/"
+  }
 }
 
 ext {
   problemVersion = '0.27.1'
-  logstashLogbackVersion = '8.1'
-  springCloudWiremockVersion = '4.1.5'
-  micrometerVersion = '1.0.0'
-  liquibaseCoreVersion = '4.31.1'
+  swaggerAnnotationsVersion = '2.2.34'
 }
 
 dependencies {
-       implementation project(':openapi')
-       implementation 'org.springframework.boot:spring-boot-starter-actuator'
-       implementation 'org.springframework.boot:spring-boot-starter-oauth2-resource-server'
-       implementation 'org.springframework.boot:spring-boot-starter-security'
-       implementation 'org.springframework.boot:spring-boot-starter-webflux'
-       implementation 'org.springframework.boot:spring-boot-starter-validation'
-    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
-       implementation "org.zalando:problem:$problemVersion"
-       implementation "net.logstash.logback:logstash-logback-encoder:$logstashLogbackVersion"
-
-    implementation "org.liquibase:liquibase-core:$liquibaseCoreVersion"
-    implementation 'org.postgresql:postgresql'
-
-       implementation 'io.micrometer:micrometer-tracing'
-       implementation 'io.micrometer:micrometer-tracing-bridge-otel'
-       implementation 'io.opentelemetry:opentelemetry-exporter-zipkin'
-    implementation 'io.micrometer:micrometer-registry-prometheus'
-    implementation 'org.apache.commons:commons-lang3:3.17.0'
-
-       compileOnly 'org.projectlombok:lombok'
-
-       developmentOnly 'org.springframework.boot:spring-boot-devtools'
-
-       annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
-       annotationProcessor 'org.projectlombok:lombok'
-
-       testImplementation 'org.springframework.boot:spring-boot-starter-test'
-       testImplementation 'io.projectreactor:reactor-test'
-       testImplementation 'io.rest-assured:rest-assured'
-       testImplementation "org.springframework.cloud:spring-cloud-contract-wiremock:$springCloudWiremockVersion"
-    testImplementation "org.testcontainers:postgresql"
-       testCompileOnly 'org.projectlombok:lombok'
-       testAnnotationProcessor 'org.projectlombok:lombok'
+  implementation project(':openapi')
+  implementation 'org.springframework.boot:spring-boot-starter-actuator'
+  implementation 'org.springframework.boot:spring-boot-starter-oauth2-resource-server'
+  implementation 'org.springframework.boot:spring-boot-starter-security'
+  implementation 'org.springframework.boot:spring-boot-starter-webflux'
+  implementation 'org.springframework.boot:spring-boot-starter-validation'
+  implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
+  implementation "org.zalando:problem:$problemVersion"
+
+  implementation "org.liquibase:liquibase-core"
+  implementation 'org.postgresql:postgresql'
+
+  implementation 'io.micrometer:micrometer-tracing'
+  implementation 'io.micrometer:micrometer-tracing-bridge-otel'
+  implementation 'io.opentelemetry:opentelemetry-exporter-zipkin'
+  implementation 'io.micrometer:micrometer-registry-prometheus'
+
+  compileOnly 'org.projectlombok:lombok'
+  compileOnly "io.swagger.core.v3:swagger-annotations:$swaggerAnnotationsVersion"
+
+  developmentOnly 'org.springframework.boot:spring-boot-devtools'
+
+  annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
+  annotationProcessor 'org.projectlombok:lombok'
+
+  testImplementation 'org.springframework.boot:spring-boot-starter-test'
+  testImplementation 'org.springframework.security:spring-security-test'
+  testImplementation 'io.projectreactor:reactor-test'
+  testImplementation "org.testcontainers:postgresql"
+  testCompileOnly 'org.projectlombok:lombok'
+  testCompileOnly "io.swagger.core.v3:swagger-annotations:$swaggerAnnotationsVersion"
+  testAnnotationProcessor 'org.projectlombok:lombok'
 }
 
 test {
-       useJUnitPlatform()
-       finalizedBy(jacocoTestReport)
+  useJUnitPlatform()
+  finalizedBy(jacocoTestReport)
 }
 
 jacocoTestReport {
-       reports {
-               xml.required = true
-       }
+  reports {
+    xml.required = true
+  }
 }
 
 // avoid generating X.X.X-plain.jar
@@ -101,22 +98,28 @@ jar {
 }
 
 def String getAppVersion() {
-       Properties versionProperties = getVersionProperties()
-       String major = versionProperties.getProperty('major')
-       String minor = versionProperties.getProperty('minor')
-       String patch = versionProperties.getProperty('patch')
-       return major + '.' + minor + '.' + patch
+  Properties versionProperties = getVersionProperties()
+  String major = versionProperties.getProperty('major')
+  String minor = versionProperties.getProperty('minor')
+  String patch = versionProperties.getProperty('patch')
+  return major + '.' + minor + '.' + patch
 }
 
 def Properties getVersionProperties() {
-       def versionProperties = new Properties()
-       rootProject.file('version.properties').withInputStream {
-               versionProperties.load(it)
-       }
-       return versionProperties
+  def versionProperties = new Properties()
+  rootProject.file('version.properties').withInputStream {
+    versionProperties.load(it)
+  }
+  return versionProperties
 }
 
 gitProperties {
-    // if .git directory is on the same level as the root project
-    dotGitDirectory = project.rootProject.layout.projectDirectory.dir(".git")
+  // if .git directory is on the same level as the root project
+  dotGitDirectory = project.rootProject.layout.projectDirectory.dir(".git")
+}
+
+spotless {
+  java {
+    googleJavaFormat()
+  }
 }
index 1bbdddf..7741952 100644 (file)
@@ -21,8 +21,8 @@
 
 package org.onap.portalng.preferences;
 
-import org.onap.portalng.preferences.logging.LoggerProperties;
 import org.onap.portalng.preferences.configuration.PreferencesConfig;
+import org.onap.portalng.preferences.logging.LoggerProperties;
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
 import org.springframework.boot.context.properties.EnableConfigurationProperties;
@@ -31,8 +31,7 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties
 @EnableConfigurationProperties({PreferencesConfig.class, LoggerProperties.class})
 public class PreferencesApplication {
 
-       public static void main(String[] args) {
-               SpringApplication.run(PreferencesApplication.class, args);
-       }
-
+  public static void main(String[] args) {
+    SpringApplication.run(PreferencesApplication.class, args);
+  }
 }
index f35d43c..0ed8673 100644 (file)
 
 package org.onap.portalng.preferences.configuration;
 
+import java.time.Clock;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 
-import java.time.Clock;
-
 @Configuration
 public class BeansConfig {
   @Bean
index a66c5c4..d42e63b 100644 (file)
@@ -21,6 +21,7 @@
 
 package org.onap.portalng.preferences.configuration;
 
+import java.util.List;
 import org.onap.portalng.preferences.util.Logger;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Component;
@@ -30,12 +31,10 @@ import org.springframework.web.server.WebFilter;
 import org.springframework.web.server.WebFilterChain;
 import reactor.core.publisher.Mono;
 
-import java.util.List;
-
 @Component
 public class LogInterceptor implements WebFilter {
-  public static final String EXCHANGE_CONTEXT_ATTRIBUTE = ServerWebExchangeContextFilter.class.getName()
-      + ".EXCHANGE_CONTEXT";
+  public static final String EXCHANGE_CONTEXT_ATTRIBUTE =
+      ServerWebExchangeContextFilter.class.getName() + ".EXCHANGE_CONTEXT";
 
   @Value("${logger.traceIdHeaderName}")
   public static String X_REQUEST_ID;
@@ -45,13 +44,15 @@ public class LogInterceptor implements WebFilter {
     List<String> xRequestIdList = exchange.getRequest().getHeaders().get(X_REQUEST_ID);
     if (xRequestIdList != null && !xRequestIdList.isEmpty()) {
       String xRequestId = xRequestIdList.get(0);
-      Logger.requestLog(
-          exchange.getRequest().getMethod(), exchange.getRequest().getURI());
+      Logger.requestLog(exchange.getRequest().getMethod(), exchange.getRequest().getURI());
       exchange.getResponse().getHeaders().add(X_REQUEST_ID, xRequestId);
-      exchange.getResponse().beforeCommit(() -> {
-        Logger.responseLog(exchange.getResponse().getStatusCode());
-        return Mono.empty();
-      });
+      exchange
+          .getResponse()
+          .beforeCommit(
+              () -> {
+                Logger.responseLog(exchange.getResponse().getStatusCode());
+                return Mono.empty();
+              });
     }
 
     return chain
index 1394fd5..32ccf1d 100644 (file)
 
 package org.onap.portalng.preferences.configuration;
 
-import org.springframework.boot.context.properties.ConfigurationProperties;
-
 import jakarta.validation.constraints.NotBlank;
-import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.validation.annotation.Validated;
 
-@Data
+@Validated
 @ConfigurationProperties("preferences")
-public class PreferencesConfig {
-
-    @NotBlank
-    private final String realm;
-    
-}
+public record PreferencesConfig(@NotBlank String realm) {}
index 2f86d55..7286e69 100644 (file)
@@ -29,9 +29,7 @@ import org.springframework.security.config.annotation.web.reactive.EnableWebFlux
 import org.springframework.security.config.web.server.ServerHttpSecurity;
 import org.springframework.security.web.server.SecurityWebFilterChain;
 
-/**
- * Configures the access control of the API endpoints.
- */
+/** Configures the access control of the API endpoints. */
 // https://hantsy.github.io/spring-reactive-sample/security/config.html
 @EnableWebFluxSecurity
 @Configuration
@@ -39,14 +37,17 @@ public class SecurityConfig {
 
   @Bean
   public SecurityWebFilterChain springSecurityWebFilterChain(ServerHttpSecurity http) {
-    return http
-        .httpBasic(basic -> basic.disable())
+    return http.httpBasic(basic -> basic.disable())
         .formLogin(login -> login.disable())
         .csrf(csrf -> csrf.disable())
         .cors(Customizer.withDefaults())
-        .authorizeExchange(exchange -> exchange
-            .pathMatchers(HttpMethod.GET, "/actuator/**").permitAll()
-            .anyExchange().authenticated())
+        .authorizeExchange(
+            exchange ->
+                exchange
+                    .pathMatchers(HttpMethod.GET, "/actuator/**")
+                    .permitAll()
+                    .anyExchange()
+                    .authenticated())
         .oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()))
         .build();
   }
index 5241848..6f9b4a1 100644 (file)
@@ -31,7 +31,6 @@ import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
 import org.springframework.web.bind.annotation.RestController;
 import org.springframework.web.server.ServerWebExchange;
-
 import reactor.core.publisher.Mono;
 
 @RestController
@@ -45,37 +44,36 @@ public class PreferencesController implements PreferencesApi {
 
   @Override
   public Mono<ResponseEntity<PreferencesApiDto>> getPreferences(ServerWebExchange exchange) {
-    return IdTokenExchange
-        .extractUserId(exchange)
-        .flatMap(userid -> preferencesService.getPreferences(userid)
-            .map(ResponseEntity::ok))
-        .onErrorResume(ProblemException.class, ex -> {
-          Logger.errorLog("user preferences", null, "preferences");
-          return Mono.error(ex);
-        })
+    return IdTokenExchange.extractUserId(exchange)
+        .flatMap(userid -> preferencesService.getPreferences(userid).map(ResponseEntity::ok))
+        .onErrorResume(
+            ProblemException.class,
+            ex -> {
+              Logger.errorLog("user preferences", null, "preferences");
+              return Mono.error(ex);
+            })
         .onErrorReturn(new ResponseEntity<>(HttpStatus.BAD_REQUEST));
   }
 
   @Override
-  public Mono<ResponseEntity<PreferencesApiDto>> savePreferences(Mono<PreferencesApiDto> preferences,
-      ServerWebExchange exchange) {
-    return IdTokenExchange
-        .extractUserId(exchange)
-        .flatMap(userid -> preferences
-            .flatMap(pref -> preferencesService
-                .savePreferences(userid, pref)))
+  public Mono<ResponseEntity<PreferencesApiDto>> savePreferences(
+      Mono<PreferencesApiDto> preferences, ServerWebExchange exchange) {
+    return IdTokenExchange.extractUserId(exchange)
+        .flatMap(
+            userid -> preferences.flatMap(pref -> preferencesService.savePreferences(userid, pref)))
         .map(ResponseEntity::ok)
-        .onErrorResume(ProblemException.class, ex -> {
-          Logger.errorLog("user preferences", null, "preferences");
-          return Mono.error(ex);
-        })
+        .onErrorResume(
+            ProblemException.class,
+            ex -> {
+              Logger.errorLog("user preferences", null, "preferences");
+              return Mono.error(ex);
+            })
         .onErrorReturn(new ResponseEntity<>(HttpStatus.BAD_REQUEST));
   }
 
   @Override
-  public Mono<ResponseEntity<PreferencesApiDto>> updatePreferences(Mono<PreferencesApiDto> preferences,
-      ServerWebExchange exchange) {
+  public Mono<ResponseEntity<PreferencesApiDto>> updatePreferences(
+      Mono<PreferencesApiDto> preferences, ServerWebExchange exchange) {
     return savePreferences(preferences, exchange);
   }
-
 }
index 390a720..6a8e3a8 100644 (file)
 
 package org.onap.portalng.preferences.entities;
 
-import lombok.Getter;
-import lombok.Setter;
-
-import java.util.Map;
-
-import org.hibernate.annotations.JdbcTypeCode;
-import org.hibernate.type.SqlTypes;
-
 import com.fasterxml.jackson.databind.JsonNode;
-
 import jakarta.persistence.Entity;
 import jakarta.persistence.Id;
 import jakarta.persistence.Table;
+import lombok.Getter;
+import lombok.Setter;
+import org.hibernate.annotations.JdbcTypeCode;
+import org.hibernate.type.SqlTypes;
 
 @Entity
 @Getter
 @Setter
 @Table(name = "preferences")
 public class PreferencesDto {
-  @Id
-  private String userId;
+  @Id private String userId;
 
   @JdbcTypeCode(SqlTypes.JSON)
   private JsonNode properties;
-
 }
-
index da7872f..70eae83 100644 (file)
@@ -21,6 +21,7 @@
 
 package org.onap.portalng.preferences.exception;
 
+import java.net.URI;
 import lombok.AllArgsConstructor;
 import lombok.Builder;
 import lombok.Data;
@@ -31,8 +32,6 @@ import org.zalando.problem.Problem;
 import org.zalando.problem.Status;
 import org.zalando.problem.StatusType;
 
-import java.net.URI;
-
 /** The default preferences exception */
 @Data
 @Builder
@@ -49,5 +48,4 @@ public class ProblemException extends AbstractThrowableProblem {
   @Builder.Default private final String detail = "Please add more details here";
 
   @Builder.Default private final URI instance = null;
-
 }
index 7bcc512..e1a311f 100644 (file)
@@ -1,11 +1,29 @@
+/*
+ *
+ * Copyright (c) 2023. 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.portalng.preferences.logging;
 
 import java.util.List;
-
 import org.springframework.boot.context.properties.ConfigurationProperties;
 
 @ConfigurationProperties("logger")
 public record LoggerProperties(
-    String traceIdHeaderName,
-    Boolean enabled, List<String> excludePaths) {
-}
\ No newline at end of file
+    String traceIdHeaderName, Boolean enabled, List<String> excludePaths) {}
index 0131ec3..1a350f3 100644 (file)
 
 package org.onap.portalng.preferences.logging;
 
+import java.util.Map;
+import java.util.function.BiConsumer;
 import lombok.AccessLevel;
 import lombok.NoArgsConstructor;
 import org.slf4j.Logger;
 import org.slf4j.MDC;
 
-import java.util.Map;
-import java.util.function.BiConsumer;
-
 @NoArgsConstructor(access = AccessLevel.PRIVATE)
 public class LoggingHelper {
   public static void error(
index d230162..48deab8 100644 (file)
@@ -21,6 +21,8 @@
 
 package org.onap.portalng.preferences.logging;
 
+import java.time.Duration;
+import java.time.LocalDateTime;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
@@ -30,9 +32,6 @@ import org.springframework.web.server.WebFilter;
 import org.springframework.web.server.WebFilterChain;
 import reactor.core.publisher.Mono;
 
-import java.time.Duration;
-import java.time.LocalDateTime;
-
 @Slf4j
 @Component
 @RequiredArgsConstructor
@@ -79,7 +78,8 @@ public class ReactiveRequestLoggingFilter implements WebFilter {
     boolean loggingDisabled = loggerProperties.enabled() == null || !loggerProperties.enabled();
 
     boolean urlShouldBeSkipped =
-        WebExchangeUtils.matchUrlsPatternsToPath(loggerProperties.excludePaths(), exchange.getRequest().getPath().value());
+        WebExchangeUtils.matchUrlsPatternsToPath(
+            loggerProperties.excludePaths(), exchange.getRequest().getPath().value());
 
     return loggingDisabled || urlShouldBeSkipped;
   }
index 6a56065..18b75ae 100644 (file)
 
 package org.onap.portalng.preferences.logging;
 
+import java.util.EnumMap;
+import java.util.List;
+import java.util.Map;
 import lombok.AccessLevel;
 import lombok.NoArgsConstructor;
 import org.springframework.util.AntPathMatcher;
 import org.springframework.util.PathMatcher;
 import org.springframework.web.server.ServerWebExchange;
 
-import java.util.EnumMap;
-import java.util.List;
-import java.util.Map;
-
 @NoArgsConstructor(access = AccessLevel.PRIVATE)
 public class WebExchangeUtils {
   private static final String DEFAULT_TRACE_ID = "REQUEST_ID_IS_NOT_SET";
index 537ee7e..69f8ca0 100644 (file)
@@ -23,6 +23,15 @@ package org.onap.portalng.preferences.repository;
 
 import org.onap.portalng.preferences.entities.PreferencesDto;
 import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Modifying;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
 
+@Repository
 public interface PreferencesRepository extends JpaRepository<PreferencesDto, String> {
+  @Modifying
+  @Transactional
+  @Query(value = "TRUNCATE TABLE preferences", nativeQuery = true)
+  void truncateTable();
 }
index 591c05f..86eec37 100644 (file)
 
 package org.onap.portalng.preferences.services;
 
+import com.fasterxml.jackson.databind.ObjectMapper;
+import lombok.RequiredArgsConstructor;
 import org.onap.portalng.preferences.entities.PreferencesDto;
 import org.onap.portalng.preferences.exception.ProblemException;
 import org.onap.portalng.preferences.openapi.model.PreferencesApiDto;
 import org.onap.portalng.preferences.repository.PreferencesRepository;
 import org.onap.portalng.preferences.util.Logger;
-import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
-
-import com.fasterxml.jackson.databind.ObjectMapper;
-
-import lombok.RequiredArgsConstructor;
 import reactor.core.publisher.Mono;
 
 @RequiredArgsConstructor
@@ -43,9 +40,7 @@ public class PreferencesService {
   private final ObjectMapper objectMapper;
 
   public Mono<PreferencesApiDto> getPreferences(String userId) {
-    return Mono.just(repository
-        .findById(userId)
-        .orElse(defaultPreferences()))
+    return Mono.just(repository.findById(userId).orElse(defaultPreferences()))
         .map(this::toPreferences);
   }
 
@@ -57,11 +52,12 @@ public class PreferencesService {
 
     return Mono.just(repository.save(preferencesDto))
         .map(this::toPreferences)
-        .onErrorResume(ProblemException.class, ex -> {
-          Logger.errorLog("user prefrences", userId, "preferences");
-          return Mono.error(ex);
-        });
-
+        .onErrorResume(
+            ProblemException.class,
+            ex -> {
+              Logger.errorLog("user prefrences", userId, "preferences");
+              return Mono.error(ex);
+            });
   }
 
   private PreferencesApiDto toPreferences(PreferencesDto preferencesDto) {
@@ -71,9 +67,8 @@ public class PreferencesService {
   }
 
   /**
-   * Get a Preferences object that is initialised with an empty string.
-   * This is a) for convenience to not handle 404 on the consuming side and
-   * b) for security reasons
+   * Get a Preferences object that is initialised with an empty string. This is a) for convenience
+   * to not handle 404 on the consuming side and b) for security reasons
    *
    * @return PreferencesDto
    */
index 7751374..9f6d207 100644 (file)
@@ -26,26 +26,25 @@ import org.springframework.web.server.ServerWebExchange;
 import reactor.core.publisher.Mono;
 
 /**
- * Represents a function that handles the
- * <a href="https://jwt.io/introduction">JWT</a> identity token.
- * Use this to check if the incoming requests are authorized to call the given
- * endpoint
+ * Represents a function that handles the <a href="https://jwt.io/introduction">JWT</a> identity
+ * token. Use this to check if the incoming requests are authorized to call the given endpoint
  */
-
 public final class IdTokenExchange {
 
   public static final String JWT_CLAIM_USERID = "sub";
 
-  private IdTokenExchange() {
+  private IdTokenExchange() {}
 
-  }
   /**
    * Extract the <code>userId</code> from the given {@link ServerWebExchange}
+   *
    * @param exchange the ServerWebExchange that contains information about the incoming request
    * @return the id of the user
    */
   public static Mono<String> extractUserId(ServerWebExchange exchange) {
-    return exchange.getPrincipal().cast(JwtAuthenticationToken.class)
+    return exchange
+        .getPrincipal()
+        .cast(JwtAuthenticationToken.class)
         .map(auth -> auth.getToken().getClaimAsString(JWT_CLAIM_USERID));
   }
 }
index 546dfae..4d9cfba 100644 (file)
 package org.onap.portalng.preferences.util;
 
 import java.net.URI;
-
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.http.HttpMethod;
 import org.springframework.http.HttpStatusCode;
 
-import lombok.extern.slf4j.Slf4j;
-
 @Slf4j
 public class Logger {
 
-  private Logger() {
-  }
+  private Logger() {}
 
   public static void requestLog(HttpMethod methode, URI path) {
     log.info("Preferences - request - {} {}", methode, path);
@@ -43,12 +40,10 @@ public class Logger {
   }
 
   public static void errorLog(String msg, String id, String app) {
-    log.info(
-        "Preferences - error - {} {} not found in {}", msg, id, app);
+    log.info("Preferences - error - {} {} not found in {}", msg, id, app);
   }
 
-  public static void errorLog(
-      String msg, String id, String app, String errorDetails) {
+  public static void errorLog(String msg, String id, String app, String errorDetails) {
     log.info(
         "Preferences - error - {} {} not found in {} error message: {}",
         msg,
index d5a196a..7c578ce 100644 (file)
@@ -1,19 +1,11 @@
-server:
-    port: 9001
-    address: 0.0.0.0
-
 spring:
-  jackson:
-    serialization:
-      # needed for serializing objects of type object
-      FAIL_ON_EMPTY_BEANS: false
   security:
     oauth2:
       resourceserver:
         jwt:
           jwk-set-uri: http://localhost:8080/realms/ONAP/protocol/openid-connect/certs #Keycloak Endpoint
   datasource:
-    url: jdbc:postgresql://localhost:5441/preferences
+    url: jdbc:postgresql://localhost:5432/preferences
     username: postgres
     password: postgres
   jpa:
@@ -23,16 +15,6 @@ preferences:
     realm: ONAP
 
 management:
-  endpoints:
-    web:
-      exposure:
-        include: "*"
-  info:
-    build:
-      enabled: true
-    env:
-      enabled: true
-    git:
-      enabled: true
-    java:
-      enabled: true
+  zipkin:
+    tracing:
+      endpoint: http://localhost:9411/api/v2/spans
index 695e4cc..f86f190 100644 (file)
@@ -1,6 +1,6 @@
 server:
-    port: 9001
-    address: 0.0.0.0
+  port: 9001
+  address: 0.0.0.0
 
 spring:
   application:
@@ -8,7 +8,7 @@ spring:
   jackson:
     serialization:
       # needed for serializing objects of type object
-      FAIL_ON_EMPTY_BEANS: false
+      fail-on-empty-beans: false
   security:
     oauth2:
       resourceserver:
@@ -34,7 +34,7 @@ spring:
     change-log: "classpath:/db/changelog.xml"
 
 preferences:
-    realm: ${KEYCLOAK_REALM}
+  realm: ${KEYCLOAK_REALM}
 management:
   endpoints:
     web:
@@ -62,3 +62,10 @@ logger:
   enabled: true
   excludePaths:
     - "/actuator/**"
+
+logging:
+  structured:
+    format:
+      console: logstash
+  level:
+    root: info
diff --git a/app/src/main/resources/logback-spring.xml b/app/src/main/resources/logback-spring.xml
deleted file mode 100644 (file)
index 05503bc..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-<?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>
  *
  */
 
-package org.onap.portalng.preferences.actuator;
+package org.onap.portalng.preferences;
 
-import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertEquals;
 
-import org.onap.portalng.preferences.BaseIntegrationTest;
 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;
+import org.springframework.boot.test.context.SpringBootTest;
 
-class ActuatorIntegrationTest extends BaseIntegrationTest {
-    
-    @Autowired private ApplicationAvailability applicationAvailability;
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
+class AcutatorIntegrationTest {
+  @Autowired private ApplicationAvailability applicationAvailability;
 
-    @Test
-    void livenessProbeIsAvailable() {
-      assertThat(applicationAvailability.getLivenessState()).isEqualTo(LivenessState.CORRECT);
-    }
-  
-    @Test
-    void readinessProbeIsAvailable() {
-  
-      assertThat(applicationAvailability.getReadinessState())
-          .isEqualTo(ReadinessState.ACCEPTING_TRAFFIC);
-    }
+  @Test
+  void livenessProbeIsAvailable() {
+    assertEquals(applicationAvailability.getLivenessState(), LivenessState.CORRECT);
+  }
+
+  @Test
+  void readinessProbeIsAvailable() {
+    assertEquals(applicationAvailability.getReadinessState(), ReadinessState.ACCEPTING_TRAFFIC);
+  }
 }
diff --git a/app/src/test/java/org/onap/portalng/preferences/BaseIntegrationTest.java b/app/src/test/java/org/onap/portalng/preferences/BaseIntegrationTest.java
deleted file mode 100644 (file)
index 2a6c35a..0000000
+++ /dev/null
@@ -1,156 +0,0 @@
-/*
- *
- * 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.portalng.preferences;
-
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.github.tomakehurst.wiremock.client.WireMock;
-import com.nimbusds.jose.jwk.JWKSet;
-import org.onap.portalng.preferences.util.IdTokenExchange;
-import org.onap.portalng.preferences.configuration.PreferencesConfig;
-import io.restassured.RestAssured;
-import io.restassured.filter.log.RequestLoggingFilter;
-import io.restassured.filter.log.ResponseLoggingFilter;
-import io.restassured.specification.RequestSpecification;
-import org.junit.jupiter.api.BeforeAll;
-import org.junit.jupiter.api.BeforeEach;
-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.web.server.LocalServerPort;
-import org.springframework.cloud.contract.wiremock.AutoConfigureWireMock;
-import org.springframework.http.HttpHeaders;
-import org.springframework.http.MediaType;
-
-import java.util.List;
-import java.util.UUID;
-
-/** 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 {
-
-  @LocalServerPort protected int port;
-  @Value("${preferences.realm}")
-  protected String realm;
-
-  @Autowired protected ObjectMapper objectMapper;
-  @Autowired private TokenGenerator tokenGenerator;
-  @Autowired protected PreferencesConfig preferencesConfig;
-
-  @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(
-                        "/realms/%s/protocol/openid-connect/certs".formatted(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).sub("test-user").build();
-
-    WireMock.stubFor(
-        WireMock.post(
-                WireMock.urlMatching(
-                        "/realms/%s/protocol/openid-connect/token".formatted(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())));
-  }
-
-    /**
-   * Builds an OAuth2 configuration including the roles, port and realm. This config can be used to
-   * generate OAuth2 access tokens.
-   *
-   * @param sub the userId
-   * @param roles the roles used for RBAC
-   * @return the OAuth2 configuration
-   */
-  protected TokenGenerator.TokenGeneratorConfig getTokenGeneratorConfig(String sub, List<String> roles) {
-    return TokenGenerator.TokenGeneratorConfig.builder()
-        .port(port)
-        .sub(sub)
-        .realm(realm)
-        .roles(roles)
-        .build();
-  }
-
-  /** Get a RequestSpecification that does not have an Identity header. */
-  protected RequestSpecification unauthenticatedRequestSpecification() {
-    return RestAssured.given().port(port);
-  }
-
-  /**
-   * 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.
-   * @return the definition of the incoming request (northbound)
-   */
-  protected RequestSpecification requestSpecification() {
-    final String idToken = tokenGenerator.generateToken(getTokenGeneratorConfig("test-user", List.of("foo")));
-
-    return unauthenticatedRequestSpecification()
-        .auth()
-        .preemptive()
-        .oauth2(idToken)
-        .header(HttpHeaders.AUTHORIZATION, "Bearer " + idToken);
-  }
-
-  /**
-   * 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.
-   * @param userId the userId that should be contained in the incoming request
-   * @return the definition of the incoming request (northbound)
-   */
-  protected RequestSpecification requestSpecification(String userId) {
-    final String idToken = tokenGenerator.generateToken(getTokenGeneratorConfig(userId, List.of("foo")));
-
-    return unauthenticatedRequestSpecification()
-        .auth()
-        .preemptive()
-        .oauth2(idToken)
-        .header(HttpHeaders.AUTHORIZATION, "Bearer " + idToken);
-  }
-}
diff --git a/app/src/test/java/org/onap/portalng/preferences/PreferencesControllerIntegrationTest.java b/app/src/test/java/org/onap/portalng/preferences/PreferencesControllerIntegrationTest.java
new file mode 100644 (file)
index 0000000..5bc13d8
--- /dev/null
@@ -0,0 +1,147 @@
+/*
+ *
+ * Copyright (c) 2025. 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.portalng.preferences;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import java.util.Map;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.onap.portalng.preferences.openapi.model.PreferencesApiDto;
+import org.onap.portalng.preferences.repository.PreferencesRepository;
+import org.onap.portalng.preferences.services.PreferencesService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.context.ApplicationContext;
+import org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers;
+import org.springframework.test.web.reactive.server.WebTestClient;
+
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
+public class PreferencesControllerIntegrationTest {
+
+  @Autowired private WebTestClient webTestClient;
+  @Autowired private ObjectMapper objectMapper;
+
+  @BeforeEach
+  void setup(
+      final ApplicationContext context,
+      @Autowired final PreferencesRepository preferencesRepository) {
+    webTestClient =
+        WebTestClient.bindToApplicationContext(context)
+            .apply(SecurityMockServerConfigurers.springSecurity())
+            .configureClient()
+            .build();
+    preferencesRepository.truncateTable();
+  }
+
+  @Test
+  void testAuthenticatedAccess() {
+    webTestClient
+        .mutateWith(SecurityMockServerConfigurers.mockJwt().jwt(jwt -> jwt.claim("sub", "user")))
+        .get()
+        .uri("/v1/preferences")
+        .exchange()
+        .expectStatus()
+        .isOk();
+  }
+
+  @Test
+  void testUnauthorizedAccess() {
+    webTestClient.get().uri("/v1/preferences").exchange().expectStatus().isUnauthorized();
+  }
+
+  @Test
+  void thatDefaultUserPreferencesCanBeRetrieved() throws Exception {
+    final var prefs = getDefaultPreferencesApiDto();
+    webTestClient
+        .mutateWith(SecurityMockServerConfigurers.mockJwt().jwt(jwt -> jwt.claim("sub", "user")))
+        .get()
+        .uri("/v1/preferences")
+        .exchange()
+        .expectStatus()
+        .isOk()
+        .expectBody()
+        .json(objectMapper.writeValueAsString(prefs));
+  }
+
+  @Test
+  void thatSimpleUserPreferencesCanBeSaved() throws Exception {
+    final var prefs = getSimplePreferencesApiDto();
+    webTestClient
+        .mutateWith(SecurityMockServerConfigurers.mockJwt().jwt(jwt -> jwt.claim("sub", "user")))
+        .post()
+        .uri("/v1/preferences")
+        .bodyValue(prefs)
+        .exchange()
+        .expectStatus()
+        .isOk()
+        .expectBody()
+        .json(objectMapper.writeValueAsString(prefs));
+  }
+
+  @Test
+  void thatSimpleUserPreferencesCanBeUpdated() throws Exception {
+    final var prefs = getSimplePreferencesApiDto();
+    webTestClient
+        .mutateWith(SecurityMockServerConfigurers.mockJwt().jwt(jwt -> jwt.claim("sub", "user")))
+        .put()
+        .uri("/v1/preferences")
+        .bodyValue(prefs)
+        .exchange()
+        .expectStatus()
+        .isOk()
+        .expectBody()
+        .json(objectMapper.writeValueAsString(prefs));
+  }
+
+  @Test
+  void thatComplexUserPreferencesCanBeRetrieved(
+      @Autowired final PreferencesService preferencesService) throws Exception {
+    final var prefs = getComplexPreferencesApiDto();
+    preferencesService.savePreferences("user", prefs);
+    webTestClient
+        .mutateWith(SecurityMockServerConfigurers.mockJwt().jwt(jwt -> jwt.claim("sub", "user")))
+        .get()
+        .uri("/v1/preferences")
+        .exchange()
+        .expectStatus()
+        .isOk()
+        .expectBody()
+        .json(objectMapper.writeValueAsString(prefs));
+  }
+
+  private PreferencesApiDto getDefaultPreferencesApiDto() {
+    return new PreferencesApiDto().properties(null);
+  }
+
+  private PreferencesApiDto getSimplePreferencesApiDto() throws Exception {
+    return new PreferencesApiDto()
+        .properties(objectMapper.readValue("{\"appStarter\":\"appStarterValue\"}", Map.class));
+  }
+
+  private PreferencesApiDto getComplexPreferencesApiDto() throws Exception {
+    return new PreferencesApiDto()
+        .properties(
+            objectMapper.readValue(
+                "{\"appStarter\":\"appStarterValue1\", \"dashboard\":{\"dashboardKey\":\"dashboardValue\"}}",
+                Map.class));
+  }
+}
diff --git a/app/src/test/java/org/onap/portalng/preferences/TokenGenerator.java b/app/src/test/java/org/onap/portalng/preferences/TokenGenerator.java
deleted file mode 100644 (file)
index 246aeb2..0000000
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- *
- * 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.portalng.preferences;
-
-import java.time.Clock;
-import java.time.Duration;
-import java.time.Instant;
-import java.util.Collections;
-import java.util.Date;
-import java.util.List;
-import java.util.UUID;
-
-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 org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Component;
-
-import lombok.Builder;
-import lombok.Getter;
-import lombok.NonNull;
-
-@Component
-public class TokenGenerator {
-
-  private static final String ROLES_CLAIM = "roles";
-  private static final String USERID_CLAIM = "sub";
-
-  private final Clock clock;
-  private final RSAKey jwk;
-  private final JWKSet jwkSet;
-  private final JWSSigner signer;
-
-  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())
-            .claim(USERID_CLAIM, config.getSub())
-            .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 sub;
-
-    @NonNull private final String realm;
-
-    @NonNull @Builder.Default private final Duration expireIn = Duration.ofMinutes(5);
-
-    @Builder.Default private final List<String> roles = Collections.emptyList();
-
-    public String issuer() {
-      return "http://localhost:%d/realms/%s".formatted(port, realm);
-    }
-  }
-}
diff --git a/app/src/test/java/org/onap/portalng/preferences/preferences/PreferencesControllerIntegrationTest.java b/app/src/test/java/org/onap/portalng/preferences/preferences/PreferencesControllerIntegrationTest.java
deleted file mode 100644 (file)
index f2bae1c..0000000
+++ /dev/null
@@ -1,161 +0,0 @@
-/*
- *
- * 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.portalng.preferences.preferences;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-import org.junit.jupiter.api.Test;
-import org.onap.portalng.preferences.BaseIntegrationTest;
-import org.onap.portalng.preferences.openapi.model.PreferencesApiDto;
-import org.onap.portalng.preferences.services.PreferencesService;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.http.HttpStatus;
-import org.springframework.http.MediaType;
-
-import io.restassured.http.ContentType;
-
-class PreferencesControllerIntegrationTest extends BaseIntegrationTest {
-
-  @Autowired
-  PreferencesService preferencesService;
-
-  @Test
-  void thatUserPreferencesCanBeRetrieved() {
-    // First save a user preference before a GET can run
-    PreferencesApiDto expectedResponse = new PreferencesApiDto()
-        .properties("{\"properties\": {\"dashboard\": {\"key1:\": \"value2\"}, \"appStarter\": \"value1\"}}");
-    preferencesService
-        .savePreferences("test-user", expectedResponse)
-        .block();
-
-    PreferencesApiDto actualResponse = requestSpecification("test-user")
-        .given()
-        .accept(MediaType.APPLICATION_JSON_VALUE)
-        .when()
-        .get("/v1/preferences")
-        .then()
-        .statusCode(HttpStatus.OK.value())
-        .extract()
-        .body()
-        .as(PreferencesApiDto.class);
-
-    assertThat(actualResponse).isNotNull();
-    assertThat(actualResponse.getProperties()).isEqualTo(expectedResponse.getProperties());
-  }
-
-  @Test
-  void thatUserPreferencesCanNotBeRetrieved() {
-    unauthenticatedRequestSpecification()
-        .given()
-        .accept(MediaType.APPLICATION_JSON_VALUE)
-        .contentType(ContentType.JSON)
-        .when()
-        .get("/v1/preferences")
-        .then()
-        .statusCode(HttpStatus.UNAUTHORIZED.value());
-  }
-
-  @Test
-  void thatUserPreferencesCanBeSaved() {
-    PreferencesApiDto expectedResponse = new PreferencesApiDto()
-        .properties("""
-            {
-                "properties": { "appStarter": "value1",
-                "dashboard": {"key1:" : "value2"}
-                }\s
-            }\
-            """);
-    PreferencesApiDto actualResponse = requestSpecification()
-        .given()
-        .accept(MediaType.APPLICATION_JSON_VALUE)
-        .contentType(ContentType.JSON)
-        .body(expectedResponse)
-        .when()
-        .post("/v1/preferences")
-        .then()
-        .statusCode(HttpStatus.OK.value())
-        .extract()
-        .body()
-        .as(PreferencesApiDto.class);
-
-    assertThat(actualResponse).isNotNull();
-    assertThat(actualResponse.getProperties()).isEqualTo(expectedResponse.getProperties());
-  }
-
-  @Test
-  void thatUserPreferencesCanBeUpdated() {
-    // First save a user preference before a GET can run
-    PreferencesApiDto initialPreferences = new PreferencesApiDto()
-        .properties("""
-            {
-                "properties": { "appStarter": "value1",
-                "dashboard": {"key1:" : "value2"}
-                }\s
-            }\
-            """);
-    preferencesService
-        .savePreferences("test-user", initialPreferences)
-        .block();
-
-    PreferencesApiDto expectedResponse = new PreferencesApiDto()
-        .properties("""
-            {
-                "properties": { "appStarter": "value3",
-                "dashboard": {"key2:" : "value4"}
-                }\s
-            }\
-            """);
-    PreferencesApiDto actualResponse = requestSpecification("test-user")
-        .given()
-        .accept(MediaType.APPLICATION_JSON_VALUE)
-        .contentType(ContentType.JSON)
-        .body(expectedResponse)
-        .when()
-        .put("/v1/preferences")
-        .then()
-        .statusCode(HttpStatus.OK.value())
-        .extract()
-        .body()
-        .as(PreferencesApiDto.class);
-
-    assertThat(actualResponse).isNotNull();
-    assertThat(actualResponse.getProperties()).isEqualTo(expectedResponse.getProperties());
-  }
-
-  @Test
-  void thatUserPreferencesCanNotBeFound() {
-
-    PreferencesApiDto actualResponse = requestSpecification("test-canNotBeFound")
-        .given()
-        .accept(MediaType.APPLICATION_JSON_VALUE)
-        .when()
-        .get("/v1/preferences")
-        .then()
-        .statusCode(HttpStatus.OK.value())
-        .extract()
-        .body()
-        .as(PreferencesApiDto.class);
-
-    assertThat(actualResponse).isNotNull();
-    assertThat(actualResponse.getProperties()).isNull();
-  }
-}
index a0e639a..4a40a8f 100644 (file)
@@ -3,17 +3,20 @@ server:
   address: 0.0.0.0
 
 spring:
+  application:
+    name: preferences-test
   jackson:
     serialization:
       # needed for serializing objects of type object
-      FAIL_ON_EMPTY_BEANS: false
+      fail-on-empty-beans: false
   security:
     oauth2:
       resourceserver:
         jwt:
-          jwk-set-uri: http://localhost:${wiremock.server.port}/realms/ONAP/protocol/openid-connect/certs #Keycloak Endpoint
+         # this value needs to be set for SpringBoot Context, but the tests us mockJwt so it doesn't need to be a valid address
+          jwk-set-uri: http://localhost
   datasource:
-    url: jdbc:tc:postgresql:16:///preferences
+    url: jdbc:tc:postgresql:///preferences
     username: postgres
     password: postgres
   jpa:
@@ -34,7 +37,6 @@ spring:
 
 preferences:
   realm: ONAP
-
 management:
   endpoints:
     web:
@@ -49,9 +51,18 @@ management:
       enabled: true
     java:
       enabled: true
+  tracing:
+    enabled: false
 
 logger:
-  traceIdHeaderName: ${TRACE_ID_HEADER_NAME}
+  traceIdHeaderName: x-b3-traceid
   enabled: true
   excludePaths:
     - "/actuator/**"
+
+logging:
+  structured:
+    format:
+      console: logstash
+  level:
+    root: info
diff --git a/app/src/test/resources/logback-spring.xml b/app/src/test/resources/logback-spring.xml
deleted file mode 100644 (file)
index 05503bc..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-<?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>
index 9a90e43..899b991 100644 (file)
@@ -1,6 +1,6 @@
 // this build.gradle is mainly here to satisfy the Jenkins gradle plugin
 allprojects {
   repositories {
-         mavenCentral()
+    mavenCentral()
   }
 }
index 87c0bbb..5d7e42c 100644 (file)
@@ -1,15 +1,12 @@
 KEYCLOAK_IMAGE=quay.io/keycloak/keycloak
-KEYCLOAK_VERSION=22.04
+KEYCLOAK_VERSION=26.3
 KEYCLOAK_USER=admin
-KEYCLOAK_PASSWORD=password
-KEYCLOAK_DB=keycloak
-KEYCLOAK_DB_USER=keycloak
-KEYCLOAK_DB_PASSWORD=password
-POSTGRES_IMAGE=postgres
-POSTGRES_VERSION=15rc1
-MONGO_IMAGE=mongo
-MONGO_VERSION=latest
-MONGO_USER=root
-MONGO_PASSWORD=password
+KEYCLOAK_PASSWORD=admin
 
+POSTGRES_IMAGE=docker.io/postgres
+POSTGRES_VERSION=16
+POSTGRES_USER=postgres
+POSTGRES_PASSWORD=postgres
 
+JAEGER_IMAGE=cr.jaegertracing.io/jaegertracing/jaeger
+JAEGER_VERSION=2.8.0
index b08f7d5..08fff78 100644 (file)
@@ -1,40 +1,27 @@
-version: '3'
-
-volumes:
-  postgres_data:
-      driver: local
-
 services:
-  postgres:
-      image: "${POSTGRES_IMAGE}:${POSTGRES_VERSION}"
-      volumes:
-        - postgres_data:/var/lib/postgresql/data
-      environment:
-        POSTGRES_DB: ${KEYCLOAK_DB}
-        POSTGRES_USER: ${KEYCLOAK_DB_USER}
-        POSTGRES_PASSWORD: ${KEYCLOAK_DB_PASSWORD}
   keycloak:
-      image: "${KEYCLOAK_IMAGE}:${KEYCLOAK_VERSION}"
-      environment:
-        DB_VENDOR: POSTGRES
-        DB_ADDR: postgres
-        DB_DATABASE: ${KEYCLOAK_DB}
-        DB_USER: ${KEYCLOAK_DB_USER}
-        DB_SCHEMA: public
-        DB_PASSWORD: ${KEYCLOAK_DB_PASSWORD}
-        KEYCLOAK_USER: ${KEYCLOAK_USER}
-        KEYCLOAK_PASSWORD: ${KEYCLOAK_PASSWORD}
-        KEYCLOAK_IMPORT: /config/onap-realm.json
-      ports:
-        - 8080:8080
-      volumes:
-        - ./config:/config
-      depends_on:
-        - postgres
-  mongo:
-    image: "${MONGO_IMAGE}:${MONGO_VERSION}"
+    command: start-dev --import-realm
+    image: "${KEYCLOAK_IMAGE}:${KEYCLOAK_VERSION}"
+    environment:
+      KC_BOOTSTRAP_ADMIN_USERNAME: ${KEYCLOAK_USER}
+      KC_BOOTSTRAP_ADMIN_PASSWORD: ${KEYCLOAK_PASSWORD}
     ports:
-      - 27017:27017
+      - 8080:8080
+    volumes:
+      - ./config:/opt/keycloak/data/import
+  postgres:
+    image: "${POSTGRES_IMAGE}:${POSTGRES_VERSION}"
+    ports:
+      - 5432:5432
     environment:
-      MONGO_INITDB_ROOT_USERNAME: ${MONGO_USER}
-      MONGO_INITDB_ROOT_PASSWORD: ${MONGO_PASSWORD}
+      POSTGRES_USER: ${POSTGRES_USER}
+      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
+      POSTGRES_DB: preferences
+  jaeger:
+    image: "${JAEGER_IMAGE}:${JAEGER_VERSION}"
+    ports:
+      - '9411:9411'
+      - '5778:5778'
+      - '4318:4318'
+      - '4317:4317'
+      - '16686:16686'
index 7c3de0d..42b483c 100644 (file)
@@ -1,52 +1,52 @@
 plugins {
-    id 'java'
-    id 'org.openapi.generator'
+  id 'java'
+  id 'org.openapi.generator'
 }
 
 dependencies {
-    compileOnly 'io.swagger.core.v3:swagger-annotations:2.2.34'
-    compileOnly 'org.springframework.boot:spring-boot-starter-webflux:3.5.4'
-    compileOnly 'jakarta.validation:jakarta.validation-api:3.1.1'
+  compileOnly 'io.swagger.core.v3:swagger-annotations:2.2.34'
+  compileOnly 'org.springframework.boot:spring-boot-starter-webflux:3.5.4'
+  compileOnly 'jakarta.validation:jakarta.validation-api:3.1.1'
 }
 
 // https://github.com/OpenAPITools/openapi-generator/blob/master/modules/openapi-generator-gradle-plugin/README.adoc
 openApiGenerate {
-    generatorName = "spring"
-    library = "spring-boot"
-    inputSpec = "$projectDir/src/main/resources/api/api.yml"
-    outputDir = "$buildDir/openapi"
-    configOptions = [
-        hideGenerationTimestamp: "true",
-               openApiNullable: "false",
-               skipDefaultInterface: "true",
-               dateLibrary: "java8",
-               interfaceOnly: "true",
-               useTags: "true",
-               useOptional: "true",
-               reactive: "true",
-        useSpringBoot3: "true"
-    ]
-    generateApiTests = false
-    generateApiDocumentation = false
-    generateModelTests = false
-    generateModelDocumentation = false
-    invokerPackage = "org.onap.portalng.preferences.openapi"
-    apiPackage = "org.onap.portalng.preferences.openapi.api"
-    modelPackage = "org.onap.portalng.preferences.openapi.model"
-    modelNameSuffix = "ApiDto"
+  generatorName = "spring"
+  library = "spring-boot"
+  inputSpec = "$projectDir/src/main/resources/api/api.yml"
+  outputDir = "$buildDir/openapi"
+  configOptions = [
+    hideGenerationTimestamp: "true",
+    openApiNullable: "false",
+    skipDefaultInterface: "true",
+    dateLibrary: "java8",
+    interfaceOnly: "true",
+    useTags: "true",
+    useOptional: "true",
+    reactive: "true",
+    useSpringBoot3: "true"
+  ]
+  generateApiTests = false
+  generateApiDocumentation = false
+  generateModelTests = false
+  generateModelDocumentation = false
+  invokerPackage = "org.onap.portalng.preferences.openapi"
+  apiPackage = "org.onap.portalng.preferences.openapi.api"
+  modelPackage = "org.onap.portalng.preferences.openapi.model"
+  modelNameSuffix = "ApiDto"
 }
 
 compileJava {
-    dependsOn tasks.openApiGenerate
+  dependsOn tasks.openApiGenerate
 }
 
 sourceSets {
-    main {
-        java {
-            srcDirs += file("$buildDir/openapi/src/main/java")
-        }
+  main {
+    java {
+      srcDirs += file("$buildDir/openapi/src/main/java")
     }
+  }
 }
 tasks.withType(Test).configureEach {
-    useJUnitPlatform()
+  useJUnitPlatform()
 }
index 815dbfc..b45244a 100644 (file)
@@ -1,37 +1,38 @@
 // Centrally declare plugin versions here
 pluginManagement {
-    // https://docs.gradle.org/current/userguide/plugins.html#sec:plugin_version_management
-    plugins {
-        id 'io.spring.dependency-management' version '1.1.7'
-        id 'org.springframework.boot' version '3.4.5'
-        id 'com.github.hierynomus.license' version '0.16.1'
-        id 'com.gorylenko.gradle-git-properties' version '2.5.0'
-        id 'org.openapi.generator' version '7.13.0'
-    }
-    // https://docs.gradle.org/current/userguide/plugins.html#sec:custom_plugin_repositories
-    repositories {
-        mavenCentral()
-        gradlePluginPortal()
-    }
+  // https://docs.gradle.org/current/userguide/plugins.html#sec:plugin_version_management
+  plugins {
+    id 'io.spring.dependency-management' version '1.1.7'
+    id 'org.springframework.boot' version '3.5.4'
+    id 'com.github.hierynomus.license' version '0.16.1'
+    id 'com.gorylenko.gradle-git-properties' version '2.5.2'
+    id 'org.openapi.generator' version '7.14.0'
+    id 'com.diffplug.spotless' version '7.2.1'
+  }
+  // https://docs.gradle.org/current/userguide/plugins.html#sec:custom_plugin_repositories
+  repositories {
+    mavenCentral()
+    gradlePluginPortal()
+  }
 }
 
 // This is a preview feature, enable in the future and remove repositories blocks from sub build.gradles
 // https://docs.gradle.org/current/userguide/declaring_repositories.html#sub:centralized-repository-declaration
 // dependencyResolutionManagement {
-//     maven {
-//             url "${maven_central_url}"
-//             credentials {
-//                     username = "${artifactory_user}"
-//                     password = "${artifactory_password}"
-//             }
-//     }
-//     maven {
-//             url "${gradle_plugins_url}"
-//             credentials {
-//                     username = "${artifactory_user}"
-//                     password = "${artifactory_password}"
-//             }
-//     }
+//   maven {
+//     url "${maven_central_url}"
+//     credentials {
+//       username = "${artifactory_user}"
+//       password = "${artifactory_password}"
+//     }
+//   }
+//   maven {
+//     url "${gradle_plugins_url}"
+//     credentials {
+//       username = "${artifactory_user}"
+//       password = "${artifactory_password}"
+//     }
+//   }
 // }
 
 rootProject.name = 'preferences'