61a3ceed35d4cf84a4e8f94297f3a3fa1a17bca9
[cps.git] /
1 /*
2  *  ============LICENSE_START=======================================================
3  *  Copyright (C) 2024-2025 OpenInfra Foundation Europe. All rights reserved.
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 com.fasterxml.jackson.core.JsonProcessingException
24 import com.fasterxml.jackson.databind.ObjectMapper
25 import org.onap.cps.ncmp.api.exceptions.NcmpException
26 import org.onap.cps.ncmp.api.exceptions.ProvMnSException;
27 import org.onap.cps.ncmp.impl.provmns.RequestPathParameters;
28 import org.onap.cps.ncmp.impl.provmns.model.PatchItem;
29 import org.onap.cps.ncmp.impl.provmns.model.ResourceOneOf
30 import org.onap.cps.utils.JsonObjectMapper;
31 import spock.lang.Specification;
32
33 import static org.onap.cps.ncmp.api.data.models.OperationType.CREATE;
34
35 class OperationDetailsFactorySpec extends Specification {
36
37     def spiedObjectMapper = Spy(ObjectMapper)
38     def jsonObjectMapper = new JsonObjectMapper(spiedObjectMapper)
39
40     OperationDetailsFactory objectUnderTest = new OperationDetailsFactory(jsonObjectMapper, spiedObjectMapper)
41
42     static def complexValueAsResource = new ResourceOneOf(id: 'myId', attributes: ['myAttribute1:myValue1', 'myAttribute2:myValue2'], objectClass: 'myClassName')
43     static def simpleValueAsResource = new ResourceOneOf(id: 'myId', attributes: ['simpleAttribute:1'], objectClass: 'myClassName')
44
45
46     def 'Build policy executor patch operation details from ProvMnS request parameters where #scenario.'() {
47         given: 'a provMnsRequestParameter and a patchItem list'
48             def path = new RequestPathParameters(uriLdnFirstPart: 'myUriLdnFirstPart', className: 'classNameInUri', id: 'myId')
49             def resource = new ResourceOneOf(id: 'someResourceId', attributes: ['someAttribute1:someValue1', 'someAttribute2:someValue2'], objectClass: classNameInBody)
50             def patchItemsList = [new PatchItem(op: 'ADD', 'path':'myUriLdnFirstPart', value: resource), new PatchItem(op: 'REPLACE', 'path':'myUriLdnFirstPart', value: resource), new PatchItem(op: 'REMOVE', 'path':'myUriLdnFirstPart'),]
51         when: 'patch operation details are created'
52             def result = objectUnderTest.buildPatchOperationDetails(path, patchItemsList)
53         then: 'the result contain 3 operations of the correct types in the correct order'
54             result.operations.size() == 3
55         and: 'note that Add and Replace both are defined using Create Operation Details'
56             assert result.operations[0] instanceof CreateOperationDetails
57             assert result.operations[1] instanceof CreateOperationDetails
58             assert result.operations[2] instanceof DeleteOperationDetails
59         and: 'the add operation target identifier is just the uri first part'
60             assert result.operations[0]['targetIdentifier'] == 'myUriLdnFirstPart'
61         and: 'the replace operation target identifier is just the uri first part'
62             assert result.operations[1]['targetIdentifier'] == 'myUriLdnFirstPart'
63         and: 'the replace change request has the correct class name'
64             assert result.operations[1].changeRequest.keySet()[0] == expectedChangeRequestKey
65         and: 'the delete operation target identifier includes the target class and id'
66             assert result.operations[2]['targetIdentifier'] == 'myUriLdnFirstPart/classNameInUri=myId'
67         where: 'the following class names are used in the body'
68             scenario                          | classNameInBody || expectedChangeRequestKey
69             'class name in body is populated' | 'myClass'       || 'myClass'
70             'class name in body  is empty'    | ''              || 'classNameInUri'
71             'class name in body  is null'     | null            || 'classNameInUri'
72     }
73
74     def 'Build policy executor patch operation details with single replace operation and #scenario.'() {
75         given: 'a requestParameter and a patchItem list'
76             def path = new RequestPathParameters(uriLdnFirstPart: 'myUriLdnFirstPart', className: 'myClassName', id: 'myId')
77             def pathItems = [new PatchItem(op: 'REPLACE', 'path':"myUriLdnFirstPart${suffix}", value: value)]
78         when: 'patch operation details are created'
79             def result = objectUnderTest.buildPatchOperationDetails(path, pathItems)
80         then: 'the result has the correct type'
81             assert result instanceof PatchOperationsDetails
82         and: 'the change request contains the correct attributes value'
83             assert result.operations[0]['changeRequest']['myClassName'][0]['attributes'].toString() == attributesValueInOperation
84         where: 'attributes are set using # or resource'
85             scenario                           | suffix                         | value                  || attributesValueInOperation
86             'set simple value using #'         | '#/attributes/simpleAttribute' | 1                      || '[simpleAttribute:1]'
87             'set simple value using resource'  | ''                             | simpleValueAsResource  || '[simpleAttribute:1]'
88             'set complex value using resource' | ''                             | complexValueAsResource || '[myAttribute1:myValue1, myAttribute2:myValue2]'
89     }
90
91     def 'Build an attribute map with different depths of hierarchy with #scenario.'() {
92         given: 'a patch item with a path'
93             def patchItem = new PatchItem(op: 'REPLACE', 'path':path, value: 123)
94         when: 'transforming the attributes'
95             def hierarchyMap = objectUnderTest.createNestedMap(patchItem)
96         then: 'the map depth is equal to the expected number of attributes'
97             assert hierarchyMap.get(expectedAttributeName).toString() == expectedAttributeValue
98         where: 'simple and complex attributes are tested'
99             scenario                                   | path                                                             || expectedAttributeName || expectedAttributeValue
100             'set a simple attribute'                   | 'myUriLdnFirstPart#/attributes/simpleAttribute'                  || 'simpleAttribute'     || '123'
101             'set a simple attribute with a trailing /' | 'myUriLdnFirstPart#/attributes/simpleAttribute/'                 || 'simpleAttribute'     || '123'
102             'set a complex attribute'                  | 'myUriLdnFirstPart#/attributes/complexAttribute/simpleAttribute' || 'complexAttribute'    || '[simpleAttribute:123]'
103     }
104
105     def 'Build policy executor patch operation details from ProvMnS request parameters with invalid op.'() {
106         given: 'a provMnsRequestParameter and a patchItem list'
107             def path = new RequestPathParameters(uriLdnFirstPart: 'myUriLdnFirstPart', className: 'someClassName', id: 'someId')
108             def patchItemsList = [new PatchItem(op: 'TEST', 'path':'myUriLdnFirstPart')]
109         when: 'a build is attempted with an invalid op'
110             objectUnderTest.buildPatchOperationDetails(path, patchItemsList)
111         then: 'the result is as expected (exception thrown)'
112             thrown(ProvMnSException)
113     }
114
115     def 'Build policy executor create operation details from ProvMnS request parameters where #scenario.'() {
116         given: 'a provMnsRequestParameter and a resource'
117             def path = new RequestPathParameters(uriLdnFirstPart: 'myUriLdnFirstPart', className: 'someClassName', id: 'someId')
118             def resource = new ResourceOneOf(id: 'someResourceId', attributes: ['someAttribute1:someValue1', 'someAttribute2:someValue2'], objectClass: objectClass)
119         when: 'a configurationManagementOperation is created and converted to JSON'
120             def result = objectUnderTest.buildCreateOperationDetails(CREATE, path, resource)
121         then: 'the result is as expected (using json to compare)'
122             String expectedJsonString = '{"operation":"CREATE","targetIdentifier":"myUriLdnFirstPart","changeRequest":{"' + changeRequestClassReference + '":[{"id":"someId","attributes":["someAttribute1:someValue1","someAttribute2:someValue2"]}]}}'
123             assert jsonObjectMapper.asJsonString(result) == expectedJsonString
124         where:
125             scenario                   | objectClass        || changeRequestClassReference
126             'objectClass is populated' | 'someObjectClass'  || 'someObjectClass'
127             'objectClass is empty'     | ''                 || 'someClassName'
128             'objectClass is null'      | null               || 'someClassName'
129     }
130
131     def 'Build Policy Executor Operation Details with a exception during conversion'() {
132         given: 'a provMnsRequestParameter and a resource'
133             def path = new RequestPathParameters(uriLdnFirstPart: 'myUriLdnFirstPart', className: 'someClassName', id: 'someId')
134             def resource = new ResourceOneOf(id: 'myResourceId', attributes: ['someAttribute1:someValue1', 'someAttribute2:someValue2'])
135         and: 'json object mapper throws an exception'
136             def originalException = new JsonProcessingException('some-exception')
137             spiedObjectMapper.readValue(*_) >> {throw originalException}
138         when: 'a configurationManagementOperation is created and converted to JSON'
139             objectUnderTest.buildCreateOperationDetails(CREATE, path, resource)
140         then: 'the expected exception is throw and matches the original'
141             def thrown = thrown(NcmpException)
142             assert thrown.message.contains('Cannot convert Resource Object')
143             assert thrown.details.contains('some-exception')
144     }
145
146 }