2 * ============LICENSE_START=======================================================
3 * Copyright (C) 2021-2023 Nordix Foundation
4 * Modifications Copyright (C) 2021 Pantheon.tech
5 * Modifications Copyright (C) 2021-2022 Bell Canada.
6 * Modifications Copyright (C) 2022-2023 TechMahindra Ltd.
7 * Modifications Copyright (C) 2022 Deutsche Telekom AG
8 * Licensed under the Apache License, Version 2.0 (the "License");
9 * you may not use this file except in compliance with the License.
10 * You may obtain a copy of the License at
12 * http://www.apache.org/licenses/LICENSE-2.0
14 * Unless required by applicable law or agreed to in writing, software
15 * distributed under the License is distributed on an "AS IS" BASIS,
16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 * See the License for the specific language governing permissions and
18 * limitations under the License.
20 * SPDX-License-Identifier: Apache-2.0
21 * ============LICENSE_END=========================================================
24 package org.onap.cps.api.impl
26 import org.onap.cps.TestUtils
27 import org.onap.cps.api.CpsAdminService
28 import org.onap.cps.notification.NotificationService
29 import org.onap.cps.notification.Operation
30 import org.onap.cps.spi.CpsDataPersistenceService
31 import org.onap.cps.spi.FetchDescendantsOption
32 import org.onap.cps.spi.exceptions.DataValidationException
33 import org.onap.cps.spi.model.Anchor
34 import org.onap.cps.spi.model.DataNode
35 import org.onap.cps.spi.model.DataNodeBuilder
36 import org.onap.cps.utils.ContentType
37 import org.onap.cps.utils.TimedYangParser
38 import org.onap.cps.yang.YangTextSchemaSourceSet
39 import org.onap.cps.yang.YangTextSchemaSourceSetBuilder
40 import spock.lang.Specification
41 import org.onap.cps.spi.utils.CpsValidator
43 import java.time.OffsetDateTime
44 import java.util.stream.Collectors
46 class CpsDataServiceImplSpec extends Specification {
47 def mockCpsDataPersistenceService = Mock(CpsDataPersistenceService)
48 def mockCpsAdminService = Mock(CpsAdminService)
49 def mockYangTextSchemaSourceSetCache = Mock(YangTextSchemaSourceSetCache)
50 def mockNotificationService = Mock(NotificationService)
51 def mockCpsValidator = Mock(CpsValidator)
52 def timedYangParser = new TimedYangParser()
54 def objectUnderTest = new CpsDataServiceImpl(mockCpsDataPersistenceService, mockCpsAdminService,
55 mockYangTextSchemaSourceSetCache, mockNotificationService, mockCpsValidator, timedYangParser)
58 mockCpsAdminService.getAnchor(dataspaceName, anchorName) >> anchor
61 def dataspaceName = 'some-dataspace'
62 def anchorName = 'some-anchor'
63 def schemaSetName = 'some-schema-set'
64 def anchor = Anchor.builder().name(anchorName).dataspaceName(dataspaceName).schemaSetName(schemaSetName).build()
65 def observedTimestamp = OffsetDateTime.now()
67 def 'Saving #scenario data.'() {
68 given: 'schema set for given anchor and dataspace references test-tree model'
69 setupSchemaSetMocks('test-tree.yang')
70 when: 'save data method is invoked with test-tree #scenario data'
71 def data = TestUtils.getResourceFileContent(dataFile)
72 objectUnderTest.saveData(dataspaceName, anchorName, data, observedTimestamp, contentType)
73 then: 'the persistence service method is invoked with correct parameters'
74 1 * mockCpsDataPersistenceService.storeDataNodes(dataspaceName, anchorName,
75 { dataNode -> dataNode.xpath[0] == '/test-tree' })
76 and: 'the CpsValidator is called on the dataspaceName and AnchorName'
77 1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
78 and: 'data updated event is sent to notification service'
79 1 * mockNotificationService.processDataUpdatedEvent(anchor, '/', Operation.CREATE, observedTimestamp)
80 where: 'given parameters'
81 scenario | dataFile | contentType
82 'json' | 'test-tree.json' | ContentType.JSON
83 'xml' | 'test-tree.xml' | ContentType.XML
86 def 'Saving data with error: #scenario.'() {
87 given: 'schema set for given anchor and dataspace references test-tree model'
88 setupSchemaSetMocks('test-tree.yang')
89 when: 'save data method is invoked with test-tree json data'
90 objectUnderTest.saveData(dataspaceName, anchorName, invalidData, observedTimestamp, contentType)
91 then: 'a data validation exception is thrown with the correct message'
92 def exceptionThrown = thrown(DataValidationException)
93 assert exceptionThrown.message.startsWith(expectedMessage)
94 where: 'given parameters'
95 scenario | invalidData | contentType || expectedMessage
96 'no data nodes' | '{}' | ContentType.JSON || 'No data nodes'
97 'invalid json' | '{invalid json' | ContentType.JSON || 'Failed to parse json data'
98 'invalid xml' | '<invalid xml' | ContentType.XML || 'Failed to parse xml data'
101 def 'Saving #scenarioDesired data exception during notification.'() {
102 given: 'schema set for given anchor and dataspace references test-tree model'
103 setupSchemaSetMocks('test-tree.yang')
104 and: 'the notification service throws an exception'
105 mockNotificationService.processDataUpdatedEvent(*_) >> { throw new RuntimeException('to be ignored')}
106 when: 'save data method is invoked with test-tree json data'
107 def data = TestUtils.getResourceFileContent('test-tree.json')
108 objectUnderTest.saveData(dataspaceName, anchorName, data, observedTimestamp)
109 then: 'the exception is ignored'
113 def 'Saving child data fragment under existing node.'() {
114 given: 'schema set for given anchor and dataspace references test-tree model'
115 setupSchemaSetMocks('test-tree.yang')
116 when: 'save data method is invoked with test-tree json data'
117 def jsonData = '{"branch": [{"name": "New"}]}'
118 objectUnderTest.saveData(dataspaceName, anchorName, '/test-tree', jsonData, observedTimestamp)
119 then: 'the persistence service method is invoked with correct parameters'
120 1 * mockCpsDataPersistenceService.addChildDataNodes(dataspaceName, anchorName, '/test-tree',
121 { dataNode -> dataNode.xpath[0] == '/test-tree/branch[@name=\'New\']' })
122 and: 'the CpsValidator is called on the dataspaceName and AnchorName'
123 1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
124 and: 'data updated event is sent to notification service'
125 1 * mockNotificationService.processDataUpdatedEvent(anchor, '/test-tree', Operation.CREATE, observedTimestamp)
128 def 'Saving list element data fragment under existing node.'() {
129 given: 'schema set for given anchor and dataspace references test-tree model'
130 setupSchemaSetMocks('test-tree.yang')
131 when: 'save data method is invoked with list element json data'
132 def jsonData = '{"branch": [{"name": "A"}, {"name": "B"}]}'
133 objectUnderTest.saveListElements(dataspaceName, anchorName, '/test-tree', jsonData, observedTimestamp)
134 then: 'the persistence service method is invoked with correct parameters'
135 1 * mockCpsDataPersistenceService.addListElements(dataspaceName, anchorName, '/test-tree',
136 { dataNodeCollection ->
138 assert dataNodeCollection.size() == 2
139 assert dataNodeCollection.collect { it.getXpath() }
140 .containsAll(['/test-tree/branch[@name=\'A\']', '/test-tree/branch[@name=\'B\']'])
144 and: 'the CpsValidator is called on the dataspaceName and AnchorName'
145 1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
146 and: 'data updated event is sent to notification service'
147 1 * mockNotificationService.processDataUpdatedEvent(anchor, '/test-tree', Operation.UPDATE, observedTimestamp)
150 def 'Saving collection of a batch with data fragment under existing node.'() {
151 given: 'schema set for given anchor and dataspace references test-tree model'
152 setupSchemaSetMocks('test-tree.yang')
153 when: 'save data method is invoked with list element json data'
154 def jsonData = '{"branch": [{"name": "A"}, {"name": "B"}]}'
155 objectUnderTest.saveListElementsBatch(dataspaceName, anchorName, '/test-tree', [jsonData], observedTimestamp)
156 then: 'the persistence service method is invoked with correct parameters'
157 1 * mockCpsDataPersistenceService.addMultipleLists(dataspaceName, anchorName, '/test-tree',_) >> {
159 def listElementsCollection = args[3] as Collection<Collection<DataNode>>
160 assert listElementsCollection.size() == 1
161 def listOfXpaths = listElementsCollection.stream().flatMap(x -> x.stream()).map(it-> it.xpath).collect(Collectors.toList())
162 assert listOfXpaths.size() == 2
163 assert listOfXpaths.containsAll(['/test-tree/branch[@name=\'B\']','/test-tree/branch[@name=\'A\']'])
166 and: 'data updated event is sent to notification service'
167 1 * mockNotificationService.processDataUpdatedEvent(anchor, '/test-tree', Operation.UPDATE, observedTimestamp)
170 def 'Saving empty list element data fragment.'() {
171 given: 'schema set for given anchor and dataspace references test-tree model'
172 setupSchemaSetMocks('test-tree.yang')
173 when: 'save data method is invoked with an empty list'
174 def jsonData = '{"branch": []}'
175 objectUnderTest.saveListElements(dataspaceName, anchorName, '/test-tree', jsonData, observedTimestamp)
176 then: 'invalid data exception is thrown'
177 thrown(DataValidationException)
180 def 'Get all data nodes #scenario.'() {
181 given: 'persistence service returns data for GET request'
182 mockCpsDataPersistenceService.getDataNodes(dataspaceName, anchorName, xpath, fetchDescendantsOption) >> dataNode
183 expect: 'service returns same data if using same parameters'
184 objectUnderTest.getDataNodes(dataspaceName, anchorName, xpath, fetchDescendantsOption) == dataNode
185 where: 'following parameters were used'
186 scenario | xpath | fetchDescendantsOption | dataNode
187 'with root node xpath and descendants' | '/' | FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS | [new DataNodeBuilder().withXpath('/xpath-1').build(), new DataNodeBuilder().withXpath('/xpath-2').build()]
188 'with root node xpath and no descendants' | '/' | FetchDescendantsOption.OMIT_DESCENDANTS | [new DataNodeBuilder().withXpath('/xpath-1').build(), new DataNodeBuilder().withXpath('/xpath-2').build()]
189 'with valid xpath and descendants' | '/xpath'| FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS | [new DataNodeBuilder().withXpath('/xpath').build()]
190 'with valid xpath and no descendants' | '/xpath'| FetchDescendantsOption.OMIT_DESCENDANTS | [new DataNodeBuilder().withXpath('/xpath').build()]
193 def 'Get all data nodes over multiple xpaths with option #fetchDescendantsOption.'() {
194 def xpath1 = '/xpath-1'
195 def xpath2 = '/xpath-2'
196 def dataNode = [new DataNodeBuilder().withXpath(xpath1).build(), new DataNodeBuilder().withXpath(xpath2).build()]
197 given: 'persistence service returns data for get data request'
198 mockCpsDataPersistenceService.getDataNodesForMultipleXpaths(dataspaceName, anchorName, [xpath1, xpath2], fetchDescendantsOption) >> dataNode
199 expect: 'service returns same data if uses same parameters'
200 objectUnderTest.getDataNodesForMultipleXpaths(dataspaceName, anchorName, [xpath1, xpath2], fetchDescendantsOption) == dataNode
201 where: 'all fetch options are supported'
202 fetchDescendantsOption << [FetchDescendantsOption.OMIT_DESCENDANTS, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS]
205 def 'Update data node leaves: #scenario.'() {
206 given: 'schema set for given anchor and dataspace references test-tree model'
207 setupSchemaSetMocks('test-tree.yang')
208 when: 'update data method is invoked with json data #jsonData and parent node xpath #parentNodeXpath'
209 objectUnderTest.updateNodeLeaves(dataspaceName, anchorName, parentNodeXpath, jsonData, observedTimestamp)
210 then: 'the persistence service method is invoked with correct parameters'
211 1 * mockCpsDataPersistenceService.batchUpdateDataLeaves(dataspaceName, anchorName, {dataNode -> dataNode.keySet()[0] == expectedNodeXpath})
212 and: 'the CpsValidator is called on the dataspaceName and AnchorName'
213 1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
214 and: 'data updated event is sent to notification service'
215 1 * mockNotificationService.processDataUpdatedEvent(anchor, parentNodeXpath, Operation.UPDATE, observedTimestamp)
216 where: 'following parameters were used'
217 scenario | parentNodeXpath | jsonData || expectedNodeXpath
218 'top level node' | '/' | '{"test-tree": {"branch": []}}' || '/test-tree'
219 'level 2 node' | '/test-tree' | '{"branch": [{"name":"Name"}]}' || '/test-tree/branch[@name=\'Name\']'
222 def 'Update list-element data node with : #scenario.'() {
223 given: 'schema set for given anchor and dataspace references bookstore model'
224 setupSchemaSetMocks('bookstore.yang')
225 when: 'update data method is invoked with json data #jsonData and parent node xpath'
226 objectUnderTest.updateNodeLeaves(dataspaceName, anchorName, '/bookstore/categories[@code=2]',
227 jsonData, observedTimestamp)
228 then: 'the persistence service method is invoked with correct parameters'
229 thrown(DataValidationException)
230 where: 'following parameters were used'
232 'multiple expectedLeaves' | '{"code": "01","name": "some-name"}'
233 'one leaf' | '{"name": "some-name"}'
236 def 'Update data nodes in different containers.' () {
237 given: 'schema set for given dataspace and anchor refers multipleDataTree model'
238 setupSchemaSetMocks('multipleDataTree.yang')
239 and: 'json string with multiple data trees'
240 def parentNodeXpath = '/'
241 def updatedJsonData = '{"first-container":{"a-leaf":"a-new-Value"},"last-container":{"x-leaf":"x-new-value"}}'
242 when: 'update operation is performed on multiple data nodes'
243 objectUnderTest.updateNodeLeaves(dataspaceName, anchorName, parentNodeXpath, updatedJsonData, observedTimestamp)
244 then: 'the persistence service method is invoked with correct parameters'
245 1 * mockCpsDataPersistenceService.batchUpdateDataLeaves(dataspaceName, anchorName, {dataNode -> dataNode.keySet()[index] == expectedNodeXpath})
246 and: 'the CpsValidator is called on the dataspaceName and AnchorName'
247 1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
248 and: 'data updated event is sent to notification service'
249 1 * mockNotificationService.processDataUpdatedEvent(anchor, parentNodeXpath, Operation.UPDATE, observedTimestamp)
250 where: 'the following parameters were used'
251 index | expectedNodeXpath
252 0 | '/first-container'
253 1 | '/last-container'
256 def 'Update Bookstore node leaves and child.' () {
257 given: 'a DMI registry model'
258 setupSchemaSetMocks('bookstore.yang')
259 and: 'json update for a category (parent) and new book (child)'
260 def jsonData = '{"categories":[{"code":01,"name":"Romance","books": [{"title": "new"}]}]}'
261 when: 'update data method is invoked with json data and parent node xpath'
262 objectUnderTest.updateNodeLeavesAndExistingDescendantLeaves(dataspaceName, anchorName, '/bookstore', jsonData, observedTimestamp)
263 then: 'the persistence service method is invoked for the category (parent)'
264 1 * mockCpsDataPersistenceService.batchUpdateDataLeaves(dataspaceName, anchorName,
265 {updatedDataNodesPerXPath -> updatedDataNodesPerXPath.keySet()
266 .iterator().next() == "/bookstore/categories[@code='01']"})
267 and: 'the persistence service method is invoked for the new book (child)'
268 1 * mockCpsDataPersistenceService.batchUpdateDataLeaves(dataspaceName, anchorName,
269 {updatedDataNodesPerXPath -> updatedDataNodesPerXPath.keySet()
270 .iterator().next() == "/bookstore/categories[@code='01']/books[@title='new']"})
271 and: 'the CpsValidator is called on the dataspaceName and AnchorName'
272 1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
273 and: 'the data updated event is sent to the notification service'
274 1 * mockNotificationService.processDataUpdatedEvent(anchor, '/bookstore', Operation.UPDATE, observedTimestamp)
277 def 'Replace data node using singular data node: #scenario.'() {
278 given: 'schema set for given anchor and dataspace references test-tree model'
279 setupSchemaSetMocks('test-tree.yang')
280 when: 'replace data method is invoked with json data #jsonData and parent node xpath #parentNodeXpath'
281 objectUnderTest.updateDataNodeAndDescendants(dataspaceName, anchorName, parentNodeXpath, jsonData, observedTimestamp)
282 then: 'the persistence service method is invoked with correct parameters'
283 1 * mockCpsDataPersistenceService.updateDataNodesAndDescendants(dataspaceName, anchorName,
284 { dataNode -> dataNode.xpath[0] == expectedNodeXpath })
285 and: 'data updated event is sent to notification service'
286 1 * mockNotificationService.processDataUpdatedEvent(anchor, parentNodeXpath, Operation.UPDATE, observedTimestamp)
287 and: 'the CpsValidator is called on the dataspaceName and AnchorName'
288 1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
289 where: 'following parameters were used'
290 scenario | parentNodeXpath | jsonData || expectedNodeXpath
291 'top level node' | '/' | '{"test-tree": {"branch": []}}' || '/test-tree'
292 'level 2 node' | '/test-tree' | '{"branch": [{"name":"Name"}]}' || '/test-tree/branch[@name=\'Name\']'
295 def 'Replace data node using multiple data nodes: #scenario.'() {
296 given: 'schema set for given anchor and dataspace references test-tree model'
297 setupSchemaSetMocks('test-tree.yang')
298 when: 'replace data method is invoked with a map of xpaths and json data'
299 objectUnderTest.updateDataNodesAndDescendants(dataspaceName, anchorName, nodesJsonData, observedTimestamp)
300 then: 'the persistence service method is invoked with correct parameters'
301 1 * mockCpsDataPersistenceService.updateDataNodesAndDescendants(dataspaceName, anchorName,
302 { dataNode -> dataNode.xpath == expectedNodeXpath})
303 and: 'data updated event is sent to notification service'
304 1 * mockNotificationService.processDataUpdatedEvent(anchor, nodesJsonData.keySet()[0], Operation.UPDATE, observedTimestamp)
305 1 * mockNotificationService.processDataUpdatedEvent(anchor, nodesJsonData.keySet()[1], Operation.UPDATE, observedTimestamp)
306 and: 'the CpsValidator is called on the dataspaceName and AnchorName'
307 1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
308 where: 'following parameters were used'
309 scenario | nodesJsonData || expectedNodeXpath
310 'top level node' | ['/' : '{"test-tree": {"branch": []}}', '/test-tree' : '{"branch": [{"name":"Name"}]}'] || ["/test-tree", "/test-tree/branch[@name='Name']"]
311 'level 2 node' | ['/test-tree' : '{"branch": [{"name":"Name"}]}', '/test-tree/branch[@name=\'Name\']':'{"nest":{"name":"nestName"}}'] || ["/test-tree/branch[@name='Name']", "/test-tree/branch[@name='Name']/nest"]
314 def 'Replace list content data fragment under parent node.'() {
315 given: 'schema set for given anchor and dataspace references test-tree model'
316 setupSchemaSetMocks('test-tree.yang')
317 when: 'replace list data method is invoked with list element json data'
318 def jsonData = '{"branch": [{"name": "A"}, {"name": "B"}]}'
319 objectUnderTest.replaceListContent(dataspaceName, anchorName, '/test-tree', jsonData, observedTimestamp)
320 then: 'the persistence service method is invoked with correct parameters'
321 1 * mockCpsDataPersistenceService.replaceListContent(dataspaceName, anchorName, '/test-tree',
322 { dataNodeCollection ->
324 assert dataNodeCollection.size() == 2
325 assert dataNodeCollection.collect { it.getXpath() }
326 .containsAll(['/test-tree/branch[@name=\'A\']', '/test-tree/branch[@name=\'B\']'])
330 and: 'the CpsValidator is called on the dataspaceName and AnchorName twice'
331 2 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
332 and: 'data updated event is sent to notification service'
333 1 * mockNotificationService.processDataUpdatedEvent(anchor, '/test-tree', Operation.UPDATE, observedTimestamp)
336 def 'Replace whole list content with empty list element.'() {
337 given: 'schema set for given anchor and dataspace references test-tree model'
338 setupSchemaSetMocks('test-tree.yang')
339 when: 'replace list data method is invoked with empty list'
340 def jsonData = '{"branch": []}'
341 objectUnderTest.replaceListContent(dataspaceName, anchorName, '/test-tree', jsonData, observedTimestamp)
342 then: 'invalid data exception is thrown'
343 thrown(DataValidationException)
346 def 'Delete list element under existing node.'() {
347 given: 'schema set for given anchor and dataspace references test-tree model'
348 setupSchemaSetMocks('test-tree.yang')
349 when: 'delete list data method is invoked with list element json data'
350 objectUnderTest.deleteListOrListElement(dataspaceName, anchorName, '/test-tree/branch', observedTimestamp)
351 then: 'the persistence service method is invoked with correct parameters'
352 1 * mockCpsDataPersistenceService.deleteListDataNode(dataspaceName, anchorName, '/test-tree/branch')
353 and: 'the CpsValidator is called on the dataspaceName and AnchorName'
354 1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
355 and: 'data updated event is sent to notification service'
356 1 * mockNotificationService.processDataUpdatedEvent(anchor, '/test-tree/branch', Operation.DELETE, observedTimestamp)
359 def 'Delete multiple list elements under existing node.'() {
360 given: 'schema set for given anchor and dataspace references test-tree model'
361 setupSchemaSetMocks('test-tree.yang')
362 when: 'delete multiple list data method is invoked with list element json data'
363 objectUnderTest.deleteDataNodes(dataspaceName, anchorName, ['/test-tree/branch[@name="A"]', '/test-tree/branch[@name="B"]'], observedTimestamp)
364 then: 'the persistence service method is invoked with correct parameters'
365 1 * mockCpsDataPersistenceService.deleteDataNodes(dataspaceName, anchorName, ['/test-tree/branch[@name="A"]', '/test-tree/branch[@name="B"]'])
366 and: 'the CpsValidator is called on the dataspaceName and AnchorName'
367 1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
368 and: 'two data updated events are sent to notification service'
369 2 * mockNotificationService.processDataUpdatedEvent(anchor, _, Operation.DELETE, observedTimestamp)
372 def 'Delete data node under anchor and dataspace.'() {
373 given: 'schema set for given anchor and dataspace references test tree model'
374 setupSchemaSetMocks('test-tree.yang')
375 when: 'delete data node method is invoked with correct parameters'
376 objectUnderTest.deleteDataNode(dataspaceName, anchorName, '/data-node', observedTimestamp)
377 then: 'the persistence service method is invoked with the correct parameters'
378 1 * mockCpsDataPersistenceService.deleteDataNode(dataspaceName, anchorName, '/data-node')
379 and: 'the CpsValidator is called on the dataspaceName and AnchorName'
380 1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
381 and: 'data updated event is sent to notification service'
382 1 * mockNotificationService.processDataUpdatedEvent(anchor, '/data-node', Operation.DELETE, observedTimestamp)
385 def 'Delete all data nodes for a given anchor and dataspace.'() {
386 given: 'schema set for given anchor and dataspace references test tree model'
387 setupSchemaSetMocks('test-tree.yang')
388 when: 'delete data node method is invoked with correct parameters'
389 objectUnderTest.deleteDataNodes(dataspaceName, anchorName, observedTimestamp)
390 then: 'data updated event is sent to notification service before the delete'
391 1 * mockNotificationService.processDataUpdatedEvent(anchor, '/', Operation.DELETE, observedTimestamp)
392 and: 'the CpsValidator is called on the dataspaceName and AnchorName'
393 1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
394 and: 'the persistence service method is invoked with the correct parameters'
395 1 * mockCpsDataPersistenceService.deleteDataNodes(dataspaceName, anchorName)
398 def 'Delete all data nodes for given dataspace and multiple anchors.'() {
399 given: 'schema set for given anchors and dataspace references test tree model'
400 setupSchemaSetMocks('test-tree.yang')
401 mockCpsAdminService.getAnchors(dataspaceName, ['anchor1', 'anchor2']) >>
402 [new Anchor(name: 'anchor1', dataspaceName: dataspaceName),
403 new Anchor(name: 'anchor2', dataspaceName: dataspaceName)]
404 when: 'delete data node method is invoked with correct parameters'
405 objectUnderTest.deleteDataNodes(dataspaceName, ['anchor1', 'anchor2'], observedTimestamp)
406 then: 'data updated events are sent to notification service before the delete'
407 2 * mockNotificationService.processDataUpdatedEvent(_, '/', Operation.DELETE, observedTimestamp)
408 and: 'the CpsValidator is called on the dataspace name and the anchor names'
409 2 * mockCpsValidator.validateNameCharacters(_)
410 and: 'the persistence service method is invoked with the correct parameters'
411 1 * mockCpsDataPersistenceService.deleteDataNodes(dataspaceName, _ as Collection<String>)
414 def setupSchemaSetMocks(String... yangResources) {
415 def mockYangTextSchemaSourceSet = Mock(YangTextSchemaSourceSet)
416 mockYangTextSchemaSourceSetCache.get(dataspaceName, schemaSetName) >> mockYangTextSchemaSourceSet
417 def yangResourceNameToContent = TestUtils.getYangResourcesAsMap(yangResources)
418 def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent).getSchemaContext()
419 mockYangTextSchemaSourceSet.getSchemaContext() >> schemaContext
422 def 'start session'() {
423 when: 'start session method is called'
424 objectUnderTest.startSession()
425 then: 'the persistence service method to start session is invoked'
426 1 * mockCpsDataPersistenceService.startSession()
429 def 'close session'(){
430 given: 'session Id from calling the start session method'
431 def sessionId = objectUnderTest.startSession()
432 when: 'close session method is called'
433 objectUnderTest.closeSession(sessionId)
434 then: 'the persistence service method to close session is invoked'
435 1 * mockCpsDataPersistenceService.closeSession(sessionId)
438 def 'lock anchor with no timeout parameter'(){
439 when: 'lock anchor method with no timeout parameter with details of anchor entity to lock'
440 objectUnderTest.lockAnchor('some-sessionId', 'some-dataspaceName', 'some-anchorName')
441 then: 'the persistence service method to lock anchor is invoked with default timeout'
442 1 * mockCpsDataPersistenceService.lockAnchor('some-sessionId', 'some-dataspaceName',
443 'some-anchorName', 300L)
446 def 'lock anchor with timeout parameter'(){
447 when: 'lock anchor method with timeout parameter is called with details of anchor entity to lock'
448 objectUnderTest.lockAnchor('some-sessionId', 'some-dataspaceName',
449 'some-anchorName', 250L)
450 then: 'the persistence service method to lock anchor is invoked with the given timeout'
451 1 * mockCpsDataPersistenceService.lockAnchor('some-sessionId', 'some-dataspaceName',
452 'some-anchorName', 250L)