2 * ============LICENSE_START=======================================================
3 * Copyright (C) 2021-2022 Nordix Foundation
4 * Modifications Copyright (C) 2021 Pantheon.tech
5 * Modifications Copyright (C) 2021-2022 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.notification.NotificationService
28 import org.onap.cps.notification.Operation
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 mockYangTextSchemaSourceSetCache = Mock(YangTextSchemaSourceSetCache)
44 def mockNotificationService = Mock(NotificationService)
46 def objectUnderTest = new CpsDataServiceImpl(mockCpsDataPersistenceService, mockCpsAdminService,
47 mockYangTextSchemaSourceSetCache, mockNotificationService)
50 mockCpsAdminService.getAnchor(dataspaceName, anchorName) >> anchor
53 def dataspaceName = 'some dataspace'
54 def anchorName = 'some anchor'
55 def schemaSetName = 'some schema set'
56 def anchor = Anchor.builder().name(anchorName).schemaSetName(schemaSetName).build()
57 def observedTimestamp = OffsetDateTime.now()
59 def 'Saving json data.'() {
60 given: 'schema set for given anchor and dataspace references test-tree model'
61 setupSchemaSetMocks('test-tree.yang')
62 when: 'save data method is invoked with test-tree json data'
63 def jsonData = TestUtils.getResourceFileContent('test-tree.json')
64 objectUnderTest.saveData(dataspaceName, anchorName, jsonData, observedTimestamp)
65 then: 'the persistence service method is invoked with correct parameters'
66 1 * mockCpsDataPersistenceService.storeDataNode(dataspaceName, anchorName,
67 { dataNode -> dataNode.xpath == '/test-tree' })
68 and: 'data updated event is sent to notification service'
69 1 * mockNotificationService.processDataUpdatedEvent(anchor, observedTimestamp, '/', Operation.CREATE)
72 def 'Saving child data fragment under existing node.'() {
73 given: 'schema set for given anchor and dataspace references test-tree model'
74 setupSchemaSetMocks('test-tree.yang')
75 when: 'save data method is invoked with test-tree json data'
76 def jsonData = '{"branch": [{"name": "New"}]}'
77 objectUnderTest.saveData(dataspaceName, anchorName, '/test-tree', jsonData, observedTimestamp)
78 then: 'the persistence service method is invoked with correct parameters'
79 1 * mockCpsDataPersistenceService.addChildDataNode(dataspaceName, anchorName, '/test-tree',
80 { dataNode -> dataNode.xpath == '/test-tree/branch[@name=\'New\']' })
81 and: 'data updated event is sent to notification service'
82 1 * mockNotificationService.processDataUpdatedEvent(anchor, observedTimestamp, '/test-tree', Operation.CREATE)
85 def 'Saving list element data fragment under existing node.'() {
86 given: 'schema set for given anchor and dataspace references test-tree model'
87 setupSchemaSetMocks('test-tree.yang')
88 when: 'save data method is invoked with list element json data'
89 def jsonData = '{"branch": [{"name": "A"}, {"name": "B"}]}'
90 objectUnderTest.saveListElements(dataspaceName, anchorName, '/test-tree', jsonData, observedTimestamp)
91 then: 'the persistence service method is invoked with correct parameters'
92 1 * mockCpsDataPersistenceService.addListElements(dataspaceName, anchorName, '/test-tree',
93 { dataNodeCollection ->
95 assert dataNodeCollection.size() == 2
96 assert dataNodeCollection.collect { it.getXpath() }
97 .containsAll(['/test-tree/branch[@name=\'A\']', '/test-tree/branch[@name=\'B\']'])
101 and: 'data updated event is sent to notification service'
102 1 * mockNotificationService.processDataUpdatedEvent(anchor, observedTimestamp, '/test-tree', Operation.UPDATE)
105 def 'Saving empty list element data fragment.'() {
106 given: 'schema set for given anchor and dataspace references test-tree model'
107 setupSchemaSetMocks('test-tree.yang')
108 when: 'save data method is invoked with an empty list'
109 def jsonData = '{"branch": []}'
110 objectUnderTest.saveListElements(dataspaceName, anchorName, '/test-tree', jsonData, observedTimestamp)
111 then: 'invalid data exception is thrown'
112 thrown(DataValidationException)
115 def 'Get data node with option #fetchDescendantsOption.'() {
117 def dataNode = new DataNodeBuilder().withXpath(xpath).build()
118 given: 'persistence service returns data for get data request'
119 mockCpsDataPersistenceService.getDataNode(dataspaceName, anchorName, xpath, fetchDescendantsOption) >> dataNode
120 expect: 'service returns same data if uses same parameters'
121 objectUnderTest.getDataNode(dataspaceName, anchorName, xpath, fetchDescendantsOption) == dataNode
122 where: 'all fetch options are supported'
123 fetchDescendantsOption << FetchDescendantsOption.values()
126 def 'Update data node leaves: #scenario.'() {
127 given: 'schema set for given anchor and dataspace references test-tree model'
128 setupSchemaSetMocks('test-tree.yang')
129 when: 'update data method is invoked with json data #jsonData and parent node xpath #parentNodeXpath'
130 objectUnderTest.updateNodeLeaves(dataspaceName, anchorName, parentNodeXpath, jsonData, observedTimestamp)
131 then: 'the persistence service method is invoked with correct parameters'
132 1 * mockCpsDataPersistenceService.updateDataLeaves(dataspaceName, anchorName, expectedNodeXpath, leaves)
133 and: 'data updated event is sent to notification service'
134 1 * mockNotificationService.processDataUpdatedEvent(anchor, observedTimestamp, parentNodeXpath, Operation.UPDATE)
135 where: 'following parameters were used'
136 scenario | parentNodeXpath | jsonData || expectedNodeXpath | leaves
137 'top level node' | '/' | '{"test-tree": {"branch": []}}' || '/test-tree' | Collections.emptyMap()
138 'level 2 node' | '/test-tree' | '{"branch": [{"name":"Name"}]}' || '/test-tree/branch[@name=\'Name\']' | ['name': 'Name']
141 def 'Update list-element data node with : #scenario.'() {
142 given: 'schema set for given anchor and dataspace references bookstore model'
143 setupSchemaSetMocks('bookstore.yang')
144 when: 'update data method is invoked with json data #jsonData and parent node xpath'
145 objectUnderTest.updateNodeLeaves(dataspaceName, anchorName, '/bookstore/categories[@code=2]',
146 jsonData, observedTimestamp)
147 then: 'the persistence service method is invoked with correct parameters'
148 thrown(DataValidationException)
149 where: 'following parameters were used'
151 'multiple expectedLeaves' | '{"code": "01","name": "some-name"}'
152 'one leaf' | '{"name": "some-name"}'
155 def 'Update Bookstore node leaves' () {
156 given: 'a DMI registry model'
157 setupSchemaSetMocks('bookstore.yang')
158 and: 'the expected json string'
159 def jsonData = '{"categories":[{"code":01,"name":"Romance"}]}'
160 when: 'update data method is invoked with json data and parent node xpath'
161 objectUnderTest.updateNodeLeavesAndExistingDescendantLeaves(dataspaceName, anchorName,
162 '/bookstore', jsonData, observedTimestamp)
163 then: 'the persistence service method is invoked with correct parameters'
164 1 * mockCpsDataPersistenceService.updateDataLeaves(dataspaceName, anchorName,
165 "/bookstore/categories[@code='01']", ['name':'Romance', 'code': '01'])
166 and: 'the data updated event is sent to the notification service'
167 1 * mockNotificationService.processDataUpdatedEvent(anchor, observedTimestamp, '/bookstore', Operation.UPDATE)
170 def 'Replace data node: #scenario.'() {
171 given: 'schema set for given anchor and dataspace references test-tree model'
172 setupSchemaSetMocks('test-tree.yang')
173 when: 'replace data method is invoked with json data #jsonData and parent node xpath #parentNodeXpath'
174 objectUnderTest.replaceNodeTree(dataspaceName, anchorName, parentNodeXpath, jsonData, observedTimestamp)
175 then: 'the persistence service method is invoked with correct parameters'
176 1 * mockCpsDataPersistenceService.replaceDataNodeTree(dataspaceName, anchorName,
177 { dataNode -> dataNode.xpath == expectedNodeXpath })
178 and: 'data updated event is sent to notification service'
179 1 * mockNotificationService.processDataUpdatedEvent(anchor, observedTimestamp, parentNodeXpath, Operation.UPDATE)
180 where: 'following parameters were used'
181 scenario | parentNodeXpath | jsonData || expectedNodeXpath
182 'top level node' | '/' | '{"test-tree": {"branch": []}}' || '/test-tree'
183 'level 2 node' | '/test-tree' | '{"branch": [{"name":"Name"}]}' || '/test-tree/branch[@name=\'Name\']'
186 def 'Replace list content data fragment under parent node.'() {
187 given: 'schema set for given anchor and dataspace references test-tree model'
188 setupSchemaSetMocks('test-tree.yang')
189 when: 'replace list data method is invoked with list element json data'
190 def jsonData = '{"branch": [{"name": "A"}, {"name": "B"}]}'
191 objectUnderTest.replaceListContent(dataspaceName, anchorName, '/test-tree', jsonData, observedTimestamp)
192 then: 'the persistence service method is invoked with correct parameters'
193 1 * mockCpsDataPersistenceService.replaceListContent(dataspaceName, anchorName, '/test-tree',
194 { dataNodeCollection ->
196 assert dataNodeCollection.size() == 2
197 assert dataNodeCollection.collect { it.getXpath() }
198 .containsAll(['/test-tree/branch[@name=\'A\']', '/test-tree/branch[@name=\'B\']'])
202 and: 'data updated event is sent to notification service'
203 1 * mockNotificationService.processDataUpdatedEvent(anchor, observedTimestamp, '/test-tree', Operation.UPDATE)
206 def 'Replace whole list content with empty list element.'() {
207 given: 'schema set for given anchor and dataspace references test-tree model'
208 setupSchemaSetMocks('test-tree.yang')
209 when: 'replace list data method is invoked with empty list'
210 def jsonData = '{"branch": []}'
211 objectUnderTest.replaceListContent(dataspaceName, anchorName, '/test-tree', jsonData, observedTimestamp)
212 then: 'invalid data exception is thrown'
213 thrown(DataValidationException)
216 def 'Delete list element under existing node.'() {
217 given: 'schema set for given anchor and dataspace references test-tree model'
218 setupSchemaSetMocks('test-tree.yang')
219 when: 'delete list data method is invoked with list element json data'
220 objectUnderTest.deleteListOrListElement(dataspaceName, anchorName, '/test-tree/branch', observedTimestamp)
221 then: 'the persistence service method is invoked with correct parameters'
222 1 * mockCpsDataPersistenceService.deleteListDataNode(dataspaceName, anchorName, '/test-tree/branch')
223 and: 'data updated event is sent to notification service'
224 1 * mockNotificationService.processDataUpdatedEvent(anchor, observedTimestamp, '/test-tree/branch', Operation.DELETE)
227 def 'Delete data node under anchor and dataspace.'() {
228 given: 'schema set for given anchor and dataspace references test tree model'
229 setupSchemaSetMocks('test-tree.yang')
230 when: 'delete data node method is invoked with correct parameters'
231 objectUnderTest.deleteDataNode(dataspaceName, anchorName, '/data-node', observedTimestamp)
232 then: 'the persistence service method is invoked with the correct parameters'
233 1 * mockCpsDataPersistenceService.deleteDataNode(dataspaceName, anchorName, '/data-node')
234 and: 'data updated event is sent to notification service'
235 1 * mockNotificationService.processDataUpdatedEvent(anchor, observedTimestamp, '/data-node', Operation.DELETE)
238 def 'Delete all data nodes for a given anchor and dataspace.'() {
239 given: 'schema set for given anchor and dataspace references test tree model'
240 setupSchemaSetMocks('test-tree.yang')
241 when: 'delete data node method is invoked with correct parameters'
242 objectUnderTest.deleteDataNodes(dataspaceName, anchorName, observedTimestamp)
243 then: 'the persistence service method is invoked with the correct parameters'
244 1 * mockCpsDataPersistenceService.deleteDataNodes(dataspaceName, anchorName)
245 and: 'data updated event is sent to notification service'
246 1 * mockNotificationService.processDataUpdatedEvent(anchor, observedTimestamp, '/', Operation.DELETE)
250 def setupSchemaSetMocks(String... yangResources) {
251 def mockYangTextSchemaSourceSet = Mock(YangTextSchemaSourceSet)
252 mockYangTextSchemaSourceSetCache.get(dataspaceName, schemaSetName) >> mockYangTextSchemaSourceSet
253 def yangResourceNameToContent = TestUtils.getYangResourcesAsMap(yangResources)
254 def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent).getSchemaContext()
255 mockYangTextSchemaSourceSet.getSchemaContext() >> schemaContext
258 def 'start session'() {
259 when: 'start session method is called'
260 objectUnderTest.startSession()
261 then: 'the persistence service method to start session is invoked'
262 1 * mockCpsDataPersistenceService.startSession()
265 def 'close session'(){
266 given: 'session Id from calling the start session method'
267 def sessionId = objectUnderTest.startSession()
268 when: 'close session method is called'
269 objectUnderTest.closeSession(sessionId)
270 then: 'the persistence service method to close session is invoked'
271 1 * mockCpsDataPersistenceService.closeSession(sessionId)