Invoke Policy Executor and handle not-allowed response

- Execute Policy Executor REST request
- Act (and log) on response from Policy Executor
- Add dispatcher(mock) in integration test FWK
- Add integration test for allow/non allowed and no authorization use cases
- disabled PolicyExecution feature by default (only enabled for testware)

Issue-ID: CPS-2247

Change-Id: I111ba9ba89cc91649b63b20f88414aa33721dbeb
Signed-off-by: ToineSiebelink <toine.siebelink@est.tech>
Signed-off-by: mpriyank <priyank.maheshwari@est.tech>
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/exceptions/PolicyExecutorException.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/exceptions/PolicyExecutorException.java
new file mode 100644
index 0000000..333c122
--- /dev/null
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/exceptions/PolicyExecutorException.java
@@ -0,0 +1,42 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2024 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.onap.cps.ncmp.api.exceptions;
+
+import lombok.Getter;
+
+/**
+ * Exception to be used when policy execution fails or does not allow to proceed.
+ */
+@Getter
+public class PolicyExecutorException extends NcmpException {
+
+    private static final long serialVersionUID = 6659897770659834798L;
+
+    /**
+     * Constructor to form exception for policy executor responses.
+     *
+     * @param message response message
+     * @param details response details
+     */
+    public PolicyExecutorException(final String message, final String details) {
+        super(message, details);
+    }
+}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/DmiDataOperations.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/DmiDataOperations.java
index 0cedeae..e49ba66 100644
--- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/DmiDataOperations.java
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/DmiDataOperations.java
@@ -139,8 +139,7 @@
                                            final String requestId,
                                            final String authorization)  {
 
-        final Set<String> cmHandlesIds
-                = getDistinctCmHandleIds(dataOperationRequest);
+        final Set<String> cmHandlesIds = getDistinctCmHandleIds(dataOperationRequest);
 
         final Collection<YangModelCmHandle> yangModelCmHandles
             = inventoryPersistence.getYangModelCmHandles(cmHandlesIds);
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/policyexecutor/PolicyExecutor.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/policyexecutor/PolicyExecutor.java
index 8e7620c..89b48f3 100644
--- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/policyexecutor/PolicyExecutor.java
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/policyexecutor/PolicyExecutor.java
@@ -20,12 +20,26 @@
 
 package org.onap.cps.ncmp.impl.data.policyexecutor;
 
+import com.fasterxml.jackson.databind.JsonNode;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.onap.cps.ncmp.api.data.models.OperationType;
+import org.onap.cps.ncmp.api.exceptions.PolicyExecutorException;
+import org.onap.cps.ncmp.api.exceptions.ServerNcmpException;
 import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle;
+import org.onap.cps.ncmp.impl.utils.http.RestServiceUrlTemplateBuilder;
+import org.onap.cps.ncmp.impl.utils.http.UrlTemplateParameters;
+import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.ResponseEntity;
 import org.springframework.stereotype.Service;
+import org.springframework.web.reactive.function.BodyInserters;
+import org.springframework.web.reactive.function.client.WebClient;
 
 @Slf4j
 @Service
@@ -41,7 +55,8 @@
     @Value("${ncmp.policy-executor.server.port:8080}")
     private String serverPort;
 
-    private static final String PAYLOAD_TYPE_PREFIX = "cm_";
+    @Qualifier("policyExecutorWebClient")
+    private final WebClient policyExecutorWebClient;
 
     /**
      * Use the Policy Executor to check permission for a cm write operation.
@@ -58,17 +73,102 @@
                                 final String authorization,
                                 final String resourceIdentifier,
                                 final String changeRequestAsJson) {
+        log.trace("Policy Executor Enabled: {}", enabled);
         if (enabled) {
-            final String payloadType = PAYLOAD_TYPE_PREFIX + operationType.getOperationName();
-            log.info("Policy Executor Enabled");
-            log.info("Address               : {}", serverAddress);
-            log.info("Port                  : {}", serverPort);
-            log.info("Authorization         : {}", authorization);
-            log.info("Payload Type          : {}", payloadType);
-            log.info("Target FDN            : {}", yangModelCmHandle.getAlternateId());
-            log.info("CM Handle Id          : {}", yangModelCmHandle.getId());
-            log.info("Resource Identifier   : {}", resourceIdentifier);
-            log.info("Change Request (json) : {}", changeRequestAsJson);
+            final ResponseEntity<JsonNode> responseEntity =
+                getPolicyExecutorResponse(yangModelCmHandle, operationType, authorization, resourceIdentifier,
+                    changeRequestAsJson);
+
+            if (responseEntity == null) {
+                log.warn("No valid response from policy, ignored");
+                return;
+            }
+
+            if (responseEntity.getStatusCode().is2xxSuccessful()) {
+                if (responseEntity.getBody() == null) {
+                    log.warn("No valid response body from policy, ignored");
+                    return;
+                }
+                processResponse(responseEntity.getBody());
+            } else {
+                log.warn("Policy Executor invocation failed with status {}",
+                    responseEntity.getStatusCode().value());
+                throw new ServerNcmpException("Policy Executor invocation failed", "HTTP status code: "
+                    + responseEntity.getStatusCode().value());
+            }
         }
     }
+
+    private Map<String, Object> getSingleRequestAsMap(final YangModelCmHandle yangModelCmHandle,
+                                                      final OperationType operationType,
+                                                      final String resourceIdentifier,
+                                                      final String changeRequestAsJson) {
+        final Map<String, Object> data = new HashMap<>(4);
+        data.put("cmHandleId", yangModelCmHandle.getId());
+        data.put("resourceIdentifier", resourceIdentifier);
+        data.put("targetIdentifier", yangModelCmHandle.getAlternateId());
+        if (!OperationType.DELETE.equals(operationType)) {
+            data.put("cmChangeRequest", changeRequestAsJson);
+        }
+
+        final Map<String, Object> request = new HashMap<>(2);
+        request.put("schema", getAssociatedPolicyDataSchemaName(operationType));
+        request.put("data", data);
+        return request;
+    }
+
+    private static String getAssociatedPolicyDataSchemaName(final OperationType operationType) {
+        return "urn:cps:org.onap.cps.ncmp.policy-executor.ncmp-" + operationType.getOperationName() + "-schema:1.0.0";
+    }
+
+    private Object createBodyAsObject(final List<Object> requests) {
+        final Map<String, Object> bodyAsMap = new HashMap<>(2);
+        bodyAsMap.put("decisionType", "allow");
+        bodyAsMap.put("requests", requests);
+        return bodyAsMap;
+    }
+
+    private ResponseEntity<JsonNode> getPolicyExecutorResponse(final YangModelCmHandle yangModelCmHandle,
+                                                               final OperationType operationType,
+                                                               final String authorization,
+                                                               final String resourceIdentifier,
+                                                               final String changeRequestAsJson) {
+        final String serviceBaseUrl = serverAddress + ":" + serverPort;
+
+        final Map<String, Object> requestAsMap = getSingleRequestAsMap(yangModelCmHandle,
+            operationType,
+            resourceIdentifier,
+            changeRequestAsJson);
+
+        final Object bodyAsObject = createBodyAsObject(Collections.singletonList(requestAsMap));
+
+        final UrlTemplateParameters urlTemplateParameters = RestServiceUrlTemplateBuilder.newInstance()
+            .fixedPathSegment("execute")
+            .createUrlTemplateParameters(serviceBaseUrl, "");
+
+        return policyExecutorWebClient.post()
+            .uri(urlTemplateParameters.urlTemplate(), urlTemplateParameters.urlVariables())
+            .header(HttpHeaders.AUTHORIZATION, authorization)
+            .body(BodyInserters.fromValue(bodyAsObject))
+            .retrieve()
+            .toEntity(JsonNode.class)
+            .block();
+    }
+
+    private static void processResponse(final JsonNode responseBody) {
+        final String decisionId = responseBody.path("decisionId").asText("unknown id");
+        log.trace("Policy Executor Decision ID: {} ", decisionId);
+        final String decision = responseBody.path("decision").asText("unknown");
+        if ("allow".equals(decision)) {
+            log.trace("Policy Executor allows the operation");
+        } else {
+            log.warn("Policy Executor decision: {}", decision);
+            final String details = responseBody.path("message").asText();
+            log.warn("Policy Executor message: {}", details);
+            final String message = "Policy Executor did not allow request. Decision #"
+                + decisionId + " : " + decision;
+            throw new PolicyExecutorException(message, details);
+        }
+    }
+
 }
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/utils/http/RestServiceUrlTemplateBuilder.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/utils/http/RestServiceUrlTemplateBuilder.java
index fafb090..c850ca9 100644
--- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/utils/http/RestServiceUrlTemplateBuilder.java
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/utils/http/RestServiceUrlTemplateBuilder.java
@@ -96,9 +96,7 @@
      * @return a UrlTemplateParameters instance containing the complete URL template and URL variables
      */
     public UrlTemplateParameters createUrlTemplateParameters(final String serviceBaseUrl, final String basePath) {
-        this.uriComponentsBuilder.pathSegment(basePath)
-            .pathSegment(VERSION_SEGMENT);
-
+        this.uriComponentsBuilder.pathSegment(basePath).pathSegment(VERSION_SEGMENT);
         final Map<String, String> urlTemplateVariables = new HashMap<>();
 
         pathSegments.forEach((pathSegmentName, variablePathValue) ->  {
@@ -120,7 +118,7 @@
     }
 
     /**
-     * Constructs a URL for DMI health check based on the given base URL.
+     * Constructs a URL for a spring actuator health check based on the given base URL.
      *
      * @param serviceBaseUrl the base URL of the service, e.g., "http://dmi-service.com".
      * @return a {@link UrlTemplateParameters} instance containing the complete URL template and empty URL variables,
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/config/DmiHttpClientConfigSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/config/DmiHttpClientConfigSpec.groovy
index e0ae204..23f5edd 100644
--- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/config/DmiHttpClientConfigSpec.groovy
+++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/config/DmiHttpClientConfigSpec.groovy
@@ -29,7 +29,7 @@
 
 @SpringBootTest
 @ContextConfiguration(classes = [DmiHttpClientConfig])
-@EnableConfigurationProperties(DmiHttpClientConfig)
+@EnableConfigurationProperties
 class DmiHttpClientConfigSpec extends Specification {
 
     @Autowired
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/config/PolicyExecutorHttpClientConfigSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/config/PolicyExecutorHttpClientConfigSpec.groovy
index 1946a45..ca71c34 100644
--- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/config/PolicyExecutorHttpClientConfigSpec.groovy
+++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/config/PolicyExecutorHttpClientConfigSpec.groovy
@@ -34,7 +34,7 @@
     @Autowired
     PolicyExecutorHttpClientConfig policyExecutorHttpClientConfig
 
-    def 'Test http client configuration properties of data with custom and default values'() {
+    def 'Http client configuration properties for policy executor http client.'() {
         expect: 'properties are populated correctly for all services'
             with(policyExecutorHttpClientConfig.allServices) {
                 assert maximumInMemorySizeInMegabytes == 31
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/PolicyExecutorConfigurationSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/PolicyExecutorConfigurationSpec.groovy
new file mode 100644
index 0000000..c086eab
--- /dev/null
+++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/PolicyExecutorConfigurationSpec.groovy
@@ -0,0 +1,45 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2024 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.onap.cps.ncmp.impl.data
+
+import org.onap.cps.ncmp.config.PolicyExecutorHttpClientConfig
+import org.onap.cps.ncmp.impl.data.policyexecutor.PolicyExecutor
+import org.onap.cps.ncmp.impl.policyexecutor.PolicyExecutorWebClientConfiguration
+import org.onap.cps.ncmp.utils.WebClientBuilderTestConfig
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.boot.test.context.SpringBootTest
+import org.springframework.test.context.ContextConfiguration
+import spock.lang.Specification
+
+@SpringBootTest
+@ContextConfiguration(classes = [PolicyExecutor, PolicyExecutorWebClientConfiguration,  PolicyExecutorHttpClientConfig, WebClientBuilderTestConfig ])
+class PolicyExecutorConfigurationSpec extends Specification {
+
+    @Autowired
+    PolicyExecutor objectUnderTest
+
+    def 'Policy executor configuration properties.'() {
+        expect: 'properties used from application.yml'
+            assert objectUnderTest.enabled
+            assert objectUnderTest.serverAddress == 'http://localhost'
+            assert objectUnderTest.serverPort == '8785'
+    }
+}
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/PolicyExecutorSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/PolicyExecutorSpec.groovy
index 4b09afa..a577667 100644
--- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/PolicyExecutorSpec.groovy
+++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/PolicyExecutorSpec.groovy
@@ -1,69 +1,139 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2024 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.onap.cps.ncmp.impl.data
 
 import ch.qos.logback.classic.Level
 import ch.qos.logback.classic.Logger
 import ch.qos.logback.classic.spi.ILoggingEvent
 import ch.qos.logback.core.read.ListAppender
+import com.fasterxml.jackson.databind.JsonNode
+import com.fasterxml.jackson.databind.ObjectMapper
+import org.onap.cps.ncmp.api.exceptions.PolicyExecutorException
+import org.onap.cps.ncmp.api.exceptions.ServerNcmpException
 import org.onap.cps.ncmp.impl.data.policyexecutor.PolicyExecutor
 import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle
 import org.slf4j.LoggerFactory
-import org.springframework.beans.factory.annotation.Autowired
-import org.springframework.boot.test.context.SpringBootTest
-import org.springframework.test.context.ContextConfiguration
+import org.springframework.http.HttpStatus
+import org.springframework.http.ResponseEntity
+import org.springframework.web.reactive.function.client.WebClient
+import reactor.core.publisher.Mono
 import spock.lang.Specification
 
+import static org.onap.cps.ncmp.api.data.models.OperationType.CREATE
+import static org.onap.cps.ncmp.api.data.models.OperationType.DELETE
 import static org.onap.cps.ncmp.api.data.models.OperationType.PATCH
+import static org.onap.cps.ncmp.api.data.models.OperationType.UPDATE
 
-@SpringBootTest
-@ContextConfiguration(classes = [PolicyExecutor])
 class PolicyExecutorSpec extends Specification {
 
-    @Autowired
-    PolicyExecutor objectUnderTest
+    def mockWebClient = Mock(WebClient)
+    def mockRequestBodyUriSpec = Mock(WebClient.RequestBodyUriSpec)
+    def mockResponseSpec = Mock(WebClient.ResponseSpec)
+
+    PolicyExecutor objectUnderTest = new PolicyExecutor(mockWebClient)
 
     def logAppender = Spy(ListAppender<ILoggingEvent>)
 
+    ObjectMapper objectMapper = new ObjectMapper()
+
     def setup() {
         setupLogger()
+        objectUnderTest.enabled = true
+        mockWebClient.post() >> mockRequestBodyUriSpec
+        mockRequestBodyUriSpec.uri(*_) >> mockRequestBodyUriSpec
+        mockRequestBodyUriSpec.header(*_) >> mockRequestBodyUriSpec
+        mockRequestBodyUriSpec.body(*_) >> mockRequestBodyUriSpec
+        mockRequestBodyUriSpec.retrieve() >> mockResponseSpec
     }
 
     def cleanup() {
         ((Logger) LoggerFactory.getLogger(PolicyExecutor)).detachAndStopAllAppenders()
     }
 
-    def 'Configuration properties.'() {
-        expect: 'properties used from application.yml'
-            assert objectUnderTest.enabled
-            assert objectUnderTest.serverAddress == 'http://localhost'
-            assert objectUnderTest.serverPort == '8785'
-    }
-
-    def 'Permission check logging.'() {
+    def 'Permission check with allow response.'() {
+        given: 'allow response'
+            mockResponse([decision:'allow'], HttpStatus.OK)
         when: 'permission is checked for an operation'
-            def yangModelCmHandle = new YangModelCmHandle(id:'ch-1', alternateId:'fdn1')
-            objectUnderTest.checkPermission(yangModelCmHandle, PATCH, 'my credentials','my resource','my change')
-        then: 'correct details are logged '
-            assert getLogEntry(0) == 'Policy Executor Enabled'
-            assert getLogEntry(3).contains('my credentials')
-            assert getLogEntry(4).contains('cm_patch')
-            assert getLogEntry(5).contains('fdn1')
-            assert getLogEntry(6).contains('ch-1')
-            assert getLogEntry(7).contains('my resource')
-            assert getLogEntry(8).contains('my change')
+            objectUnderTest.checkPermission(new YangModelCmHandle(), operationType, 'my credentials','my resource','my change')
+        then: 'system logs the operation is allowed'
+            assert getLogEntry(2) == 'Policy Executor allows the operation'
+        and: 'no exception occurs'
+            noExceptionThrown()
+        where: 'all write operations are tested'
+            operationType << [ CREATE, DELETE, PATCH, UPDATE ]
     }
 
-    def 'Permission check with feature disabled.'() {
+    def 'Permission check with other response (not allowed).'() {
+        given: 'other response'
+            mockResponse([decision:'other', decisionId:123, message:'I dont like Mondays' ], HttpStatus.OK)
+        when: 'permission is checked for an operation'
+            objectUnderTest.checkPermission(new YangModelCmHandle(), PATCH, 'my credentials','my resource','my change')
+        then: 'Policy Executor exception is thrown'
+            def thrownException = thrown(PolicyExecutorException)
+            assert thrownException.message == 'Policy Executor did not allow request. Decision #123 : other'
+            assert thrownException.details == 'I dont like Mondays'
+    }
+
+    def 'Permission check with non 2xx response.'() {
+        given: 'other response'
+            mockResponse([], HttpStatus.I_AM_A_TEAPOT)
+        when: 'permission is checked for an operation'
+            objectUnderTest.checkPermission(new YangModelCmHandle(), PATCH, 'my credentials','my resource','my change')
+        then: 'Server Ncmp exception is thrown'
+            def thrownException = thrown(ServerNcmpException)
+            assert thrownException.message == 'Policy Executor invocation failed'
+            assert thrownException.details == 'HTTP status code: 418'
+    }
+
+    def 'Permission check with invalid response from Policy Executor.'() {
+        given: 'invalid response from Policy executor'
+            mockResponseSpec.toEntity(*_) >> invalidResponse
+        when: 'permission is checked for an operation'
+            objectUnderTest.checkPermission(new YangModelCmHandle(), CREATE, 'my credentials','my resource','my change')
+        then: 'system logs the expected message'
+            assert getLogEntry(1) == expectedMessage
+        where: 'following invalid responses are received'
+            invalidResponse                                        || expectedMessage
+            Mono.empty()                                           || 'No valid response from policy, ignored'
+            Mono.just(new ResponseEntity<>(null, HttpStatus.OK))   || 'No valid response body from policy, ignored'
+    }
+
+    def 'Permission check feature disabled.'() {
         given: 'feature is disabled'
             objectUnderTest.enabled = false
         when: 'permission is checked for an operation'
             objectUnderTest.checkPermission(new YangModelCmHandle(), PATCH, 'my credentials','my resource','my change')
-        then: 'nothing is logged'
-            assert logAppender.list.isEmpty()
+        then: 'system logs that the feature not enabled'
+            assert getLogEntry(0) == 'Policy Executor Enabled: false'
+    }
+
+    def mockResponse(mockResponseAsMap, httpStatus) {
+        JsonNode jsonNode = objectMapper.readTree(objectMapper.writeValueAsString(mockResponseAsMap))
+        def mono = Mono.just(new ResponseEntity<>(jsonNode, httpStatus))
+        mockResponseSpec.toEntity(*_) >> mono
     }
 
     def setupLogger() {
         def logger = LoggerFactory.getLogger(PolicyExecutor)
-        logger.setLevel(Level.DEBUG)
+        logger.setLevel(Level.TRACE)
         logger.addAppender(logAppender)
         logAppender.start()
     }
diff --git a/cps-ncmp-service/src/test/java/org/onap/cps/ncmp/utils/WebClientBuilderTestConfig.java b/cps-ncmp-service/src/test/java/org/onap/cps/ncmp/utils/WebClientBuilderTestConfig.java
new file mode 100644
index 0000000..2f6b270
--- /dev/null
+++ b/cps-ncmp-service/src/test/java/org/onap/cps/ncmp/utils/WebClientBuilderTestConfig.java
@@ -0,0 +1,40 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2024 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.onap.cps.ncmp.utils;
+
+import org.springframework.boot.test.context.TestConfiguration;
+import org.springframework.context.annotation.Bean;
+import org.springframework.web.reactive.function.client.WebClient;
+
+@TestConfiguration
+public class WebClientBuilderTestConfig {
+
+    /**
+     * Configures and creates a web client builder bean to make it accessible for the Spring Boot Test Context.
+     *
+     * @return a WebClient Builder instance.
+     */
+    @Bean
+    public WebClient.Builder webClientBuilder() {
+        return WebClient.builder();
+    }
+
+}
diff --git a/cps-ncmp-service/src/test/resources/application.yml b/cps-ncmp-service/src/test/resources/application.yml
index 72d074a..c76831d 100644
--- a/cps-ncmp-service/src/test/resources/application.yml
+++ b/cps-ncmp-service/src/test/resources/application.yml
@@ -84,8 +84,8 @@
     policy-executor:
         enabled: true
         server:
-            address: "http://localhost"
-            port: "8785"
+            address: http://localhost
+            port: 8785
         httpclient:
             all-services:
                 maximumInMemorySizeInMegabytes: 31