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.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;
33 import static org.onap.cps.ncmp.api.data.models.OperationType.CREATE;
35 class OperationDetailsFactorySpec extends Specification {
37 def spiedObjectMapper = Spy(ObjectMapper)
38 def jsonObjectMapper = new JsonObjectMapper(spiedObjectMapper)
40 OperationDetailsFactory objectUnderTest = new OperationDetailsFactory(jsonObjectMapper, spiedObjectMapper)
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')
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'
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]'
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]'
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)
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')