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
9 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 * SPDX-License-Identifier: Apache-2.0
18 * ============LICENSE_END=========================================================
21 package org.onap.cps.ncmp.impl.data.policyexecutor
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.impl.provmns.RequestPathParameters;
27 import org.onap.cps.ncmp.impl.provmns.model.PatchItem;
28 import org.onap.cps.ncmp.impl.provmns.model.ResourceOneOf
29 import org.onap.cps.utils.JsonObjectMapper;
30 import spock.lang.Specification;
32 import static org.onap.cps.ncmp.api.data.models.OperationType.CREATE;
34 class OperationDetailsFactorySpec extends Specification {
36 def spiedObjectMapper = Spy(ObjectMapper)
37 def jsonObjectMapper = new JsonObjectMapper(spiedObjectMapper)
39 OperationDetailsFactory objectUnderTest = new OperationDetailsFactory(jsonObjectMapper, spiedObjectMapper)
41 static def complexValueAsResource = new ResourceOneOf(id: 'myId', attributes: ['myAttribute1:myValue1', 'myAttribute2:myValue2'], objectClass: 'myClassName')
42 static def simpleValueAsResource = new ResourceOneOf(id: 'myId', attributes: ['simpleAttribute:1'], objectClass: 'myClassName')
45 def 'Build policy executor patch operation details from ProvMnS request parameters where #scenario.'() {
46 given: 'a provMnsRequestParameter and a patchItem list'
47 def path = new RequestPathParameters(uriLdnFirstPart: 'myUriLdnFirstPart', className: 'classNameInUri', id: 'myId')
48 def resource = new ResourceOneOf(id: 'someResourceId', attributes: ['someAttribute1:someValue1', 'someAttribute2:someValue2'], objectClass: classNameInBody)
49 def patchItemsList = [new PatchItem(op: 'ADD', 'path':'myUriLdnFirstPart', value: resource), new PatchItem(op: 'REPLACE', 'path':'myUriLdnFirstPart', value: resource), new PatchItem(op: 'REMOVE', 'path':'myUriLdnFirstPart'),]
50 when: 'patch operation details are created'
51 def result = objectUnderTest.buildPatchOperationDetails(path, patchItemsList)
52 then: 'the result contain 3 operations of the correct types in the correct order'
53 result.operations.size() == 3
54 and: 'note that Add and Replace both are defined using Create Operation Details'
55 assert result.operations[0] instanceof CreateOperationDetails
56 assert result.operations[1] instanceof CreateOperationDetails
57 assert result.operations[2] instanceof DeleteOperationDetails
58 and: 'the add operation target identifier is just the uri first part'
59 assert result.operations[0]['targetIdentifier'] == 'myUriLdnFirstPart'
60 and: 'the replace operation target identifier is just the uri first part'
61 assert result.operations[1]['targetIdentifier'] == 'myUriLdnFirstPart'
62 and: 'the replace change request has the correct class name'
63 assert result.operations[1].changeRequest.keySet()[0] == expectedChangeRequestKey
64 and: 'the delete operation target identifier includes the target class and id'
65 assert result.operations[2]['targetIdentifier'] == 'myUriLdnFirstPart/classNameInUri=myId'
66 where: 'the following class names are used in the body'
67 scenario | classNameInBody || expectedChangeRequestKey
68 'class name in body is populated' | 'myClass' || 'myClass'
69 'class name in body is empty' | '' || 'classNameInUri'
70 'class name in body is null' | null || 'classNameInUri'
73 def 'Build policy executor patch operation details with single replace operation and #scenario.'() {
74 given: 'a requestParameter and a patchItem list'
75 def path = new RequestPathParameters(uriLdnFirstPart: 'myUriLdnFirstPart', className: 'myClassName', id: 'myId')
76 def pathItems = [new PatchItem(op: 'REPLACE', 'path':"myUriLdnFirstPart${suffix}", value: value)]
77 when: 'patch operation details are created'
78 def result = objectUnderTest.buildPatchOperationDetails(path, pathItems)
79 then: 'the result has the correct type'
80 assert result instanceof PatchOperationsDetails
81 and: 'the change request contains the correct attributes value'
82 assert result.operations[0]['changeRequest']['myClassName'][0]['attributes'].toString() == attributesValueInOperation
83 where: 'attributes are set using # or resource'
84 scenario | suffix | value || attributesValueInOperation
85 'set simple value using #' | '#/attributes/simpleAttribute' | 1 || '[simpleAttribute:1]'
86 'set simple value using resource' | '' | simpleValueAsResource || '[simpleAttribute:1]'
87 'set complex value using resource' | '' | complexValueAsResource || '[myAttribute1:myValue1, myAttribute2:myValue2]'
90 def 'Build an attribute map with different depths of hierarchy with #scenario.'() {
91 given: 'a patch item with a path'
92 def patchItem = new PatchItem(op: 'REPLACE', 'path':path, value: 123)
93 when: 'transforming the attributes'
94 def hierarchyMap = objectUnderTest.createNestedMap(patchItem)
95 then: 'the map depth is equal to the expected number of attributes'
96 assert hierarchyMap.get(expectedAttributeName).toString() == expectedAttributeValue
97 where: 'simple and complex attributes are tested'
98 scenario | path || expectedAttributeName || expectedAttributeValue
99 'set a simple attribute' | 'myUriLdnFirstPart#/attributes/simpleAttribute' || 'simpleAttribute' || '123'
100 'set a simple attribute with a trailing /' | 'myUriLdnFirstPart#/attributes/simpleAttribute/' || 'simpleAttribute' || '123'
101 'set a complex attribute' | 'myUriLdnFirstPart#/attributes/complexAttribute/simpleAttribute' || 'complexAttribute' || '[simpleAttribute:123]'
104 def 'Build policy executor patch operation details from ProvMnS request parameters with invalid op.'() {
105 given: 'a provMnsRequestParameter and a patchItem list'
106 def path = new RequestPathParameters(uriLdnFirstPart: 'myUriLdnFirstPart', className: 'someClassName', id: 'someId')
107 def patchItemsList = [new PatchItem(op: 'TEST', 'path':'myUriLdnFirstPart')]
108 when: 'a configurationManagementOperation is created and converted to JSON'
109 def result = objectUnderTest.buildPatchOperationDetails(path, patchItemsList)
110 then: 'the result is as expected (using json to compare)'
111 def expectedJsonString = '{"permissionId":"Some Permission Id","changeRequestFormat":"cm-legacy","operations":[]}'
112 assert expectedJsonString == jsonObjectMapper.asJsonString(result)
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
125 scenario | objectClass || changeRequestClassReference
126 'objectClass is populated' | 'someObjectClass' || 'someObjectClass'
127 'objectClass is empty' | '' || 'someClassName'
128 'objectClass is null' | null || 'someClassName'
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')