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-application/src/main/resources/application.yml b/cps-application/src/main/resources/application.yml
index dd4576e..8ca6b0f 100644
--- a/cps-application/src/main/resources/application.yml
+++ b/cps-application/src/main/resources/application.yml
@@ -189,10 +189,10 @@
                 cps: INFO
 ncmp:
     policy-executor:
-        enabled: true
+        enabled: false
         server:
-            address: "http://localhost"
-            port: "8785"
+            address: http://localhost
+            port: 8785
         httpclient:
             all-services:
                 maximumInMemorySizeInMegabytes: 16
diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyRestExceptionHandler.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyRestExceptionHandler.java
index d61d30d..6910003 100644
--- a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyRestExceptionHandler.java
+++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyRestExceptionHandler.java
@@ -31,6 +31,7 @@
 import org.onap.cps.ncmp.api.exceptions.InvalidTopicException;
 import org.onap.cps.ncmp.api.exceptions.NcmpException;
 import org.onap.cps.ncmp.api.exceptions.PayloadTooLargeException;
+import org.onap.cps.ncmp.api.exceptions.PolicyExecutorException;
 import org.onap.cps.ncmp.api.exceptions.ServerNcmpException;
 import org.onap.cps.ncmp.rest.model.DmiErrorMessage;
 import org.onap.cps.ncmp.rest.model.DmiErrorMessageDmiResponse;
@@ -84,8 +85,8 @@
         return buildErrorResponse(HttpStatus.BAD_REQUEST, exception);
     }
 
-    @ExceptionHandler({AlreadyDefinedException.class})
-    public static ResponseEntity<Object> handleAlreadyDefinedExceptions(final Exception exception) {
+    @ExceptionHandler({AlreadyDefinedException.class, PolicyExecutorException.class})
+    public static ResponseEntity<Object> handleConflictExceptions(final Exception exception) {
         return buildErrorResponse(HttpStatus.CONFLICT, exception);
     }
 
@@ -113,8 +114,6 @@
         } else {
             errorMessage.setDetails(CHECK_LOGS_FOR_DETAILS);
         }
-        errorMessage.setDetails(
-                exception instanceof CpsException ? ((CpsException) exception).getDetails() : CHECK_LOGS_FOR_DETAILS);
         return new ResponseEntity<>(errorMessage, status);
     }
 
diff --git a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyRestExceptionHandlerSpec.groovy b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyRestExceptionHandlerSpec.groovy
index e6288ff..9d36d10 100644
--- a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyRestExceptionHandlerSpec.groovy
+++ b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyRestExceptionHandlerSpec.groovy
@@ -29,11 +29,13 @@
 import org.onap.cps.ncmp.api.exceptions.DmiClientRequestException
 import org.onap.cps.ncmp.api.exceptions.DmiRequestException
 import org.onap.cps.ncmp.api.exceptions.PayloadTooLargeException
+import org.onap.cps.ncmp.api.exceptions.PolicyExecutorException
 import org.onap.cps.ncmp.api.exceptions.ServerNcmpException
 import org.onap.cps.ncmp.api.inventory.NetworkCmProxyInventoryFacade
 import org.onap.cps.ncmp.impl.data.NcmpCachedResourceRequestHandler
 import org.onap.cps.ncmp.impl.data.NcmpPassthroughResourceRequestHandler
 import org.onap.cps.ncmp.impl.data.NetworkCmProxyFacade
+import org.onap.cps.ncmp.impl.data.policyexecutor.PolicyExecutor
 import org.onap.cps.ncmp.impl.inventory.InventoryPersistence
 import org.onap.cps.ncmp.rest.util.CmHandleStateMapper
 import org.onap.cps.ncmp.rest.util.DataOperationRequestMapper
@@ -120,25 +122,26 @@
         dataNodeBaseEndpointNcmpInventory = "$basePathNcmpInventory/v1"
     }
 
-    def 'Get request with #scenario exception returns correct HTTP Status with #scenario'() {
+    def 'Get request with #scenario exception returns correct HTTP Status with #scenario exception'() {
         when: 'an exception is thrown by the service'
             setupTestException(exception, NCMP)
             def response = performTestRequest(NCMP)
         then: 'an HTTP response is returned with correct message and details'
             assertTestResponse(response, expectedErrorCode, expectedErrorMessage, expectedErrorDetails)
         where:
-            scenario                | exception                                                        || expectedErrorCode     | expectedErrorMessage        | expectedErrorDetails
-            'CPS'                   | new CpsException(sampleErrorMessage, sampleErrorDetails)         || INTERNAL_SERVER_ERROR | sampleErrorMessage          | sampleErrorDetails
-            'NCMP-server'           | new ServerNcmpException(sampleErrorMessage, sampleErrorDetails)  || INTERNAL_SERVER_ERROR | sampleErrorMessage          | null
-            'DMI Request'           | new DmiRequestException(sampleErrorMessage, sampleErrorDetails)  || BAD_REQUEST           | sampleErrorMessage          | null
-            'Invalid Operation'     | new InvalidOperationException('some reason')                     || BAD_REQUEST           | 'some reason'               | null
-            'Unsupported Operation' | new OperationNotSupportedException('not yet')                    || BAD_REQUEST           | 'not yet'                   | null
-            'DataNode Validation'   | new DataNodeNotFoundException('myDataspaceName', 'myAnchorName') || NOT_FOUND             | 'DataNode not found'        | null
-            'other'                 | new IllegalStateException(sampleErrorMessage)                    || INTERNAL_SERVER_ERROR | sampleErrorMessage          | null
-            'Data Node Not Found'   | new DataNodeNotFoundException('myDataspaceName', 'myAnchorName') || NOT_FOUND             | 'DataNode not found'        | 'DataNode not found'
-            'Existing entry'        | new AlreadyDefinedException('name',null)                         || CONFLICT              | 'Already defined exception' | 'name already exists'
-            'Existing entries'      | AlreadyDefinedException.forDataNodes(['A', 'B'], 'myAnchorName') || CONFLICT              | 'Already defined exception' | '2 data node(s) already exist'
-            'Operation too large'   | new PayloadTooLargeException(sampleErrorMessage) || PAYLOAD_TOO_LARGE | sampleErrorMessage | 'Check logs'
+            scenario                | exception                                                           || expectedErrorCode     | expectedErrorMessage        | expectedErrorDetails
+            'CPS'                   | new CpsException(sampleErrorMessage, sampleErrorDetails)            || INTERNAL_SERVER_ERROR | sampleErrorMessage          | sampleErrorDetails
+            'NCMP-server'           | new ServerNcmpException(sampleErrorMessage, sampleErrorDetails)     || INTERNAL_SERVER_ERROR | sampleErrorMessage          | null
+            'DMI Request'           | new DmiRequestException(sampleErrorMessage, sampleErrorDetails)     || BAD_REQUEST           | sampleErrorMessage          | null
+            'Invalid Operation'     | new InvalidOperationException('some reason')                        || BAD_REQUEST           | 'some reason'               | null
+            'Unsupported Operation' | new OperationNotSupportedException('not yet')                       || BAD_REQUEST           | 'not yet'                   | null
+            'DataNode Validation'   | new DataNodeNotFoundException('myDataspaceName', 'myAnchorName')    || NOT_FOUND             | 'DataNode not found'        | null
+            'other'                 | new IllegalStateException(sampleErrorMessage)                       || INTERNAL_SERVER_ERROR | sampleErrorMessage          | null
+            'Data Node Not Found'   | new DataNodeNotFoundException('myDataspaceName', 'myAnchorName')    || NOT_FOUND             | 'DataNode not found'        | 'DataNode not found'
+            'Existing entry'        | new AlreadyDefinedException('name',null)                            || CONFLICT              | 'Already defined exception' | 'name already exists'
+            'Existing entries'      | AlreadyDefinedException.forDataNodes(['A', 'B'], 'myAnchorName')    || CONFLICT              | 'Already defined exception' | '2 data node(s) already exist'
+            'Operation too large'   | new PayloadTooLargeException(sampleErrorMessage)                    || PAYLOAD_TOO_LARGE     | sampleErrorMessage          | 'Check logs'
+            'Policy Executor'       | new PolicyExecutorException(sampleErrorMessage, sampleErrorDetails) || CONFLICT              | sampleErrorMessage          | sampleErrorDetails
     }
 
     def 'Post request with exception returns correct HTTP Status.'() {
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
diff --git a/docs/schemas/policy-executor/ncmp-create-schema-1.0.0.json b/docs/schemas/policy-executor/ncmp-create-schema-1.0.0.json
index 2ec9daf..4d98bc8 100644
--- a/docs/schemas/policy-executor/ncmp-create-schema-1.0.0.json
+++ b/docs/schemas/policy-executor/ncmp-create-schema-1.0.0.json
@@ -1,6 +1,6 @@
 {
   "$schema": "https://json-schema.org/draft/2019-09/schema",
-  "$id": "urn:cps:org.onap.cps.ncmp.policy-executor:ncmp-create-schema:1.0.0",
+  "$id": "urn:cps:org.onap.cps.ncmp.policy-executor.ncmp-create-schema:1.0.0",
   "$ref": "#/definitions/NcmpCreate",
   "definitions": {
     "NcmpCreate": {
diff --git a/docs/schemas/policy-executor/ncmp-delete-schema-1.0.0.json b/docs/schemas/policy-executor/ncmp-delete-schema-1.0.0.json
index 5df0325..1246d9d 100644
--- a/docs/schemas/policy-executor/ncmp-delete-schema-1.0.0.json
+++ b/docs/schemas/policy-executor/ncmp-delete-schema-1.0.0.json
@@ -1,6 +1,6 @@
 {
   "$schema": "https://json-schema.org/draft/2019-09/schema",
-  "$id": "urn:cps:org.onap.cps.ncmp.policy-executor:ncmp-delete-schema:1.0.0",
+  "$id": "urn:cps:org.onap.cps.ncmp.policy-executor.ncmp-delete-schema:1.0.0",
   "$ref": "#/definitions/NcmpDelete",
   "definitions": {
     "NcmpDelete": {
diff --git a/docs/schemas/policy-executor/ncmp-patch-schema-1.0.0.json b/docs/schemas/policy-executor/ncmp-patch-schema-1.0.0.json
index e26c244..4917aea 100644
--- a/docs/schemas/policy-executor/ncmp-patch-schema-1.0.0.json
+++ b/docs/schemas/policy-executor/ncmp-patch-schema-1.0.0.json
@@ -1,6 +1,6 @@
 {
   "$schema": "https://json-schema.org/draft/2019-09/schema",
-  "$id": "urn:cps:org.onap.cps.ncmp.policy-executor:ncmp-patch-schema:1.0.0",
+  "$id": "urn:cps:org.onap.cps.ncmp.policy-executor.ncmp-patch-schema:1.0.0",
   "$ref": "#/definitions/NcmpPatch",
   "definitions": {
     "NcmpPatch": {
diff --git a/docs/schemas/policy-executor/ncmp-update-schema-1.0.0.json b/docs/schemas/policy-executor/ncmp-update-schema-1.0.0.json
index 0a497e3..831526c 100644
--- a/docs/schemas/policy-executor/ncmp-update-schema-1.0.0.json
+++ b/docs/schemas/policy-executor/ncmp-update-schema-1.0.0.json
@@ -1,6 +1,6 @@
 {
   "$schema": "https://json-schema.org/draft/2019-09/schema",
-  "$id": "urn:cps:org.onap.cps.ncmp.policy-executor:ncmp-update-schema:1.0.0",
+  "$id": "urn:cps:org.onap.cps.ncmp.policy-executor.ncmp-update-schema:1.0.0",
   "$ref": "#/definitions/NcmpUpdate",
   "definitions": {
     "NcmpUpdate": {
diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/base/CpsIntegrationSpecBase.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/base/CpsIntegrationSpecBase.groovy
index 5e46e95..bd53c4e 100644
--- a/integration-test/src/test/groovy/org/onap/cps/integration/base/CpsIntegrationSpecBase.groovy
+++ b/integration-test/src/test/groovy/org/onap/cps/integration/base/CpsIntegrationSpecBase.groovy
@@ -46,6 +46,7 @@
 import org.onap.cps.utils.ContentType
 import org.onap.cps.utils.JsonObjectMapper
 import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.beans.factory.annotation.Value
 import org.springframework.boot.autoconfigure.EnableAutoConfiguration
 import org.springframework.boot.autoconfigure.domain.EntityScan
 import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc
@@ -125,12 +126,19 @@
     @Autowired
     AlternateIdMatcher alternateIdMatcher
 
+
+    @Value('${ncmp.policy-executor.server.port:8080}')
+    private String policyServerPort;
+
     MockWebServer mockDmiServer1 = new MockWebServer()
     MockWebServer mockDmiServer2 = new MockWebServer()
+    MockWebServer mockPolicyServer = new MockWebServer()
 
     DmiDispatcher dmiDispatcher1 = new DmiDispatcher()
     DmiDispatcher dmiDispatcher2 = new DmiDispatcher()
 
+    PolicyDispatcher policyDispatcher = new PolicyDispatcher();
+
     def DMI1_URL = null
     def DMI2_URL = null
 
@@ -155,13 +163,18 @@
         mockDmiServer2.setDispatcher(dmiDispatcher2)
         mockDmiServer2.start()
 
+        mockPolicyServer.setDispatcher(policyDispatcher)
+        mockPolicyServer.start(Integer.valueOf(policyServerPort))
+
         DMI1_URL = String.format("http://%s:%s", mockDmiServer1.getHostName(), mockDmiServer1.getPort())
         DMI2_URL = String.format("http://%s:%s", mockDmiServer2.getHostName(), mockDmiServer2.getPort())
+
     }
 
     def cleanup() {
         mockDmiServer1.shutdown()
         mockDmiServer2.shutdown()
+        mockPolicyServer.shutdown()
     }
 
     def static readResourceDataFile(filename) {
diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/base/DmiDispatcher.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/base/DmiDispatcher.groovy
index fcc23db..35a7b6a 100644
--- a/integration-test/src/test/groovy/org/onap/cps/integration/base/DmiDispatcher.groovy
+++ b/integration-test/src/test/groovy/org/onap/cps/integration/base/DmiDispatcher.groovy
@@ -20,18 +20,18 @@
 
 package org.onap.cps.integration.base
 
-import static org.onap.cps.integration.base.CpsIntegrationSpecBase.readResourceDataFile
-
 import groovy.json.JsonSlurper
-import java.util.regex.Matcher
 import okhttp3.mockwebserver.Dispatcher
 import okhttp3.mockwebserver.MockResponse
 import okhttp3.mockwebserver.RecordedRequest
-import org.onap.cps.ncmp.api.datajobs.models.SubJobWriteRequest
 import org.springframework.http.HttpHeaders
 import org.springframework.http.HttpStatus
 import org.springframework.http.MediaType
 
+import java.util.regex.Matcher
+
+import static org.onap.cps.integration.base.CpsIntegrationSpecBase.readResourceDataFile
+
 /**
  * This class simulates responses from the DMI server in NCMP integration tests.
  *
@@ -117,32 +117,32 @@
         return mockResponseWithBody(HttpStatus.OK, response)
     }
 
-    private getModuleReferencesResponse(cmHandleId) {
+    def getModuleReferencesResponse(cmHandleId) {
         def moduleReferences = '{"schemas":[' + getModuleNamesForCmHandle(cmHandleId).collect {
             MODULE_REFERENCES_RESPONSE_TEMPLATE.replaceAll("<MODULE_NAME>", it)
         }.join(',') + ']}'
         return mockResponseWithBody(HttpStatus.OK, moduleReferences)
     }
 
-    private getModuleResourcesResponse(cmHandleId) {
+    def getModuleResourcesResponse(cmHandleId) {
         def moduleResources = '[' + getModuleNamesForCmHandle(cmHandleId).collect {
             MODULE_RESOURCES_RESPONSE_TEMPLATE.replaceAll("<MODULE_NAME>", it)
         }.join(',') + ']'
         return mockResponseWithBody(HttpStatus.OK, moduleResources)
     }
 
-    private getModuleNamesForCmHandle(cmHandleId) {
+    def getModuleNamesForCmHandle(cmHandleId) {
         if (!moduleNamesPerCmHandleId.containsKey(cmHandleId)) {
             throw new IllegalArgumentException('Mock DMI has no modules configured for ' + cmHandleId)
         }
         return moduleNamesPerCmHandleId.get(cmHandleId)
     }
 
-    private static mockResponse(status) {
+    def static mockResponse(status) {
         return new MockResponse().setResponseCode(status.value())
     }
 
-    private static mockResponseWithBody(status, responseBody) {
+    def static mockResponseWithBody(status, responseBody) {
         return new MockResponse()
                 .setResponseCode(status.value())
                 .addHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON)
diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/base/PolicyDispatcher.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/base/PolicyDispatcher.groovy
new file mode 100644
index 0000000..27e7563
--- /dev/null
+++ b/integration-test/src/test/groovy/org/onap/cps/integration/base/PolicyDispatcher.groovy
@@ -0,0 +1,74 @@
+/*
+ *  ============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.integration.base
+
+
+import okhttp3.mockwebserver.Dispatcher
+import okhttp3.mockwebserver.MockResponse
+import okhttp3.mockwebserver.RecordedRequest
+import org.springframework.http.HttpHeaders
+import org.springframework.http.HttpStatus
+import org.springframework.http.MediaType
+import org.testcontainers.shaded.com.fasterxml.jackson.databind.ObjectMapper
+
+/**
+ * This class simulates responses from the Policy Execution server in NCMP integration tests.
+ */
+class PolicyDispatcher extends Dispatcher {
+
+    def objectMapper = new ObjectMapper()
+    def expectedAuthorizationToken = 'ABC'
+    def allowAll = true; // Prevents legacy test being affected
+
+    @Override
+    MockResponse dispatch(RecordedRequest recordedRequest) {
+
+        if (!allowAll && !recordedRequest.getHeader('Authorization').contains(expectedAuthorizationToken)) {
+            return new MockResponse().setResponseCode(401)
+        }
+
+        if (recordedRequest.path != '/v1/execute') {
+            return new MockResponse().setResponseCode(400)
+        }
+
+        def body = objectMapper.readValue(recordedRequest.getBody().readUtf8(), Map.class)
+        def targetIdentifier = body.get('requests').get(0).get('data').get('targetIdentifier')
+        def responseAsMap = [:]
+        responseAsMap.put('decisionId',1)
+        if (allowAll || targetIdentifier == 'fdn1') {
+            responseAsMap.put('decision','allow')
+            responseAsMap.put('message','')
+        } else {
+            responseAsMap.put('decision','deny')
+            responseAsMap.put('message','I only like fdn1')
+        }
+        def responseAsString = objectMapper.writeValueAsString(responseAsMap)
+
+        return mockResponseWithBody(HttpStatus.OK, responseAsString)
+    }
+
+    static mockResponseWithBody(status, responseBody) {
+        return new MockResponse()
+                .setResponseCode(status.value())
+                .addHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON)
+                .setBody(responseBody)
+    }
+}
diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/PolicyExecutorIntegrationSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/PolicyExecutorIntegrationSpec.groovy
new file mode 100644
index 0000000..99f245a
--- /dev/null
+++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/PolicyExecutorIntegrationSpec.groovy
@@ -0,0 +1,63 @@
+/*
+ *  ============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.integration.functional.ncmp
+
+import org.onap.cps.integration.base.CpsIntegrationSpecBase
+import org.springframework.http.HttpHeaders
+import org.springframework.http.MediaType
+
+import static org.springframework.http.HttpMethod.POST
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.request
+
+class PolicyExecutorIntegrationSpec extends CpsIntegrationSpecBase {
+
+    def setup() {
+        // Enable mocked policy executor logic
+        policyDispatcher.allowAll = false;
+        //minimum setup for 2 cm handles with alternate ids
+        dmiDispatcher1.moduleNamesPerCmHandleId = ['ch-1': [], 'ch-2': []]
+        registerCmHandle(DMI1_URL, 'ch-1', NO_MODULE_SET_TAG, 'fdn1')
+        registerCmHandle(DMI1_URL, 'ch-2', NO_MODULE_SET_TAG, 'fdn2')
+    }
+
+    def cleanup() {
+        deregisterCmHandle(DMI1_URL, 'ch-1')
+        deregisterCmHandle(DMI1_URL, 'ch-2')
+    }
+
+    def 'Policy Executor create request with #scenario.'() {
+        when: 'a pass-through write request is sent to NCMP'
+            def response = mvc.perform(request(POST, "/ncmp/v1/ch/$cmHandle/data/ds/ncmp-datastore:passthrough-running")
+                    .queryParam('resourceIdentifier', 'my-resource-id')
+                    .contentType(MediaType.APPLICATION_JSON)
+                    .content('{ "some-json": "data" }')
+                    .header(HttpHeaders.AUTHORIZATION, authorization))
+                    .andReturn().response
+        then: 'the expected status code is returned'
+            response.getStatus() == execpectedStatusCode
+        where: 'following parameters are used'
+            scenario                | cmHandle | authorization         || execpectedStatusCode
+            'accepted cm handle'    | 'ch-1'   | 'mock expects "ABC"'  || 201
+            'un-accepted cm handle' | 'ch-2'   | 'mock expects "ABC"'  || 409
+            'invalid authorization' | 'ch-1'   | 'something else'      || 500
+    }
+
+}
diff --git a/integration-test/src/test/resources/application.yml b/integration-test/src/test/resources/application.yml
index fefae34..760dad0 100644
--- a/integration-test/src/test/resources/application.yml
+++ b/integration-test/src/test/resources/application.yml
@@ -213,6 +213,20 @@
     init:
       mode: ALWAYS
 
+  policy-executor:
+    enabled: true
+    server:
+      address: http://localhost
+      port: 8790
+    httpclient:
+      all-services:
+        maximumInMemorySizeInMegabytes: 1
+        maximumConnectionsTotal: 10
+        pendingAcquireMaxCount: 10
+        connectionTimeoutInSeconds: 30
+        readTimeoutInSeconds: 30
+        writeTimeoutInSeconds: 30
+
 hazelcast:
   cluster-name: cps-and-ncmp-test-caches
   mode:
diff --git a/spotbugs/src/main/resources/spotbugs-exclude.xml b/spotbugs/src/main/resources/spotbugs-exclude.xml
index 78f61d2..23e62cd 100644
--- a/spotbugs/src/main/resources/spotbugs-exclude.xml
+++ b/spotbugs/src/main/resources/spotbugs-exclude.xml
@@ -36,6 +36,7 @@
       <Bug pattern="NP_NULL_PARAM_DEREF" />
       <Bug pattern="NP_PARAMETER_MUST_BE_NONNULL_BUT_MARKED_AS_NULLABLE" />
       <Bug pattern="RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE" />
+      <Bug pattern="NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE" />
 
       <!-- https://stackoverflow.com/a/34674776. Doesn't detect Lombok All Args Constructor variables being used with map get key and value, which can lead to spotbugs being detected
       on used fields -->