Merge "Improve Test coverage of pmproducer Issue-ID: NONRTRIC-877"
authorJohn Keeney <john.keeney@est.tech>
Tue, 26 Sep 2023 13:45:15 +0000 (13:45 +0000)
committerGerrit Code Review <gerrit@o-ran-sc.org>
Tue, 26 Sep 2023 13:45:15 +0000 (13:45 +0000)
26 files changed:
pmproducer/pom.xml
pmproducer/src/main/java/org/oran/pmproducer/Application.java
pmproducer/src/main/java/org/oran/pmproducer/clients/AsyncRestClient.java
pmproducer/src/main/java/org/oran/pmproducer/clients/AsyncRestClientFactory.java
pmproducer/src/main/java/org/oran/pmproducer/controllers/ErrorResponse.java
pmproducer/src/main/java/org/oran/pmproducer/datastore/FileStore.java
pmproducer/src/main/java/org/oran/pmproducer/datastore/S3ObjectStore.java
pmproducer/src/main/java/org/oran/pmproducer/filter/FilterFactory.java
pmproducer/src/main/java/org/oran/pmproducer/oauth2/OAuthBearerTokenJwt.java
pmproducer/src/main/java/org/oran/pmproducer/oauth2/OAuthKafkaAuthenticateLoginCallbackHandler.java
pmproducer/src/main/java/org/oran/pmproducer/oauth2/SecurityContext.java
pmproducer/src/main/java/org/oran/pmproducer/r1/ProducerInfoTypeInfo.java
pmproducer/src/main/java/org/oran/pmproducer/r1/ProducerJobInfo.java
pmproducer/src/main/java/org/oran/pmproducer/r1/ProducerRegistrationInfo.java
pmproducer/src/main/java/org/oran/pmproducer/repository/Jobs.java
pmproducer/src/main/java/org/oran/pmproducer/tasks/JobDataDistributor.java
pmproducer/src/main/java/org/oran/pmproducer/tasks/ProducerRegstrationTask.java
pmproducer/src/main/java/org/oran/pmproducer/tasks/TopicListeners.java
pmproducer/src/test/java/org/oran/pmproducer/IntegrationWithKafka.java
pmproducer/src/test/java/org/oran/pmproducer/datastore/DataStoreTest.java [new file with mode: 0644]
pmproducer/src/test/java/org/oran/pmproducer/datastore/FileStoreTest.java [new file with mode: 0644]
pmproducer/src/test/java/org/oran/pmproducer/datastore/S3ObjectStoreTest.java [new file with mode: 0644]
pmproducer/src/test/java/org/oran/pmproducer/oauth2/OAuthBearerTokenJwtTest.java [new file with mode: 0644]
pmproducer/src/test/java/org/oran/pmproducer/oauth2/OAuthKafkaAuthenticateLoginCallbackHandlerTest.java [new file with mode: 0644]
pmproducer/src/test/java/org/oran/pmproducer/oauth2/SecurityContextTest.java [new file with mode: 0644]
pmproducer/src/test/resources/org/oran/pmproducer/datastore/file.txt [new file with mode: 0644]

index 4a4d9fd..5f55f0b 100644 (file)
@@ -61,6 +61,7 @@
         <springdoc.version>2.0.2</springdoc.version>
         <springdoc.openapi-ui.version>1.6.14</springdoc.openapi-ui.version>
         <exec.skip>true</exec.skip>
+
     </properties>
     <dependencies>
         <dependency>
             <artifactId>mockito-core</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-inline</artifactId>
+            <scope>test</scope>
+        </dependency>
         <dependency>
             <groupId>io.projectreactor.kafka</groupId>
             <artifactId>reactor-kafka</artifactId>
index 0ffb73f..e39848a 100644 (file)
@@ -23,7 +23,6 @@ package org.oran.pmproducer;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.springframework.boot.ApplicationArguments;
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
 import org.springframework.boot.context.properties.EnableConfigurationProperties;
@@ -51,20 +50,4 @@ public class Application {
         });
     }
 
-    private static void restartApplication() {
-        if (applicationContext == null) {
-            logger.info("Cannot restart in unittest");
-            return;
-        }
-        ApplicationArguments args = applicationContext.getBean(ApplicationArguments.class);
-
-        Thread thread = new Thread(() -> {
-            applicationContext.close();
-            applicationContext = SpringApplication.run(Application.class, args.getSourceArgs());
-        });
-
-        thread.setDaemon(false);
-        thread.start();
-    }
-
 }
index 1fc0620..4031559 100644 (file)
@@ -145,8 +145,7 @@ public class AsyncRestClient {
     }
 
     private void onError(Throwable t) {
-        if (t instanceof WebClientResponseException) {
-            WebClientResponseException e = (WebClientResponseException) t;
+        if (t instanceof WebClientResponseException e) {
             logger.debug("Response error: {}", e.getResponseBodyAsString());
         }
     }
index 7d50fa0..81d3335 100644 (file)
@@ -117,6 +117,7 @@ public class AsyncRestClientFactory {
             }
         }
 
+        @SuppressWarnings("java:S6204")
         private SslContext createSslContextRejectingUntrustedPeers(String trustStorePath, String trustStorePass,
                 KeyManagerFactory keyManager)
                 throws NoSuchAlgorithmException, CertificateException, IOException, KeyStoreException {
index 8411181..7c9bd2e 100644 (file)
@@ -108,11 +108,8 @@ public class ErrorResponse {
     public static ResponseEntity<Object> create(Throwable e, HttpStatus code) {
         if (e instanceof RuntimeException) {
             code = HttpStatus.INTERNAL_SERVER_ERROR;
-        } else if (e instanceof ServiceException) {
-            ServiceException se = (ServiceException) e;
-            if (se.getHttpStatus() != null) {
-                code = se.getHttpStatus();
-            }
+        } else if (e instanceof ServiceException se && se.getHttpStatus() != null) {
+            code = se.getHttpStatus();
         }
         return create(e.toString(), code);
     }
index d2d2ef1..d4fef8a 100644 (file)
@@ -139,7 +139,7 @@ class FileStore implements DataStore {
         return Mono.just("OK");
     }
 
-    private Path path(String name) {
+    public Path path(String name) {
         return Path.of(applicationConfig.getPmFilesPath(), name);
     }
 
index be30969..fd56d62 100644 (file)
@@ -67,6 +67,12 @@ class S3ObjectStore implements DataStore {
         getS3AsynchClient(applicationConfig);
     }
 
+    @SuppressWarnings({"java:S3010", "java:S2209"})
+    public S3ObjectStore(ApplicationConfig applicationConfig, S3AsyncClient s3AsynchClient) {
+        this.applicationConfig = applicationConfig;
+        this.s3AsynchClient = s3AsynchClient;
+    }
+
     private static synchronized S3AsyncClient getS3AsynchClient(ApplicationConfig applicationConfig) {
         if (applicationConfig.isS3Enabled() && s3AsynchClient == null) {
             s3AsynchClient = getS3AsyncClientBuilder(applicationConfig).build();
index 85133de..8af1e7a 100644 (file)
 package org.oran.pmproducer.filter;
 
 import com.google.gson.GsonBuilder;
-
-import java.lang.invoke.MethodHandles;
 import java.util.Collection;
 
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
 public class FilterFactory {
-    private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
     private static com.google.gson.Gson gson = new GsonBuilder().disableHtmlEscaping().create();
 
     private FilterFactory() {}
index 1160657..d00903b 100644 (file)
@@ -1,38 +1,33 @@
-//  ============LICENSE_START===============================================
-//  Copyright (C) 2023 Nordix Foundation. All rights reserved.
-//  ========================================================================
-//  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.
-//  ============LICENSE_END=================================================
-//
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2023 Nordix Foundation
+ * %%
+ * 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.
+ * ========================LICENSE_END===================================
+ */
 
 package org.oran.pmproducer.oauth2;
 
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.databind.JsonMappingException;
-
 import java.util.Base64;
 import java.util.HashSet;
 import java.util.Set;
-
 import lombok.ToString;
-
 import org.apache.kafka.common.security.oauthbearer.OAuthBearerToken;
 import org.oran.pmproducer.exceptions.ServiceException;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 public class OAuthBearerTokenJwt implements OAuthBearerToken {
-    private static final Logger logger = LoggerFactory.getLogger(OAuthBearerTokenJwt.class);
     private static final com.google.gson.Gson gson = new com.google.gson.GsonBuilder().disableHtmlEscaping().create();
 
     private final String jwtTokenRaw;
@@ -47,7 +42,7 @@ public class OAuthBearerTokenJwt implements OAuthBearerToken {
     }
 
     public static OAuthBearerTokenJwt create(String tokenRaw)
-            throws ServiceException, JsonMappingException, JsonProcessingException {
+            throws ServiceException {
         String[] chunks = tokenRaw.split("\\.");
         Base64.Decoder decoder = Base64.getUrlDecoder();
         if (chunks.length < 2) {
index b209da3..e2aa85a 100644 (file)
@@ -1,19 +1,21 @@
-//  ============LICENSE_START===============================================
-//  Copyright (C) 2023 Nordix Foundation. All rights reserved.
-//  ========================================================================
-//  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.
-//  ============LICENSE_END=================================================
-//
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * Copyright (C) 2023 Nordix Foundation
+ *
+ * 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.
+ * ========================LICENSE_END===================================
+ */
 
 package org.oran.pmproducer.oauth2;
 
@@ -51,7 +53,10 @@ public class OAuthKafkaAuthenticateLoginCallbackHandler implements AuthenticateC
     }
 
     @Override
-    public void close() {}
+    public void close() {
+        /*This method intentionally left empty.
+        Close functionality will be implemented later.*/
+    }
 
     @Override
     public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
@@ -59,11 +64,11 @@ public class OAuthKafkaAuthenticateLoginCallbackHandler implements AuthenticateC
         if (!this.isConfigured)
             throw new IllegalStateException("Callback handler not configured");
         for (Callback callback : callbacks) {
-            logger.debug("callback " + callback.toString());
-            if (callback instanceof OAuthBearerTokenCallback) {
-                handleCallback((OAuthBearerTokenCallback) callback);
-            } else if (callback instanceof SaslExtensionsCallback) {
-                handleCallback((SaslExtensionsCallback) callback);
+            logger.debug("callback {}", callback);
+            if (callback instanceof OAuthBearerTokenCallback oAuthBearerTokenCallback) {
+                handleCallback(oAuthBearerTokenCallback);
+            } else if (callback instanceof SaslExtensionsCallback saslExtensionsCallback) {
+                handleCallback(saslExtensionsCallback);
             } else {
                 logger.error("Unsupported callback: {}", callback);
                 throw new UnsupportedCallbackException(callback);
@@ -90,4 +95,8 @@ public class OAuthKafkaAuthenticateLoginCallbackHandler implements AuthenticateC
         }
     }
 
+    public boolean isConfigured() {
+        return isConfigured;
+    }
+
 }
index 936d4a9..5633b37 100644 (file)
@@ -49,10 +49,11 @@ public class SecurityContext {
     private static SecurityContext instance;
 
     @Setter
+    @Getter
     private Path authTokenFilePath;
 
     public SecurityContext(@Value("${app.auth-token-file}") String authTokenFilename) {
-        instance = this;
+        instance = this; //NOSONAR
         if (!authTokenFilename.isEmpty()) {
             this.authTokenFilePath = Path.of(authTokenFilename);
         }
index 500081e..5e8a47a 100644 (file)
@@ -28,11 +28,13 @@ import io.swagger.v3.oas.annotations.media.Schema;
 @Schema(name = "producer_info_type_info", description = "Information for an Information Type")
 public class ProducerInfoTypeInfo {
 
+    @SuppressWarnings("java:S1874")
     @Schema(name = "info_job_data_schema", description = "Json schema for the job data", required = true)
     @SerializedName("info_job_data_schema")
     @JsonProperty(value = "info_job_data_schema", required = true)
     public Object jobDataSchema;
 
+    @SuppressWarnings("java:S1874")
     @Schema(name = "info_type_information", description = "Type specific information for the information type",
             required = true)
     @SerializedName("info_type_information")
index f421e1b..eb9b956 100644 (file)
@@ -29,6 +29,7 @@ import io.swagger.v3.oas.annotations.media.Schema;
         description = "The body of the Information Producer callbacks for Information Job creation and deletion")
 public class ProducerJobInfo {
 
+    @SuppressWarnings("java:S1874")
     @Schema(name = "info_job_identity", description = "Identity of the Information Job", required = true)
     @SerializedName("info_job_identity")
     @JsonProperty("info_job_identity")
index 502abb6..e3fbdcb 100644 (file)
@@ -33,16 +33,19 @@ import lombok.Builder;
 @Schema(name = "producer_registration_info", description = "Information for an Information Producer")
 public class ProducerRegistrationInfo {
 
+    @SuppressWarnings("java:S1874")
     @Schema(name = "supported_info_types", description = "Supported Information Type IDs", required = true)
     @SerializedName("supported_info_types")
     @JsonProperty(value = "supported_info_types", required = true)
     public Collection<String> supportedTypeIds;
 
+    @SuppressWarnings("java:S1874")
     @Schema(name = "info_job_callback_url", description = "callback for Information Job", required = true)
     @SerializedName("info_job_callback_url")
     @JsonProperty(value = "info_job_callback_url", required = true)
     public String jobCallbackUrl;
 
+    @SuppressWarnings("java:S1874")
     @Schema(name = "info_producer_supervision_callback_url", description = "callback for producer supervision",
             required = true)
     @SerializedName("info_producer_supervision_callback_url")
index 2b48b62..6a5e261 100644 (file)
@@ -119,6 +119,7 @@ public class Jobs {
     private final List<Observer> observers = new ArrayList<>();
     private final ApplicationConfig appConfig;
 
+    @SuppressWarnings("java:S1172")
     public Jobs(@Autowired ApplicationConfig applicationConfig, @Autowired SecurityContext securityContext,
             @Autowired ApplicationConfig appConfig) {
         this.appConfig = appConfig;
@@ -137,8 +138,7 @@ public class Jobs {
     }
 
     public void addJob(String id, InfoType type, String owner, String lastUpdated, Parameters parameters)
-            throws ServiceException {
-
+    {
         Job job = new Job(id, type, owner, lastUpdated, parameters, this.appConfig);
         this.put(job);
     }
index b224945..130cbd3 100644 (file)
@@ -79,6 +79,7 @@ public class JobDataDistributor {
             this.consumerFaultCounter = 0;
         }
 
+        @SuppressWarnings("java:S1172")
         public void handleException(Throwable t) {
             ++this.consumerFaultCounter;
         }
index d4b29be..c836c81 100644 (file)
@@ -157,6 +157,7 @@ public class ProducerRegstrationTask {
         }
     }
 
+    @SuppressWarnings("java:S1172")
     private Object jsonSchemaObject(InfoType type) throws IOException, ServiceException {
         final String schemaFile = "/typeSchemaPmData.json";
         return jsonObject(readSchemaFile(schemaFile));
@@ -181,12 +182,6 @@ public class ProducerRegstrationTask {
         }
     }
 
-    private boolean isEqual(ProducerRegistrationInfo a, ProducerRegistrationInfo b) {
-        return a.jobCallbackUrl.equals(b.jobCallbackUrl) //
-                && a.producerSupervisionCallbackUrl.equals(b.producerSupervisionCallbackUrl) //
-                && a.supportedTypeIds.size() == b.supportedTypeIds.size();
-    }
-
     private ProducerRegistrationInfo producerRegistrationInfo() {
         return ProducerRegistrationInfo.builder() //
                 .jobCallbackUrl(baseUrl() + ProducerCallbacksController.JOB_URL) //
index 7b41a6f..581a057 100644 (file)
@@ -43,6 +43,7 @@ import org.springframework.stereotype.Component;
 public class TopicListeners {
     private static final Logger logger = LoggerFactory.getLogger(TopicListeners.class);
 
+    @SuppressWarnings("java:S1700")
     @Getter
     private final Map<String, TopicListener> topicListeners = new HashMap<>(); // Key is typeId
 
index 04b501b..27d3170 100644 (file)
@@ -459,8 +459,9 @@ class IntegrationWithKafka {
         }
 
         String msgString = kafkaReceiver.receivedKafkaOutput.valueAsString();
-        assertThat(msgString).contains("pmCounterNumber0");
-        assertThat(msgString).doesNotContain("pmCounterNumber1");
+        assertThat(msgString)
+            .contains("pmCounterNumber0")
+            .doesNotContain("pmCounterNumber1");
         assertThat(kafkaReceiver.receivedKafkaOutput.getTypeIdFromHeaders()).isEqualTo(PM_TYPE_ID);
         assertThat(kafkaReceiver.receivedKafkaOutput.getSourceNameFromHeaders()).isEqualTo("HTTPST2-0"); // This is from
                                                                                                          // the file
diff --git a/pmproducer/src/test/java/org/oran/pmproducer/datastore/DataStoreTest.java b/pmproducer/src/test/java/org/oran/pmproducer/datastore/DataStoreTest.java
new file mode 100644 (file)
index 0000000..8c787cb
--- /dev/null
@@ -0,0 +1,54 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2023 Nordix Foundation
+ * %%
+ * 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.
+ * ========================LICENSE_END===================================
+ */
+
+package org.oran.pmproducer.datastore;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.when;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.oran.pmproducer.configuration.ApplicationConfig;
+
+@ExtendWith(MockitoExtension.class)
+class DataStoreTest {
+
+    @Mock
+    private ApplicationConfig mockAppConfig;
+
+    @Test
+    void testCreateWithS3Enabled() {
+        when(mockAppConfig.isS3Enabled()).thenReturn(true);
+        when(mockAppConfig.getS3EndpointOverride()).thenReturn("https://dummy-s3-bucket.s3.amazonaws.com");
+        when(mockAppConfig.getS3AccessKeyId()).thenReturn("test-access-key-id");
+        when(mockAppConfig.getS3SecretAccessKey()).thenReturn("test-access-key-secret");
+        DataStore dataStore = DataStore.create(mockAppConfig);
+        assertTrue(dataStore instanceof S3ObjectStore);
+    }
+
+    @Test
+    void testCreateWithS3Disabled() {
+        when(mockAppConfig.isS3Enabled()).thenReturn(false);
+        DataStore dataStore = DataStore.create(mockAppConfig);
+        assertTrue(dataStore instanceof FileStore);
+    }
+}
diff --git a/pmproducer/src/test/java/org/oran/pmproducer/datastore/FileStoreTest.java b/pmproducer/src/test/java/org/oran/pmproducer/datastore/FileStoreTest.java
new file mode 100644 (file)
index 0000000..8196140
--- /dev/null
@@ -0,0 +1,247 @@
+/*-
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2019-2023 Nordix Foundation.
+ * ================================================================================
+ * 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
+ * ============LICENSE_END=========================================================
+ */
+
+package org.oran.pmproducer.datastore;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mockStatic;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.io.IOException;
+import java.nio.file.FileVisitor;
+import java.nio.file.Files;
+import java.nio.file.LinkOption;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.MockedStatic;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.oran.pmproducer.configuration.ApplicationConfig;
+import org.springframework.test.context.ContextConfiguration;
+import reactor.core.publisher.Mono;
+import reactor.test.StepVerifier;
+
+@ContextConfiguration(classes = {FileStore.class})
+@ExtendWith(MockitoExtension.class)
+class FileStoreTest {
+
+    @Mock
+    private ApplicationConfig appConfig;
+
+    private FileStore fileStore;
+
+    @Mock
+    private Path mockPath;
+
+    @BeforeEach
+    void setup() {
+        MockitoAnnotations.initMocks(this);
+        fileStore = new FileStore(appConfig);
+
+        when(appConfig.getPmFilesPath()).thenReturn("/path/to/pm/files");
+    }
+
+    @Test
+    void testListObjects() {
+        when(appConfig.getPmFilesPath()).thenReturn("PM Files Path");
+        fileStore.listObjects(DataStore.Bucket.FILES, "Prefix");
+        verify(appConfig).getPmFilesPath();
+    }
+    @Test
+    void testListObjects3() {
+        when(appConfig.getPmFilesPath()).thenReturn("PM Files Path");
+        fileStore.listObjects(DataStore.Bucket.LOCKS, "Prefix");
+        verify(appConfig).getPmFilesPath();
+    }
+
+    @Test
+    void testListObjects_WithExistingFiles() {
+        List<Path> fileList = new ArrayList<>();
+        fileList.add(Path.of("/path/to/pm/files/file1.txt"));
+        fileList.add(Path.of("/path/to/pm/files/file2.txt"));
+
+        when(appConfig.getPmFilesPath()).thenReturn("/path/to/pm/files");
+
+        // Mock Files.walk() to return the prepared stream
+        try (MockedStatic<Files> filesMockedStatic = mockStatic(Files.class)) {
+            filesMockedStatic.when(() -> Files.walk(any(), anyInt()))
+                .thenReturn(fileList.stream());
+
+            StepVerifier.create(fileStore.listObjects(DataStore.Bucket.FILES, ""))
+                .expectNext("file1.txt")
+                .expectNext("file2.txt")
+                .expectComplete();
+        }
+    }
+    @Test
+    void testReadObject() {
+        when(appConfig.getPmFilesPath()).thenReturn("PM Files Path");
+        fileStore.readObject(DataStore.Bucket.FILES, "foo.txt");
+        verify(appConfig).getPmFilesPath();
+    }
+    @Test
+    void testReadObject2() {
+        when(appConfig.getPmFilesPath()).thenReturn("PM Files Path");
+        fileStore.readObject(DataStore.Bucket.LOCKS, "foo.txt");
+        verify(appConfig).getPmFilesPath();
+    }
+
+    @Test
+    void testReadObject_WithExistingFile() {
+        byte[] content = "Hello, world!".getBytes();
+        Path filePath = Path.of("/path/to/pm/files/test.txt");
+
+        try (MockedStatic<Files> filesMockedStatic = mockStatic(Files.class)) {
+            filesMockedStatic.when(() -> Files.readAllBytes(eq(filePath)))
+                .thenReturn(content);
+
+            StepVerifier.create(fileStore.readObject(DataStore.Bucket.FILES, "test.txt"))
+                .expectNext(content)
+                .verifyComplete();
+        }
+    }
+    @Test
+    void testCreateLock() {
+        when(appConfig.getPmFilesPath()).thenReturn("PM Files Path");
+        fileStore.createLock("Name");
+        verify(appConfig, atLeast(1)).getPmFilesPath();
+    }
+    @Test
+    void testCreateLock3() {
+        when(appConfig.getPmFilesPath()).thenReturn("");
+        fileStore.createLock("/");
+        verify(appConfig, atLeast(1)).getPmFilesPath();
+    }
+    @Test
+    void testDeleteLock() {
+        when(appConfig.getPmFilesPath()).thenReturn("PM Files Path");
+        fileStore.deleteLock("Name");
+        verify(appConfig).getPmFilesPath();
+    }
+    @Test
+    void testDeleteLock2() {
+        when(appConfig.getPmFilesPath()).thenReturn("");
+        fileStore.deleteLock("//");
+        verify(appConfig).getPmFilesPath();
+    }
+    @Test
+    void testDeleteObject() {
+        when(appConfig.getPmFilesPath()).thenReturn("PM Files Path");
+        fileStore.deleteObject(DataStore.Bucket.FILES, "Name");
+        verify(appConfig).getPmFilesPath();
+    }
+    @Test
+    void testDeleteObject2() {
+        when(appConfig.getPmFilesPath()).thenReturn("PM Files Path");
+        fileStore.deleteObject(DataStore.Bucket.LOCKS, "Name");
+        verify(appConfig).getPmFilesPath();
+    }
+
+    @Test
+    void testPath() {
+        when(appConfig.getPmFilesPath()).thenReturn("PM Files Path");
+        fileStore.path("Name");
+        verify(appConfig).getPmFilesPath();
+    }
+
+    @Test
+    void testDeleteBucket() {
+        when(appConfig.getPmFilesPath()).thenReturn("PM Files Path");
+        fileStore.deleteBucket(DataStore.Bucket.FILES);
+        verify(appConfig).getPmFilesPath();
+    }
+    @Test
+    void testDeleteBucket2() throws IOException {
+        try (MockedStatic<Files> mockFiles = mockStatic(Files.class)) {
+            mockFiles.when(() -> Files.walkFileTree(Mockito.<Path>any(), Mockito.<FileVisitor<Path>>any()))
+                .thenReturn(Paths.get(System.getProperty("java.io.tmpdir"), "test.txt"));
+            mockFiles.when(() -> Files.exists(Mockito.<Path>any(), (LinkOption[]) any())).thenReturn(true);
+            when(appConfig.getPmFilesPath()).thenReturn("");
+            fileStore.deleteBucket(DataStore.Bucket.LOCKS);
+            mockFiles.verify(() -> Files.exists(Mockito.<Path>any(), (LinkOption[]) any()));
+            mockFiles.verify(() -> Files.walkFileTree(Mockito.<Path>any(), Mockito.<FileVisitor<Path>>any()));
+            verify(appConfig).getPmFilesPath();
+        }
+    }
+    @Test
+    void testDeleteBucket3() throws IOException {
+        try (MockedStatic<Files> mockFiles = mockStatic(Files.class)) {
+            mockFiles.when(() -> Files.walkFileTree(Mockito.<Path>any(), Mockito.<FileVisitor<Path>>any()))
+                .thenThrow(new IOException("OK"));
+            mockFiles.when(() -> Files.exists(Mockito.<Path>any(), (LinkOption[]) any())).thenReturn(true);
+            when(appConfig.getPmFilesPath()).thenReturn("");
+            fileStore.deleteBucket(DataStore.Bucket.LOCKS);
+            mockFiles.verify(() -> Files.exists(Mockito.<Path>any(), (LinkOption[]) any()));
+            mockFiles.verify(() -> Files.walkFileTree(Mockito.<Path>any(), Mockito.<FileVisitor<Path>>any()));
+            verify(appConfig, atLeast(1)).getPmFilesPath();
+        }
+    }
+
+    @Test
+    void testCreateLock_Success() throws IOException {
+        Path lockPath = Path.of("/path/to/pm/files/locks/lock.txt");
+
+        when(appConfig.getPmFilesPath()).thenReturn("/path/to/pm/files");
+
+        try (MockedStatic<Files> filesMockedStatic = mockStatic(Files.class)) {
+            filesMockedStatic.when(() -> Files.createDirectories(lockPath.getParent()))
+                .thenReturn(lockPath.getParent());
+
+            try (MockedStatic<Path> pathMockedStatic = mockStatic(Path.class)) {
+                filesMockedStatic.when(() -> Files.createFile(any(Path.class))).thenReturn(lockPath);
+
+                String name = "test.txt";
+                String[] pathComponents = {"pmFiles", name};
+
+                when(fileStore.path(Arrays.toString(pathComponents))).thenReturn(mockPath);
+                Path path = fileStore.path(Arrays.toString(pathComponents));
+                assertEquals(mockPath, path);
+            }
+        }
+    }
+
+    @Test
+    void testCopyFileTo_Failure() {
+        // Define dummy values for testing
+        Path from = Paths.get("non-existent-file.txt");
+        String to = "destination-folder";
+
+        // Use StepVerifier to test the method
+        Mono<String> resultMono = fileStore.copyFileTo(from, to);
+
+        StepVerifier.create(resultMono)
+            .expectError(IOException.class)
+            .verify();
+    }
+}
+
diff --git a/pmproducer/src/test/java/org/oran/pmproducer/datastore/S3ObjectStoreTest.java b/pmproducer/src/test/java/org/oran/pmproducer/datastore/S3ObjectStoreTest.java
new file mode 100644 (file)
index 0000000..2e5f7d8
--- /dev/null
@@ -0,0 +1,321 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2023 Nordix Foundation
+ * %%
+ * 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.
+ * ========================LICENSE_END===================================
+ */
+
+package org.oran.pmproducer.datastore;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.net.URISyntaxException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.oran.pmproducer.configuration.ApplicationConfig;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+import reactor.test.StepVerifier;
+import software.amazon.awssdk.core.ResponseBytes;
+import software.amazon.awssdk.core.async.AsyncRequestBody;
+import software.amazon.awssdk.core.async.AsyncResponseTransformer;
+import software.amazon.awssdk.services.s3.S3AsyncClient;
+import software.amazon.awssdk.services.s3.model.CreateBucketRequest;
+import software.amazon.awssdk.services.s3.model.CreateBucketResponse;
+import software.amazon.awssdk.services.s3.model.DeleteBucketRequest;
+import software.amazon.awssdk.services.s3.model.DeleteBucketResponse;
+import software.amazon.awssdk.services.s3.model.DeleteObjectRequest;
+import software.amazon.awssdk.services.s3.model.DeleteObjectResponse;
+import software.amazon.awssdk.services.s3.model.DeleteObjectsRequest;
+import software.amazon.awssdk.services.s3.model.DeleteObjectsResponse;
+import software.amazon.awssdk.services.s3.model.GetObjectRequest;
+import software.amazon.awssdk.services.s3.model.GetObjectResponse;
+import software.amazon.awssdk.services.s3.model.HeadObjectRequest;
+import software.amazon.awssdk.services.s3.model.HeadObjectResponse;
+import software.amazon.awssdk.services.s3.model.ListObjectsRequest;
+import software.amazon.awssdk.services.s3.model.ListObjectsResponse;
+import software.amazon.awssdk.services.s3.model.PutObjectRequest;
+import software.amazon.awssdk.services.s3.model.PutObjectResponse;
+import software.amazon.awssdk.services.s3.model.S3Object;
+
+
+@ExtendWith(MockitoExtension.class)
+class S3ObjectStoreTest {
+
+    @Mock
+    private ApplicationConfig appConfig;
+
+    @Mock
+    private S3AsyncClient s3AsynchClient;
+
+    private S3ObjectStore s3ObjectStore;
+
+    @BeforeEach
+    void setup() {
+        Mockito.lenient().when(appConfig.getS3EndpointOverride()).thenReturn("https://dummy-s3-bucket.s3.amazonaws.com");
+        Mockito.lenient().when(appConfig.getS3AccessKeyId()).thenReturn("test-access-key-id");
+        Mockito.lenient().when(appConfig.getS3SecretAccessKey()).thenReturn("test-access-key-secret");
+
+        Mockito.lenient().when(appConfig.getS3Bucket()).thenReturn("test-bucket");
+        Mockito.lenient().when(appConfig.getS3LocksBucket()).thenReturn("test-lock-bucket");
+        Mockito.lenient().when(appConfig.isS3Enabled()).thenReturn(true);
+
+        s3ObjectStore = new S3ObjectStore(appConfig, s3AsynchClient);
+    }
+
+    @Test
+    void createS3Bucket() {
+        CreateBucketRequest request = CreateBucketRequest.builder()
+            .bucket("test-bucket")
+            .build();
+
+        when(s3AsynchClient.createBucket(any(CreateBucketRequest.class)))
+            .thenReturn(CompletableFuture.completedFuture(CreateBucketResponse.builder().build()));
+
+        Mono<String> result = s3ObjectStore.create(DataStore.Bucket.FILES);
+
+        verify(s3AsynchClient, atLeast(1)).createBucket(any(CreateBucketRequest.class));
+
+        StepVerifier.create(result).expectNext("test-bucket").verifyComplete();
+
+        assertThat(result.block()).isEqualTo("test-bucket");
+    }
+
+    @Test
+    void listObjects() {
+        String prefix = "prefix/";
+
+        ListObjectsResponse response1 = ListObjectsResponse.builder()
+            .contents(createS3Object("object1"))
+            .isTruncated(true)
+            .nextMarker("marker1")
+            .build();
+
+        ListObjectsResponse response2 = ListObjectsResponse.builder()
+            .contents(createS3Object("object2"))
+            .isTruncated(false)
+            .build();
+
+        when(s3AsynchClient.listObjects(any(ListObjectsRequest.class)))
+            .thenReturn(CompletableFuture.completedFuture(response1),
+                CompletableFuture.completedFuture(response2));
+
+        Flux<String> result = s3ObjectStore.listObjects(DataStore.Bucket.FILES, prefix);
+
+        verify(s3AsynchClient, atLeast(1)).listObjects(any(ListObjectsRequest.class));
+
+        StepVerifier.create(result)
+            .expectNext("object1")
+            .expectNext("object2")
+            .verifyComplete();
+
+        // Collect the results into a list
+        List<String> resultList = result.collectList().block();
+
+        assertEquals(Arrays.asList("object1", "object2"), resultList);
+    }
+
+    @Test
+    void testCreateLockWithExistingHead() {
+        HeadObjectResponse headObjectResponse = HeadObjectResponse.builder().build();
+
+        when(s3AsynchClient.headObject(any(HeadObjectRequest.class)))
+            .thenReturn(CompletableFuture.completedFuture(headObjectResponse));
+
+        Mono<Boolean> result = s3ObjectStore.createLock("lockName");
+
+        StepVerifier.create(result)
+            .expectNext(false)
+            .verifyComplete();
+
+        assertThat(result.block()).isFalse();
+    }
+
+    @Test
+    void testCreateLockWithoutExistingHead() {
+        HeadObjectResponse headObjectResponse = null;
+        Mockito.doReturn(CompletableFuture.completedFuture(headObjectResponse))
+            .when(s3AsynchClient)
+            .headObject(any(HeadObjectRequest.class));
+
+        Mono<Boolean> result = s3ObjectStore.createLock("lockName");
+
+        StepVerifier.create(result)
+            .expectComplete()
+            .verify();
+
+        Boolean resultVal = result.block();
+
+        assertThat(resultVal).isNull();
+    }
+
+
+    @Test
+    void deleteLock() {
+        when(s3AsynchClient.deleteObject(any(DeleteObjectRequest.class)))
+            .thenReturn(CompletableFuture.completedFuture(DeleteObjectResponse.builder().build()));
+
+        Mono<Boolean> result = s3ObjectStore.deleteLock("lock-name");
+
+        StepVerifier.create(result)
+            .expectNext(true)
+            .verifyComplete();
+
+        assertThat(result.block()).isTrue();
+    }
+
+    @Test
+    void testDeleteObject() {
+        when(s3AsynchClient.deleteObject(any(DeleteObjectRequest.class)))
+            .thenReturn(CompletableFuture.completedFuture(DeleteObjectResponse.builder().build()));
+
+        Mono<Boolean> result = s3ObjectStore.deleteObject(DataStore.Bucket.LOCKS, "objectName");
+
+        StepVerifier.create(result)
+            .expectNext(true)
+            .verifyComplete();
+
+        assertThat(result.block()).isTrue();
+    }
+
+    @Test
+    void testDeleteBucket_Success() {
+        DeleteBucketRequest request = DeleteBucketRequest.builder() //
+            .bucket("test-bucket")
+            .build();
+
+        Mockito.lenient().when(s3AsynchClient.deleteBucket(any(DeleteBucketRequest.class)))
+            .thenReturn(CompletableFuture.completedFuture(DeleteBucketResponse.builder().build()));
+
+        DeleteObjectsRequest objectRequest = DeleteObjectsRequest.builder() //
+            .bucket("test-bucket")
+            .build();
+
+        Mockito.lenient().when(s3AsynchClient.deleteObjects(any(DeleteObjectsRequest.class)))
+            .thenReturn(CompletableFuture.completedFuture(DeleteObjectsResponse.builder().build()));
+
+        String prefix = "prefix/";
+
+        ListObjectsResponse response1 = ListObjectsResponse.builder()
+            .contents(createS3Object("object1"))
+            .isTruncated(true)
+            .nextMarker("marker1")
+            .build();
+
+        ListObjectsResponse response2 = ListObjectsResponse.builder()
+            .contents(createS3Object("object2"))
+            .isTruncated(false)
+            .build();
+
+        when(s3AsynchClient.listObjects(any(ListObjectsRequest.class)))
+            .thenReturn(CompletableFuture.completedFuture(response1),
+                CompletableFuture.completedFuture(response2));
+
+        Mono<String> result = s3ObjectStore.deleteBucket(DataStore.Bucket.FILES);
+
+        StepVerifier.create(result)
+            .expectNext("NOK")
+            .verifyComplete();
+    }
+
+    @Test
+    void testCopyFileTo_Success() throws URISyntaxException {
+        PutObjectRequest request = PutObjectRequest.builder() //
+            .bucket("test-bucket") //
+            .key("test-access-key-id") //
+            .build();
+
+        when(s3AsynchClient.putObject(any(PutObjectRequest.class), any(AsyncRequestBody.class)))
+            .thenAnswer(invocation -> {
+                CompletableFuture<PutObjectResponse> future = CompletableFuture.completedFuture(
+                    PutObjectResponse.builder().build()
+                );
+                return future;
+            });
+
+        Path testFile = Paths.get(getClass().getResource("/org/oran/pmproducer/datastore/file.txt").toURI());
+
+        Mono<String> result = s3ObjectStore.copyFileTo(testFile, "test-key");
+
+        StepVerifier.create(result)
+            .expectNext("test-key")
+            .verifyComplete();
+    }
+
+    @Test
+    void testReadObject() {
+        // Mock the getObject method to return a CompletableFuture with ResponseBytes
+        when(s3AsynchClient.getObject(any(GetObjectRequest.class), any(AsyncResponseTransformer.class)))
+            .thenAnswer(invocation -> {
+                ResponseBytes<GetObjectResponse> responseBytes = ResponseBytes.fromByteArray(
+                    GetObjectResponse.builder().build(),
+                    "Hello, World!".getBytes(StandardCharsets.UTF_8)
+                );
+                CompletableFuture<ResponseBytes<GetObjectResponse>> future = CompletableFuture.completedFuture(
+                    responseBytes
+                );
+                return future;
+            });
+
+        // Call the method under test
+        Mono<byte[]> result = s3ObjectStore.readObject(DataStore.Bucket.FILES, "test-key");
+
+        byte[] expectedBytes = "Hello, World!".getBytes(StandardCharsets.UTF_8);
+        StepVerifier.create(result)
+            .consumeNextWith(actualBytes -> Assertions.assertArrayEquals(expectedBytes, actualBytes))
+            .verifyComplete();
+    }
+
+    @Test
+    void testPutObject() {
+        // Mock the putObject method to return a CompletableFuture with PutObjectResponse
+        when(s3AsynchClient.putObject(any(PutObjectRequest.class), any(AsyncRequestBody.class)))
+            .thenAnswer(invocation -> {
+                CompletableFuture<PutObjectResponse> future = CompletableFuture.completedFuture(
+                    PutObjectResponse.builder().build()
+                );
+                return future;
+            });
+
+        // Call the method under test
+        Mono<String> result = s3ObjectStore.putObject(DataStore.Bucket.FILES, "test-key", "Hello, World!");
+
+        // Verify the Mono's behavior using StepVerifier
+        StepVerifier.create(result)
+            .expectNext("test-key")
+            .verifyComplete();
+    }
+
+    private S3Object createS3Object(String key) {
+        return S3Object.builder().key(key).build();
+    }
+}
diff --git a/pmproducer/src/test/java/org/oran/pmproducer/oauth2/OAuthBearerTokenJwtTest.java b/pmproducer/src/test/java/org/oran/pmproducer/oauth2/OAuthBearerTokenJwtTest.java
new file mode 100644 (file)
index 0000000..6706824
--- /dev/null
@@ -0,0 +1,111 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2023 Nordix Foundation
+ * %%
+ * 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.
+ * ========================LICENSE_END===================================
+ */
+
+package org.oran.pmproducer.oauth2;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.oran.pmproducer.exceptions.ServiceException;
+import org.springframework.test.context.ContextConfiguration;
+
+@ContextConfiguration(classes = {OAuthBearerTokenJwtTest.class})
+@ExtendWith(MockitoExtension.class)
+class OAuthBearerTokenJwtTest {
+
+    private OAuthBearerTokenJwt token;
+
+    @BeforeEach
+    void setUp() throws ServiceException, JsonProcessingException {
+        String validJwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"; // Replace with a valid JWT token for testing
+        token = OAuthBearerTokenJwt.create(validJwt);
+    }
+
+    @Test
+    void testCreateValidToken() {
+        assertNotNull(token);
+    }
+
+    @Test
+    void testCreateInvalidToken() {
+        assertThrows(ServiceException.class, () -> OAuthBearerTokenJwt.create("invalid_token"));
+    }
+
+    @Test
+    void testTokenValue() {
+        assertEquals("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c", token.value());
+    }
+
+    @Test
+    void testTokenScope() {
+        assertEquals(0, token.scope().size());
+        assertFalse(token.scope().contains(""));
+    }
+
+    @Test
+    void testTokenLifetimeMs() {
+        assertEquals(Long.MAX_VALUE, token.lifetimeMs());
+    }
+
+    @Test
+    void testTokenPrincipalName() {
+        assertEquals("1234567890", token.principalName());
+    }
+
+    @Test
+    void testTokenStartTimeMs() {
+        assertEquals(1516239022L, token.startTimeMs());
+    }
+
+    @Test
+    void testCreateTokenFromInvalidPayload() throws ServiceException {
+        // Create a JWT with an invalid payload (missing fields)
+        String invalidPayload = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9";
+        assertThrows(ServiceException.class, () -> OAuthBearerTokenJwt.create(invalidPayload));
+    }
+
+    @Test
+    void testCreateTokenWithValidPayload() throws ServiceException, JsonProcessingException {
+        // Create a JWT with a valid payload
+        String validPayload = "eyJzdWIiOiAiVGVzdCIsICJleHAiOiAxNjM1MTUwMDAwLCAiaWF0IjogMTYzNTA5NTAwMCwgInNjb3BlIjogInNjb3BlX3Rva2VuIiwgImp0aSI6ICJmb28ifQ==";
+        OAuthBearerTokenJwt jwt = OAuthBearerTokenJwt.create("header." + validPayload + ".signature");
+
+        assertNotNull(jwt);
+        assertEquals("header." + validPayload + ".signature", jwt.value());
+        assertEquals(1, jwt.scope().size());
+        assertEquals("scope_token", jwt.scope().iterator().next());
+        assertEquals("Test", jwt.principalName());
+        assertEquals(1635095000, jwt.startTimeMs());
+    }
+
+    @Test
+    void testCreateThrowsExceptionWithInvalidToken() throws ServiceException {
+        String tokenRaw = "your_mocked_token_here";
+        assertThrows(ServiceException.class, () -> OAuthBearerTokenJwt.create(tokenRaw));
+    }
+}
+
diff --git a/pmproducer/src/test/java/org/oran/pmproducer/oauth2/OAuthKafkaAuthenticateLoginCallbackHandlerTest.java b/pmproducer/src/test/java/org/oran/pmproducer/oauth2/OAuthKafkaAuthenticateLoginCallbackHandlerTest.java
new file mode 100644 (file)
index 0000000..260724a
--- /dev/null
@@ -0,0 +1,129 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2023 Nordix Foundation
+ * %%
+ * 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.
+ * ========================LICENSE_END===================================
+ */
+
+package org.oran.pmproducer.oauth2;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import javax.security.auth.login.AppConfigurationEntry;
+import org.apache.kafka.common.security.auth.SaslExtensionsCallback;
+import org.apache.kafka.common.security.oauthbearer.OAuthBearerLoginModule;
+import org.apache.kafka.common.security.oauthbearer.OAuthBearerTokenCallback;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+class OAuthKafkaAuthenticateLoginCallbackHandlerTest {
+
+    private OAuthKafkaAuthenticateLoginCallbackHandler callbackHandler;
+
+    @BeforeEach
+    void setUp() {
+        callbackHandler = new OAuthKafkaAuthenticateLoginCallbackHandler();
+    }
+
+    @Test
+    void testConfigureWithValidSaslMechanismAndConfigEntry() {
+        String saslMechanism = OAuthBearerLoginModule.OAUTHBEARER_MECHANISM;
+        List<AppConfigurationEntry> jaasConfigEntries = Collections.singletonList(Mockito.mock(AppConfigurationEntry.class));
+
+        callbackHandler.configure(new HashMap<>(), saslMechanism, jaasConfigEntries);
+
+        assertTrue(callbackHandler.isConfigured());
+    }
+
+    @SuppressWarnings("java:S5778")
+    @Test
+    void testConfigureWithInvalidSaslMechanism() {
+        String invalidSaslMechanism = "InvalidMechanism";
+        List<AppConfigurationEntry> jaasConfigEntries = Collections.singletonList(Mockito.mock(AppConfigurationEntry.class));
+
+        assertThrows(IllegalArgumentException.class, () -> callbackHandler.configure(new HashMap<>(), invalidSaslMechanism, jaasConfigEntries));
+
+        assertFalse(callbackHandler.isConfigured());
+    }
+
+    @SuppressWarnings("java:S5778")
+    @Test
+    void testConfigureWithEmptyJaasConfigEntries() {
+        String saslMechanism = OAuthBearerLoginModule.OAUTHBEARER_MECHANISM;
+        List<AppConfigurationEntry> emptyJaasConfigEntries = Collections.emptyList();
+
+        assertThrows(IllegalArgumentException.class, () -> callbackHandler.configure(new HashMap<>(), saslMechanism, emptyJaasConfigEntries));
+
+        assertFalse(callbackHandler.isConfigured());
+    }
+
+    @Test
+    void testHandleSaslExtensionsCallback() throws IOException, UnsupportedCallbackException {
+        String saslMechanism = OAuthBearerLoginModule.OAUTHBEARER_MECHANISM;
+        List<AppConfigurationEntry> jaasConfigEntries = Collections.singletonList(Mockito.mock(AppConfigurationEntry.class));
+
+        callbackHandler.configure(new HashMap<>(), saslMechanism, jaasConfigEntries);
+        SaslExtensionsCallback callback = mock(SaslExtensionsCallback.class);
+
+        callbackHandler.handle(new Callback[]{callback});
+        verify(callback).extensions(any());
+    }
+
+    @Test
+    void testHandleUnsupportedCallback() {
+        Callback unsupportedCallback = mock(Callback.class);
+        String saslMechanism = OAuthBearerLoginModule.OAUTHBEARER_MECHANISM;
+        List<AppConfigurationEntry> jaasConfigEntries = Collections.singletonList(Mockito.mock(AppConfigurationEntry.class));
+
+        callbackHandler.configure(new HashMap<>(), saslMechanism, jaasConfigEntries);
+        assertThrows(UnsupportedCallbackException.class, () -> callbackHandler.handle(new Callback[]{unsupportedCallback}));
+    }
+
+    @Test
+    void testHandleOAuthBearerTokenCallback() throws IOException, UnsupportedCallbackException {
+
+        String saslMechanism = OAuthBearerLoginModule.OAUTHBEARER_MECHANISM;
+        List<AppConfigurationEntry> jaasConfigEntries = Collections.singletonList(Mockito.mock(AppConfigurationEntry.class));
+        String validJwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c";
+
+        callbackHandler.configure(new HashMap<>(), saslMechanism, jaasConfigEntries);
+
+        OAuthBearerTokenCallback oauthBearerTokenCallback = Mockito.mock(OAuthBearerTokenCallback.class);
+        SecurityContext securityContextMock = Mockito.mock(SecurityContext.class);
+        when(oauthBearerTokenCallback.token()).thenReturn(null); // Ensure the callback has no token initially
+        when(oauthBearerTokenCallback.token()).thenAnswer(invocation -> {
+            return OAuthBearerTokenJwt.create(validJwt);
+        });
+
+        when(securityContextMock.getBearerAuthToken()).thenReturn(validJwt);
+        callbackHandler.handle(new Callback[]{oauthBearerTokenCallback});
+        verify(oauthBearerTokenCallback).token();
+    }
+}
+
diff --git a/pmproducer/src/test/java/org/oran/pmproducer/oauth2/SecurityContextTest.java b/pmproducer/src/test/java/org/oran/pmproducer/oauth2/SecurityContextTest.java
new file mode 100644 (file)
index 0000000..43d83a7
--- /dev/null
@@ -0,0 +1,81 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2023 Nordix Foundation
+ * %%
+ * 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.
+ * ========================LICENSE_END===================================
+ */
+
+package org.oran.pmproducer.oauth2;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.nio.file.Path;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.MockitoAnnotations;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+@ExtendWith(MockitoExtension.class)
+class SecurityContextTest {
+
+    @BeforeEach
+    void setUp() {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @Test
+    void testConstructorWithAuthTokenFilename() {
+        SecurityContext securityContext = new SecurityContext("auth-token-file.txt");
+        assertNotNull(securityContext.getAuthTokenFilePath());
+        assertEquals(Path.of("auth-token-file.txt"), securityContext.getAuthTokenFilePath());
+    }
+
+    @Test
+    void testConstructorWithoutAuthTokenFilename() {
+        SecurityContext securityContext = new SecurityContext("");
+        assertNull(securityContext.getAuthTokenFilePath());
+    }
+
+    @Test
+    void testIsConfigured() {
+        SecurityContext securityContext = new SecurityContext("auth-token-file.txt");
+        assertTrue(securityContext.isConfigured());
+    }
+
+    @Test
+    void testIsNotConfigured() {
+        SecurityContext securityContext = new SecurityContext("");
+        assertFalse(securityContext.isConfigured());
+    }
+
+    @Test
+    void testGetBearerAuthToken() {
+        assertEquals("", SecurityContext.getInstance().getBearerAuthToken());
+        assertEquals("", (new SecurityContext("foo.txt")).getBearerAuthToken());
+    }
+
+    @Test
+    void testGetBearerAuthTokenWhenNotConfigured() {
+        SecurityContext securityContext = new SecurityContext("");
+        assertEquals("", securityContext.getBearerAuthToken());
+    }
+}
+
diff --git a/pmproducer/src/test/resources/org/oran/pmproducer/datastore/file.txt b/pmproducer/src/test/resources/org/oran/pmproducer/datastore/file.txt
new file mode 100644 (file)
index 0000000..c95df2d
--- /dev/null
@@ -0,0 +1 @@
+Hi, How are you?
\ No newline at end of file