63a915ab64a23e1c4994de55f7747f2628e7e2eb
[cps.git] /
1 /*
2  *  ============LICENSE_START=======================================================
3  *  Copyright (C) 2024 Nordix Foundation
4  *  ================================================================================
5  *  Licensed under the Apache License, Version 2.0 (the "License");
6  *  you may not use this file except in compliance with the License.
7  *  You may obtain a copy of the License at
8  *
9  *        http://www.apache.org/licenses/LICENSE-2.0
10  *
11  *  Unless required by applicable law or agreed to in writing, software
12  *  distributed under the License is distributed on an "AS IS" BASIS,
13  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  *  See the License for the specific language governing permissions and
15  *  limitations under the License.
16  *
17  *  SPDX-License-Identifier: Apache-2.0
18  *  ============LICENSE_END=========================================================
19  */
20
21 package org.onap.cps.ncmp.impl.data.policyexecutor
22
23 import ch.qos.logback.classic.Level
24 import ch.qos.logback.classic.Logger
25 import ch.qos.logback.classic.spi.ILoggingEvent
26 import ch.qos.logback.core.read.ListAppender
27 import com.fasterxml.jackson.databind.JsonNode
28 import com.fasterxml.jackson.databind.ObjectMapper
29 import org.onap.cps.ncmp.api.exceptions.NcmpException
30 import org.onap.cps.ncmp.api.exceptions.PolicyExecutorException
31 import org.onap.cps.ncmp.api.exceptions.ServerNcmpException
32 import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle
33 import org.slf4j.LoggerFactory
34 import org.springframework.http.HttpStatus
35 import org.springframework.http.ResponseEntity
36 import org.springframework.web.reactive.function.client.WebClient
37 import reactor.core.publisher.Mono
38 import spock.lang.Specification
39
40 import static org.onap.cps.ncmp.api.data.models.OperationType.CREATE
41 import static org.onap.cps.ncmp.api.data.models.OperationType.DELETE
42 import static org.onap.cps.ncmp.api.data.models.OperationType.PATCH
43 import static org.onap.cps.ncmp.api.data.models.OperationType.UPDATE
44
45 class PolicyExecutorSpec extends Specification {
46
47     def mockWebClient = Mock(WebClient)
48     def mockRequestBodyUriSpec = Mock(WebClient.RequestBodyUriSpec)
49     def mockResponseSpec = Mock(WebClient.ResponseSpec)
50     def spiedObjectMapper = Spy(ObjectMapper)
51
52     PolicyExecutor objectUnderTest = new PolicyExecutor(mockWebClient, spiedObjectMapper)
53
54     def logAppender = Spy(ListAppender<ILoggingEvent>)
55
56     def someValidJson = '{"Hello":"World"}'
57
58     def setup() {
59         setupLogger()
60         objectUnderTest.enabled = true
61         mockWebClient.post() >> mockRequestBodyUriSpec
62         mockRequestBodyUriSpec.uri(*_) >> mockRequestBodyUriSpec
63         mockRequestBodyUriSpec.header(*_) >> mockRequestBodyUriSpec
64         mockRequestBodyUriSpec.body(*_) >> mockRequestBodyUriSpec
65         mockRequestBodyUriSpec.retrieve() >> mockResponseSpec
66     }
67
68     def cleanup() {
69         ((Logger) LoggerFactory.getLogger(PolicyExecutor)).detachAndStopAllAppenders()
70     }
71
72     def 'Permission check with allow response.'() {
73         given: 'allow response'
74             mockResponse([decision:'allow'], HttpStatus.OK)
75         when: 'permission is checked for an operation'
76             objectUnderTest.checkPermission(new YangModelCmHandle(), operationType, 'my credentials','my resource',someValidJson)
77         then: 'system logs the operation is allowed'
78             assert getLogEntry(2) == 'Policy Executor allows the operation'
79         and: 'no exception occurs'
80             noExceptionThrown()
81         where: 'all write operations are tested'
82             operationType << [ CREATE, DELETE, PATCH, UPDATE ]
83     }
84
85     def 'Permission check with other response (not allowed).'() {
86         given: 'other response'
87             mockResponse([decision:'other', decisionId:123, message:'I dont like Mondays' ], HttpStatus.OK)
88         when: 'permission is checked for an operation'
89             objectUnderTest.checkPermission(new YangModelCmHandle(), PATCH, 'my credentials','my resource',someValidJson)
90         then: 'Policy Executor exception is thrown'
91             def thrownException = thrown(PolicyExecutorException)
92             assert thrownException.message == 'Policy Executor did not allow request. Decision #123 : other'
93             assert thrownException.details == 'I dont like Mondays'
94     }
95
96     def 'Permission check with non 2xx response.'() {
97         given: 'other response'
98             mockResponse([], HttpStatus.I_AM_A_TEAPOT)
99         when: 'permission is checked for an operation'
100             objectUnderTest.checkPermission(new YangModelCmHandle(), PATCH, 'my credentials','my resource',someValidJson)
101         then: 'Server Ncmp exception is thrown'
102             def thrownException = thrown(ServerNcmpException)
103             assert thrownException.message == 'Policy Executor invocation failed'
104             assert thrownException.details == 'HTTP status code: 418'
105     }
106
107     def 'Permission check with invalid response from Policy Executor.'() {
108         given: 'invalid response from Policy executor'
109             mockResponseSpec.toEntity(*_) >> invalidResponse
110         when: 'permission is checked for an operation'
111             objectUnderTest.checkPermission(new YangModelCmHandle(), CREATE, 'my credentials','my resource',someValidJson)
112         then: 'system logs the expected message'
113             assert getLogEntry(1) == expectedMessage
114         where: 'following invalid responses are received'
115             invalidResponse                                        || expectedMessage
116             Mono.empty()                                           || 'No valid response from policy, ignored'
117             Mono.just(new ResponseEntity<>(null, HttpStatus.OK))   || 'No valid response body from policy, ignored'
118     }
119
120     def 'Permission check with an invalid change request json.'() {
121         when: 'permission is checked for an invalid change request'
122             objectUnderTest.checkPermission(new YangModelCmHandle(), CREATE, 'my credentials', 'my resource', 'invalid json string')
123         then: 'an ncmp exception thrown'
124             def ncmpException = thrown(NcmpException)
125             ncmpException.message == 'Cannot convert Change Request data to Object'
126             ncmpException.details.contains('invalid json string')
127     }
128
129     def 'Permission check feature disabled.'() {
130         given: 'feature is disabled'
131             objectUnderTest.enabled = false
132         when: 'permission is checked for an operation'
133             objectUnderTest.checkPermission(new YangModelCmHandle(), PATCH, 'my credentials','my resource',someValidJson)
134         then: 'system logs that the feature not enabled'
135             assert getLogEntry(0) == 'Policy Executor Enabled: false'
136     }
137
138     def mockResponse(mockResponseAsMap, httpStatus) {
139         JsonNode jsonNode = spiedObjectMapper.readTree(spiedObjectMapper.writeValueAsString(mockResponseAsMap))
140         def mono = Mono.just(new ResponseEntity<>(jsonNode, httpStatus))
141         mockResponseSpec.toEntity(*_) >> mono
142     }
143
144     def setupLogger() {
145         def logger = LoggerFactory.getLogger(PolicyExecutor)
146         logger.setLevel(Level.TRACE)
147         logger.addAppender(logAppender)
148         logAppender.start()
149     }
150
151     def getLogEntry(index) {
152         logAppender.list[index].formattedMessage
153     }
154 }