2 * ============LICENSE_START=======================================================
3 * Copyright (C) 2021 Nordix Foundation
4 * Modifications Copyright (C) 2021 Pantheon.tech
5 * Modifications Copyright (C) 2021 Bell Canada.
6 * ================================================================================
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
11 * http://www.apache.org/licenses/LICENSE-2.0
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
19 * SPDX-License-Identifier: Apache-2.0
20 * ============LICENSE_END=========================================================
23 package org.onap.cps.api.impl
25 import org.onap.cps.TestUtils
26 import org.onap.cps.api.CpsAdminService
27 import org.onap.cps.api.CpsModuleService
28 import org.onap.cps.notification.NotificationService
29 import org.onap.cps.spi.CpsDataPersistenceService
30 import org.onap.cps.spi.FetchDescendantsOption
31 import org.onap.cps.spi.exceptions.DataValidationException
32 import org.onap.cps.spi.model.Anchor
33 import org.onap.cps.spi.model.DataNodeBuilder
34 import org.onap.cps.yang.YangTextSchemaSourceSet
35 import org.onap.cps.yang.YangTextSchemaSourceSetBuilder
36 import spock.lang.Specification
38 import java.time.OffsetDateTime
40 class CpsDataServiceImplSpec extends Specification {
41 def mockCpsDataPersistenceService = Mock(CpsDataPersistenceService)
42 def mockCpsAdminService = Mock(CpsAdminService)
43 def mockCpsModuleService = Mock(CpsModuleService)
44 def mockYangTextSchemaSourceSetCache = Mock(YangTextSchemaSourceSetCache)
45 def mockNotificationService = Mock(NotificationService)
47 def objectUnderTest = new CpsDataServiceImpl()
50 objectUnderTest.cpsDataPersistenceService = mockCpsDataPersistenceService
51 objectUnderTest.cpsAdminService = mockCpsAdminService
52 objectUnderTest.cpsModuleService = mockCpsModuleService
53 objectUnderTest.yangTextSchemaSourceSetCache = mockYangTextSchemaSourceSetCache
54 objectUnderTest.notificationService = mockNotificationService
57 def dataspaceName = 'some dataspace'
58 def anchorName = 'some anchor'
59 def schemaSetName = 'some schema set'
60 def observedTimestamp = OffsetDateTime.now()
62 def 'Saving json data.'() {
63 given: 'schema set for given anchor and dataspace references test-tree model'
64 setupSchemaSetMocks('test-tree.yang')
65 when: 'save data method is invoked with test-tree json data'
66 def jsonData = TestUtils.getResourceFileContent('test-tree.json')
67 objectUnderTest.saveData(dataspaceName, anchorName, jsonData, observedTimestamp)
68 then: 'the persistence service method is invoked with correct parameters'
69 1 * mockCpsDataPersistenceService.storeDataNode(dataspaceName, anchorName,
70 { dataNode -> dataNode.xpath == '/test-tree' })
71 and: 'data updated event is sent to notification service'
72 1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, observedTimestamp)
75 def 'Saving child data fragment under existing node.'() {
76 given: 'schema set for given anchor and dataspace references test-tree model'
77 setupSchemaSetMocks('test-tree.yang')
78 when: 'save data method is invoked with test-tree json data'
79 def jsonData = '{"branch": [{"name": "New"}]}'
80 objectUnderTest.saveData(dataspaceName, anchorName, '/test-tree', jsonData, observedTimestamp)
81 then: 'the persistence service method is invoked with correct parameters'
82 1 * mockCpsDataPersistenceService.addChildDataNode(dataspaceName, anchorName, '/test-tree',
83 { dataNode -> dataNode.xpath == '/test-tree/branch[@name=\'New\']' })
84 and: 'data updated event is sent to notification service'
85 1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, observedTimestamp)
88 def 'Saving list element data fragment under existing node.'() {
89 given: 'schema set for given anchor and dataspace references test-tree model'
90 setupSchemaSetMocks('test-tree.yang')
91 when: 'save data method is invoked with list element json data'
92 def jsonData = '{"branch": [{"name": "A"}, {"name": "B"}]}'
93 objectUnderTest.saveListElements(dataspaceName, anchorName, '/test-tree', jsonData, observedTimestamp)
94 then: 'the persistence service method is invoked with correct parameters'
95 1 * mockCpsDataPersistenceService.addListElements(dataspaceName, anchorName, '/test-tree',
96 { dataNodeCollection ->
98 assert dataNodeCollection.size() == 2
99 assert dataNodeCollection.collect { it.getXpath() }
100 .containsAll(['/test-tree/branch[@name=\'A\']', '/test-tree/branch[@name=\'B\']'])
104 and: 'data updated event is sent to notification service'
105 1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, observedTimestamp)
108 def 'Saving empty list element data fragment.'() {
109 given: 'schema set for given anchor and dataspace references test-tree model'
110 setupSchemaSetMocks('test-tree.yang')
111 when: 'save data method is invoked with an empty list'
112 def jsonData = '{"branch": []}'
113 objectUnderTest.saveListElements(dataspaceName, anchorName, '/test-tree', jsonData, observedTimestamp)
114 then: 'invalid data exception is thrown'
115 thrown(DataValidationException)
118 def 'Get data node with option #fetchDescendantsOption.'() {
120 def dataNode = new DataNodeBuilder().withXpath(xpath).build()
121 given: 'persistence service returns data for get data request'
122 mockCpsDataPersistenceService.getDataNode(dataspaceName, anchorName, xpath, fetchDescendantsOption) >> dataNode
123 expect: 'service returns same data if uses same parameters'
124 objectUnderTest.getDataNode(dataspaceName, anchorName, xpath, fetchDescendantsOption) == dataNode
125 where: 'all fetch options are supported'
126 fetchDescendantsOption << FetchDescendantsOption.values()
129 def 'Update data node leaves: #scenario.'() {
130 given: 'schema set for given anchor and dataspace references test-tree model'
131 setupSchemaSetMocks('test-tree.yang')
132 when: 'update data method is invoked with json data #jsonData and parent node xpath #parentNodeXpath'
133 objectUnderTest.updateNodeLeaves(dataspaceName, anchorName, parentNodeXpath, jsonData, observedTimestamp)
134 then: 'the persistence service method is invoked with correct parameters'
135 1 * mockCpsDataPersistenceService.updateDataLeaves(dataspaceName, anchorName, expectedNodeXpath, leaves)
136 and: 'data updated event is sent to notification service'
137 1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, observedTimestamp)
138 where: 'following parameters were used'
139 scenario | parentNodeXpath | jsonData || expectedNodeXpath | leaves
140 'top level node' | '/' | '{"test-tree": {"branch": []}}' || '/test-tree' | Collections.emptyMap()
141 'level 2 node' | '/test-tree' | '{"branch": [{"name":"Name"}]}' || '/test-tree/branch[@name=\'Name\']' | ['name': 'Name']
144 def 'Update list-element data node with : #scenario.'() {
145 given: 'schema set for given anchor and dataspace references bookstore model'
146 setupSchemaSetMocks('bookstore.yang')
147 when: 'update data method is invoked with json data #jsonData and parent node xpath'
148 objectUnderTest.updateNodeLeaves(dataspaceName, anchorName, '/bookstore/categories[@code=2]',
149 jsonData, observedTimestamp)
150 then: 'the persistence service method is invoked with correct parameters'
151 thrown(DataValidationException)
152 where: 'following parameters were used'
154 'multiple expectedLeaves' | '{"code": "01","name": "some-name"}'
155 'one leaf' | '{"name": "some-name"}'
158 def 'Update cm-handle properties' () {
159 given: 'a dmi registry model'
160 setupSchemaSetMocks('dmi-registry.yang')
161 and: 'the expected json string'
162 def jsonData = '{"cm-handles":[{"id":"cmHandle001", "additional-properties":[{"name":"P1"}]}]}'
163 when: 'update data method is invoked with json data and parent node xpath'
164 objectUnderTest.updateNodeLeavesAndExistingDescendantLeaves(dataspaceName, anchorName,
165 '/dmi-registry', jsonData, observedTimestamp)
166 then: 'the persistence service method is invoked with correct parameters'
167 1 * mockCpsDataPersistenceService.updateDataLeaves(dataspaceName, anchorName,
168 "/dmi-registry/cm-handles[@id='cmHandle001']", ['id': 'cmHandle001'])
169 and: 'the data updated event is sent to the notification service'
170 1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, observedTimestamp)
173 def 'Replace data node: #scenario.'() {
174 given: 'schema set for given anchor and dataspace references test-tree model'
175 setupSchemaSetMocks('test-tree.yang')
176 when: 'replace data method is invoked with json data #jsonData and parent node xpath #parentNodeXpath'
177 objectUnderTest.replaceNodeTree(dataspaceName, anchorName, parentNodeXpath, jsonData, observedTimestamp)
178 then: 'the persistence service method is invoked with correct parameters'
179 1 * mockCpsDataPersistenceService.replaceDataNodeTree(dataspaceName, anchorName,
180 { dataNode -> dataNode.xpath == expectedNodeXpath })
181 and: 'data updated event is sent to notification service'
182 1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, observedTimestamp)
183 where: 'following parameters were used'
184 scenario | parentNodeXpath | jsonData || expectedNodeXpath
185 'top level node' | '/' | '{"test-tree": {"branch": []}}' || '/test-tree'
186 'level 2 node' | '/test-tree' | '{"branch": [{"name":"Name"}]}' || '/test-tree/branch[@name=\'Name\']'
189 def 'Replace list content data fragment under parent node.'() {
190 given: 'schema set for given anchor and dataspace references test-tree model'
191 setupSchemaSetMocks('test-tree.yang')
192 when: 'replace list data method is invoked with list element json data'
193 def jsonData = '{"branch": [{"name": "A"}, {"name": "B"}]}'
194 objectUnderTest.replaceListContent(dataspaceName, anchorName, '/test-tree', jsonData, observedTimestamp)
195 then: 'the persistence service method is invoked with correct parameters'
196 1 * mockCpsDataPersistenceService.replaceListContent(dataspaceName, anchorName, '/test-tree',
197 { dataNodeCollection ->
199 assert dataNodeCollection.size() == 2
200 assert dataNodeCollection.collect { it.getXpath() }
201 .containsAll(['/test-tree/branch[@name=\'A\']', '/test-tree/branch[@name=\'B\']'])
205 and: 'data updated event is sent to notification service'
206 1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, observedTimestamp)
209 def 'Replace whole list content with empty list element.'() {
210 given: 'schema set for given anchor and dataspace references test-tree model'
211 setupSchemaSetMocks('test-tree.yang')
212 when: 'replace list data method is invoked with empty list'
213 def jsonData = '{"branch": []}'
214 objectUnderTest.replaceListContent(dataspaceName, anchorName, '/test-tree', jsonData, observedTimestamp)
215 then: 'invalid data exception is thrown'
216 thrown(DataValidationException)
219 def 'Delete list element under existing node.'() {
220 given: 'schema set for given anchor and dataspace references test-tree model'
221 setupSchemaSetMocks('test-tree.yang')
222 when: 'delete list data method is invoked with list element json data'
223 objectUnderTest.deleteListOrListElement(dataspaceName, anchorName, '/test-tree/branch', observedTimestamp)
224 then: 'the persistence service method is invoked with correct parameters'
225 1 * mockCpsDataPersistenceService.deleteListDataNode(dataspaceName, anchorName, '/test-tree/branch')
226 and: 'data updated event is sent to notification service'
227 1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, observedTimestamp)
230 def 'Delete data node under anchor and dataspace.'() {
231 given: 'schema set for given anchor and dataspace references test tree model'
232 setupSchemaSetMocks('test-tree.yang')
233 when: 'delete data node method is invoked with correct parameters'
234 objectUnderTest.deleteDataNode(dataspaceName, anchorName, '/data-node', observedTimestamp)
235 then: 'the persistence service method is invoked with the correct parameters'
236 1 * mockCpsDataPersistenceService.deleteDataNode(dataspaceName, anchorName, '/data-node')
237 and: 'data updated event is sent to notification service'
238 1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, observedTimestamp)
241 def setupSchemaSetMocks(String... yangResources) {
242 def anchor = Anchor.builder().name(anchorName).schemaSetName(schemaSetName).build()
243 mockCpsAdminService.getAnchor(dataspaceName, anchorName) >> anchor
244 def mockYangTextSchemaSourceSet = Mock(YangTextSchemaSourceSet)
245 mockYangTextSchemaSourceSetCache.get(dataspaceName, schemaSetName) >> mockYangTextSchemaSourceSet
246 def yangResourceNameToContent = TestUtils.getYangResourcesAsMap(yangResources)
247 def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent).getSchemaContext()
248 mockYangTextSchemaSourceSet.getSchemaContext() >> schemaContext