From 5ec7bcc6b8104c64c3b044c765ec41159208f956 Mon Sep 17 00:00:00 2001 From: sourabh_sourabh Date: Fri, 30 Aug 2024 13:17:57 +0100 Subject: Policy Executor: pass Change request as Object - Added unit test for new exception handling Issue-ID: CPS-2335 Change-Id: I5517c91edf505932d0a94165c19420922b3f5f51 Signed-off-by: ToineSiebelink Signed-off-by: sourabh_sourabh --- .../impl/data/policyexecutor/PolicyExecutor.java | 13 +- .../data/PolicyExecutorConfigurationSpec.groovy | 45 ------ .../cps/ncmp/impl/data/PolicyExecutorSpec.groovy | 144 ------------------- .../PolicyExecutorConfigurationSpec.groovy | 45 ++++++ .../data/policyexecutor/PolicyExecutorSpec.groovy | 154 +++++++++++++++++++++ 5 files changed, 211 insertions(+), 190 deletions(-) delete mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/PolicyExecutorConfigurationSpec.groovy delete mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/PolicyExecutorSpec.groovy create mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/policyexecutor/PolicyExecutorConfigurationSpec.groovy create mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/policyexecutor/PolicyExecutorSpec.groovy (limited to 'cps-ncmp-service/src') 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 89b48f3755..b3aa848394 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,7 +20,9 @@ package org.onap.cps.ncmp.impl.data.policyexecutor; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -28,6 +30,7 @@ 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.NcmpException; 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; @@ -58,6 +61,8 @@ public class PolicyExecutor { @Qualifier("policyExecutorWebClient") private final WebClient policyExecutorWebClient; + private final ObjectMapper objectMapper; + /** * Use the Policy Executor to check permission for a cm write operation. * Wil throw an exception when the operation is not permitted (work in progress) @@ -108,7 +113,13 @@ public class PolicyExecutor { data.put("resourceIdentifier", resourceIdentifier); data.put("targetIdentifier", yangModelCmHandle.getAlternateId()); if (!OperationType.DELETE.equals(operationType)) { - data.put("cmChangeRequest", changeRequestAsJson); + try { + final Object changeRequestAsObject = objectMapper.readValue(changeRequestAsJson, Object.class); + data.put("cmChangeRequest", changeRequestAsObject); + } catch (final JsonProcessingException e) { + throw new NcmpException("Cannot convert Change Request data to Object", + "Invalid Json: " + changeRequestAsJson); + } } final Map request = new HashMap<>(2); 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 deleted file mode 100644 index c086eab810..0000000000 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/PolicyExecutorConfigurationSpec.groovy +++ /dev/null @@ -1,45 +0,0 @@ -/* - * ============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 deleted file mode 100644 index a5776676dc..0000000000 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/PolicyExecutorSpec.groovy +++ /dev/null @@ -1,144 +0,0 @@ -/* - * ============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.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 - -class PolicyExecutorSpec extends Specification { - - def mockWebClient = Mock(WebClient) - def mockRequestBodyUriSpec = Mock(WebClient.RequestBodyUriSpec) - def mockResponseSpec = Mock(WebClient.ResponseSpec) - - PolicyExecutor objectUnderTest = new PolicyExecutor(mockWebClient) - - def logAppender = Spy(ListAppender) - - 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 'Permission check with allow response.'() { - given: 'allow response' - mockResponse([decision:'allow'], HttpStatus.OK) - when: 'permission is checked for an operation' - 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 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: '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.TRACE) - logger.addAppender(logAppender) - logAppender.start() - } - - def getLogEntry(index) { - logAppender.list[index].formattedMessage - } -} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/policyexecutor/PolicyExecutorConfigurationSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/policyexecutor/PolicyExecutorConfigurationSpec.groovy new file mode 100644 index 0000000000..c859bb0a09 --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/policyexecutor/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.policyexecutor + +import com.fasterxml.jackson.databind.ObjectMapper +import org.onap.cps.ncmp.config.PolicyExecutorHttpClientConfig +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 = [ObjectMapper, 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/policyexecutor/PolicyExecutorSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/policyexecutor/PolicyExecutorSpec.groovy new file mode 100644 index 0000000000..63a915ab64 --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/policyexecutor/PolicyExecutorSpec.groovy @@ -0,0 +1,154 @@ +/* + * ============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.policyexecutor + +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.NcmpException +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.slf4j.LoggerFactory +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 + +class PolicyExecutorSpec extends Specification { + + def mockWebClient = Mock(WebClient) + def mockRequestBodyUriSpec = Mock(WebClient.RequestBodyUriSpec) + def mockResponseSpec = Mock(WebClient.ResponseSpec) + def spiedObjectMapper = Spy(ObjectMapper) + + PolicyExecutor objectUnderTest = new PolicyExecutor(mockWebClient, spiedObjectMapper) + + def logAppender = Spy(ListAppender) + + def someValidJson = '{"Hello":"World"}' + + 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 'Permission check with allow response.'() { + given: 'allow response' + mockResponse([decision:'allow'], HttpStatus.OK) + when: 'permission is checked for an operation' + objectUnderTest.checkPermission(new YangModelCmHandle(), operationType, 'my credentials','my resource',someValidJson) + 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 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',someValidJson) + 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',someValidJson) + 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',someValidJson) + 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 with an invalid change request json.'() { + when: 'permission is checked for an invalid change request' + objectUnderTest.checkPermission(new YangModelCmHandle(), CREATE, 'my credentials', 'my resource', 'invalid json string') + then: 'an ncmp exception thrown' + def ncmpException = thrown(NcmpException) + ncmpException.message == 'Cannot convert Change Request data to Object' + ncmpException.details.contains('invalid json string') + } + + 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',someValidJson) + then: 'system logs that the feature not enabled' + assert getLogEntry(0) == 'Policy Executor Enabled: false' + } + + def mockResponse(mockResponseAsMap, httpStatus) { + JsonNode jsonNode = spiedObjectMapper.readTree(spiedObjectMapper.writeValueAsString(mockResponseAsMap)) + def mono = Mono.just(new ResponseEntity<>(jsonNode, httpStatus)) + mockResponseSpec.toEntity(*_) >> mono + } + + def setupLogger() { + def logger = LoggerFactory.getLogger(PolicyExecutor) + logger.setLevel(Level.TRACE) + logger.addAppender(logAppender) + logAppender.start() + } + + def getLogEntry(index) { + logAppender.list[index].formattedMessage + } +} -- cgit 1.2.3-korg