2 * ============LICENSE_START=======================================================
3 * Copyright (C) 2021-2024 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.CpsAnchorService
28 import org.onap.cps.api.CpsDeltaService
29 import org.onap.cps.spi.CpsDataPersistenceService
30 import org.onap.cps.spi.FetchDescendantsOption
31 import org.onap.cps.spi.exceptions.ConcurrencyException
32 import org.onap.cps.spi.exceptions.DataNodeNotFoundExceptionBatch
33 import org.onap.cps.spi.exceptions.DataValidationException
34 import org.onap.cps.spi.exceptions.SessionManagerException
35 import org.onap.cps.spi.exceptions.SessionTimeoutException
36 import org.onap.cps.spi.model.Anchor
37 import org.onap.cps.spi.model.DataNodeBuilder
38 import org.onap.cps.spi.utils.CpsValidator
39 import org.onap.cps.utils.ContentType
40 import org.onap.cps.utils.YangParser
41 import org.onap.cps.utils.YangParserHelper
42 import org.onap.cps.yang.YangTextSchemaSourceSet
43 import org.onap.cps.yang.YangTextSchemaSourceSetBuilder
44 import spock.lang.Shared
45 import spock.lang.Specification
46 import java.time.OffsetDateTime
48 class CpsDataServiceImplSpec extends Specification {
49 def mockCpsDataPersistenceService = Mock(CpsDataPersistenceService)
50 def mockCpsAnchorService = Mock(CpsAnchorService)
51 def mockYangTextSchemaSourceSetCache = Mock(YangTextSchemaSourceSetCache)
52 def mockCpsValidator = Mock(CpsValidator)
53 def yangParser = new YangParser(new YangParserHelper(), mockYangTextSchemaSourceSetCache)
54 def mockCpsDeltaService = Mock(CpsDeltaService);
56 def objectUnderTest = new CpsDataServiceImpl(mockCpsDataPersistenceService, mockCpsAnchorService, mockCpsValidator, yangParser, mockCpsDeltaService)
59 mockCpsAnchorService.getAnchor(dataspaceName, anchorName) >> anchor
60 mockCpsAnchorService.getAnchor(dataspaceName, ANCHOR_NAME_1) >> anchor1
61 mockCpsAnchorService.getAnchor(dataspaceName, ANCHOR_NAME_2) >> anchor2
65 static def ANCHOR_NAME_1 = 'some-anchor-1'
67 static def ANCHOR_NAME_2 = 'some-anchor-2'
68 def dataspaceName = 'some-dataspace'
69 def anchorName = 'some-anchor'
70 def schemaSetName = 'some-schema-set'
71 def anchor = Anchor.builder().name(anchorName).dataspaceName(dataspaceName).schemaSetName(schemaSetName).build()
72 def anchor1 = Anchor.builder().name(ANCHOR_NAME_1).dataspaceName(dataspaceName).schemaSetName(schemaSetName).build()
73 def anchor2 = Anchor.builder().name(ANCHOR_NAME_2).dataspaceName(dataspaceName).schemaSetName(schemaSetName).build()
74 def observedTimestamp = OffsetDateTime.now()
76 def 'Saving #scenario data.'() {
77 given: 'schema set for given anchor and dataspace references test-tree model'
78 setupSchemaSetMocks('test-tree.yang')
79 when: 'save data method is invoked with test-tree #scenario data'
80 def data = TestUtils.getResourceFileContent(dataFile)
81 objectUnderTest.saveData(dataspaceName, anchorName, data, observedTimestamp, contentType)
82 then: 'the persistence service method is invoked with correct parameters'
83 1 * mockCpsDataPersistenceService.storeDataNodes(dataspaceName, anchorName,
84 { dataNode -> dataNode.xpath[0] == '/test-tree' })
85 and: 'the CpsValidator is called on the dataspaceName and AnchorName'
86 1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
87 where: 'given parameters'
88 scenario | dataFile | contentType
89 'json' | 'test-tree.json' | ContentType.JSON
90 'xml' | 'test-tree.xml' | ContentType.XML
93 def 'Saving data with error: #scenario.'() {
94 given: 'schema set for given anchor and dataspace references test-tree model'
95 setupSchemaSetMocks('test-tree.yang')
96 when: 'save data method is invoked with test-tree json data'
97 objectUnderTest.saveData(dataspaceName, anchorName, invalidData, observedTimestamp, contentType)
98 then: 'a data validation exception is thrown with the correct message'
99 def exceptionThrown = thrown(DataValidationException)
100 assert exceptionThrown.message.startsWith(expectedMessage)
101 where: 'given parameters'
102 scenario | invalidData | contentType || expectedMessage
103 'no data nodes' | '{}' | ContentType.JSON || 'No data nodes'
104 'invalid json' | '{invalid json' | ContentType.JSON || 'Failed to parse json data'
105 'invalid xml' | '<invalid xml' | ContentType.XML || 'Failed to parse xml data'
108 def 'Saving list element data fragment under Root node.'() {
109 given: 'schema set for given anchor and dataspace references bookstore model'
110 setupSchemaSetMocks('bookstore.yang')
111 when: 'save data method is invoked with list element json data'
112 def jsonData = '{"bookstore-address":[{"bookstore-name":"Easons","address":"Dublin,Ireland","postal-code":"D02HA21"}]}'
113 objectUnderTest.saveListElements(dataspaceName, anchorName, '/', jsonData, observedTimestamp)
114 then: 'the persistence service method is invoked with correct parameters'
115 1 * mockCpsDataPersistenceService.storeDataNodes(dataspaceName, anchorName,
116 { dataNodeCollection ->
118 assert dataNodeCollection.size() == 1
119 assert dataNodeCollection.collect { it.getXpath() }
120 .containsAll(['/bookstore-address[@bookstore-name=\'Easons\']'])
124 and: 'the CpsValidator is called on the dataspaceName and AnchorName'
125 1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
128 def 'Saving child 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 test-tree json data'
132 def jsonData = '{"branch": [{"name": "New"}]}'
133 objectUnderTest.saveData(dataspaceName, anchorName, '/test-tree', jsonData, observedTimestamp)
134 then: 'the persistence service method is invoked with correct parameters'
135 1 * mockCpsDataPersistenceService.addChildDataNodes(dataspaceName, anchorName, '/test-tree',
136 { dataNode -> dataNode.xpath[0] == '/test-tree/branch[@name=\'New\']' })
137 and: 'the CpsValidator is called on the dataspaceName and AnchorName'
138 1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
141 def 'Saving list element data fragment under existing node.'() {
142 given: 'schema set for given anchor and dataspace references test-tree model'
143 setupSchemaSetMocks('test-tree.yang')
144 when: 'save data method is invoked with list element json data'
145 def jsonData = '{"branch": [{"name": "A"}, {"name": "B"}]}'
146 objectUnderTest.saveListElements(dataspaceName, anchorName, '/test-tree', jsonData, observedTimestamp)
147 then: 'the persistence service method is invoked with correct parameters'
148 1 * mockCpsDataPersistenceService.addListElements(dataspaceName, anchorName, '/test-tree',
149 { dataNodeCollection ->
151 assert dataNodeCollection.size() == 2
152 assert dataNodeCollection.collect { it.getXpath() }
153 .containsAll(['/test-tree/branch[@name=\'A\']', '/test-tree/branch[@name=\'B\']'])
157 and: 'the CpsValidator is called on the dataspaceName and AnchorName'
158 1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
161 def 'Saving empty list element data fragment.'() {
162 given: 'schema set for given anchor and dataspace references test-tree model'
163 setupSchemaSetMocks('test-tree.yang')
164 when: 'save data method is invoked with an empty list'
165 def jsonData = '{"branch": []}'
166 objectUnderTest.saveListElements(dataspaceName, anchorName, '/test-tree', jsonData, observedTimestamp)
167 then: 'invalid data exception is thrown'
168 thrown(DataValidationException)
171 def 'Get all data nodes #scenario.'() {
172 given: 'persistence service returns data for GET request'
173 mockCpsDataPersistenceService.getDataNodes(dataspaceName, anchorName, xpath, fetchDescendantsOption) >> dataNode
174 expect: 'service returns same data if using same parameters'
175 objectUnderTest.getDataNodes(dataspaceName, anchorName, xpath, fetchDescendantsOption) == dataNode
176 where: 'following parameters were used'
177 scenario | xpath | fetchDescendantsOption | dataNode
178 'with root node xpath and descendants' | '/' | FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS | [new DataNodeBuilder().withXpath('/xpath-1').build(), new DataNodeBuilder().withXpath('/xpath-2').build()]
179 'with root node xpath and no descendants' | '/' | FetchDescendantsOption.OMIT_DESCENDANTS | [new DataNodeBuilder().withXpath('/xpath-1').build(), new DataNodeBuilder().withXpath('/xpath-2').build()]
180 'with valid xpath and descendants' | '/xpath'| FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS | [new DataNodeBuilder().withXpath('/xpath').build()]
181 'with valid xpath and no descendants' | '/xpath'| FetchDescendantsOption.OMIT_DESCENDANTS | [new DataNodeBuilder().withXpath('/xpath').build()]
184 def 'Get all data nodes over multiple xpaths with option #fetchDescendantsOption.'() {
185 def xpath1 = '/xpath-1'
186 def xpath2 = '/xpath-2'
187 def dataNode = [new DataNodeBuilder().withXpath(xpath1).build(), new DataNodeBuilder().withXpath(xpath2).build()]
188 given: 'persistence service returns data for get data request'
189 mockCpsDataPersistenceService.getDataNodesForMultipleXpaths(dataspaceName, anchorName, [xpath1, xpath2], fetchDescendantsOption) >> dataNode
190 expect: 'service returns same data if uses same parameters'
191 objectUnderTest.getDataNodesForMultipleXpaths(dataspaceName, anchorName, [xpath1, xpath2], fetchDescendantsOption) == dataNode
192 where: 'all fetch options are supported'
193 fetchDescendantsOption << [FetchDescendantsOption.OMIT_DESCENDANTS, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS]
196 def 'Get delta between 2 anchors'() {
197 given: 'some xpath, source and target data nodes'
199 def sourceDataNodes = [new DataNodeBuilder().withXpath(xpath).build()]
200 def targetDataNodes = [new DataNodeBuilder().withXpath(xpath).build()]
201 when: 'attempt to get delta between 2 anchors'
202 objectUnderTest.getDeltaByDataspaceAndAnchors(dataspaceName, ANCHOR_NAME_1, ANCHOR_NAME_2, xpath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS)
203 then: 'the dataspace and anchor names are validated'
204 2 * mockCpsValidator.validateNameCharacters(_)
205 and: 'data nodes are fetched using appropriate persistence layer method'
206 mockCpsDataPersistenceService.getDataNodesForMultipleXpaths(dataspaceName, ANCHOR_NAME_1, [xpath], FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> sourceDataNodes
207 mockCpsDataPersistenceService.getDataNodesForMultipleXpaths(dataspaceName, ANCHOR_NAME_2, [xpath], FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> targetDataNodes
208 and: 'appropriate delta service method is invoked once with correct source and target data nodes'
209 1 * mockCpsDeltaService.getDeltaReports(sourceDataNodes, targetDataNodes)
212 def 'Update data node leaves: #scenario.'() {
213 given: 'schema set for given anchor and dataspace references test-tree model'
214 setupSchemaSetMocks('test-tree.yang')
215 when: 'update data method is invoked with json data #jsonData and parent node xpath #parentNodeXpath'
216 objectUnderTest.updateNodeLeaves(dataspaceName, anchorName, parentNodeXpath, jsonData, observedTimestamp)
217 then: 'the persistence service method is invoked with correct parameters'
218 1 * mockCpsDataPersistenceService.batchUpdateDataLeaves(dataspaceName, anchorName, {dataNode -> dataNode.keySet()[0] == expectedNodeXpath})
219 and: 'the CpsValidator is called on the dataspaceName and AnchorName'
220 1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
221 where: 'following parameters were used'
222 scenario | parentNodeXpath | jsonData || expectedNodeXpath
223 'top level node' | '/' | '{"test-tree": {"branch": []}}' || '/test-tree'
224 'level 2 node' | '/test-tree' | '{"branch": [{"name":"Name"}]}' || '/test-tree/branch[@name=\'Name\']'
227 def 'Update list-element data node with : #scenario.'() {
228 given: 'schema set for given anchor and dataspace references bookstore model'
229 setupSchemaSetMocks('bookstore.yang')
230 when: 'update data method is invoked with json data #jsonData and parent node xpath'
231 objectUnderTest.updateNodeLeaves(dataspaceName, anchorName, '/bookstore/categories[@code=2]',
232 jsonData, observedTimestamp)
233 then: 'the persistence service method is invoked with correct parameters'
234 thrown(DataValidationException)
235 where: 'following parameters were used'
237 'multiple expectedLeaves' | '{"code": "01","name": "some-name"}'
238 'one leaf' | '{"name": "some-name"}'
241 def 'Update data nodes in different containers.' () {
242 given: 'schema set for given dataspace and anchor refers multipleDataTree model'
243 setupSchemaSetMocks('multipleDataTree.yang')
244 and: 'json string with multiple data trees'
245 def parentNodeXpath = '/'
246 def updatedJsonData = '{"first-container":{"a-leaf":"a-new-Value"},"last-container":{"x-leaf":"x-new-value"}}'
247 when: 'update operation is performed on multiple data nodes'
248 objectUnderTest.updateNodeLeaves(dataspaceName, anchorName, parentNodeXpath, updatedJsonData, observedTimestamp)
249 then: 'the persistence service method is invoked with correct parameters'
250 1 * mockCpsDataPersistenceService.batchUpdateDataLeaves(dataspaceName, anchorName, {dataNode -> dataNode.keySet()[index] == expectedNodeXpath})
251 and: 'the CpsValidator is called on the dataspaceName and AnchorName'
252 1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
253 where: 'the following parameters were used'
254 index | expectedNodeXpath
255 0 | '/first-container'
256 1 | '/last-container'
259 def 'Update Bookstore node leaves and child.' () {
260 given: 'a DMI registry model'
261 setupSchemaSetMocks('bookstore.yang')
262 and: 'json update for a category (parent) and new book (child)'
263 def jsonData = '{"categories":[{"code":01,"name":"Romance","books": [{"title": "new"}]}]}'
264 when: 'update data method is invoked with json data and parent node xpath'
265 objectUnderTest.updateNodeLeavesAndExistingDescendantLeaves(dataspaceName, anchorName, '/bookstore', jsonData, observedTimestamp)
266 then: 'the persistence service method is invoked for the category (parent)'
267 1 * mockCpsDataPersistenceService.batchUpdateDataLeaves(dataspaceName, anchorName,
268 {updatedDataNodesPerXPath -> updatedDataNodesPerXPath.keySet()
269 .iterator().next() == "/bookstore/categories[@code='01']"})
270 and: 'the persistence service method is invoked for the new book (child)'
271 1 * mockCpsDataPersistenceService.batchUpdateDataLeaves(dataspaceName, anchorName,
272 {updatedDataNodesPerXPath -> updatedDataNodesPerXPath.keySet()
273 .iterator().next() == "/bookstore/categories[@code='01']/books[@title='new']"})
274 and: 'the CpsValidator is called on the dataspaceName and AnchorName'
275 1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
278 def 'Replace data node using singular data node: #scenario.'() {
279 given: 'schema set for given anchor and dataspace references test-tree model'
280 setupSchemaSetMocks('test-tree.yang')
281 when: 'replace data method is invoked with json data #jsonData and parent node xpath #parentNodeXpath'
282 objectUnderTest.updateDataNodeAndDescendants(dataspaceName, anchorName, parentNodeXpath, jsonData, observedTimestamp)
283 then: 'the persistence service method is invoked with correct parameters'
284 1 * mockCpsDataPersistenceService.updateDataNodesAndDescendants(dataspaceName, anchorName,
285 { dataNode -> dataNode.xpath == expectedNodeXpath})
286 and: 'the CpsValidator is called on the dataspaceName and AnchorName'
287 1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
288 where: 'following parameters were used'
289 scenario | parentNodeXpath | jsonData || expectedNodeXpath
290 'top level node' | '/' | '{"test-tree": {"branch": []}}' || ['/test-tree']
291 'level 2 node' | '/test-tree' | '{"branch": [{"name":"Name"}]}' || ['/test-tree/branch[@name=\'Name\']']
292 'json list' | '/test-tree' | '{"branch": [{"name":"Name1"}, {"name":"Name2"}]}' || ["/test-tree/branch[@name='Name1']", "/test-tree/branch[@name='Name2']"]
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: 'the CpsValidator is called on the dataspaceName and AnchorName'
304 1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
305 where: 'following parameters were used'
306 scenario | nodesJsonData || expectedNodeXpath
307 'top level node' | ['/' : '{"test-tree": {"branch": []}}', '/test-tree' : '{"branch": [{"name":"Name"}]}'] || ["/test-tree", "/test-tree/branch[@name='Name']"]
308 '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"]
309 'json list' | ['/test-tree' : '{"branch": [{"name":"Name1"}, {"name":"Name2"}]}'] || ["/test-tree/branch[@name='Name1']", "/test-tree/branch[@name='Name2']"]
312 def 'Replace data node with concurrency exception in persistence layer.'() {
313 given: 'the persistence layer throws an concurrency exception'
314 def originalException = new ConcurrencyException('message', 'details')
315 mockCpsDataPersistenceService.updateDataNodesAndDescendants(*_) >> { throw originalException }
316 setupSchemaSetMocks('test-tree.yang')
317 when: 'attempt to replace data node'
318 objectUnderTest.updateDataNodesAndDescendants(dataspaceName, anchorName, ['/' : '{"test-tree": {}}'] , observedTimestamp)
319 then: 'the same exception is thrown up'
320 def thrownUp = thrown(ConcurrencyException)
321 assert thrownUp == originalException
324 def 'Replace list content data fragment under parent node.'() {
325 given: 'schema set for given anchor and dataspace references test-tree model'
326 setupSchemaSetMocks('test-tree.yang')
327 when: 'replace list data method is invoked with list element json data'
328 def jsonData = '{"branch": [{"name": "A"}, {"name": "B"}]}'
329 objectUnderTest.replaceListContent(dataspaceName, anchorName, '/test-tree', jsonData, observedTimestamp)
330 then: 'the persistence service method is invoked with correct parameters'
331 1 * mockCpsDataPersistenceService.replaceListContent(dataspaceName, anchorName, '/test-tree',
332 { dataNodeCollection ->
334 assert dataNodeCollection.size() == 2
335 assert dataNodeCollection.collect { it.getXpath() }
336 .containsAll(['/test-tree/branch[@name=\'A\']', '/test-tree/branch[@name=\'B\']'])
340 and: 'the CpsValidator is called on the dataspaceName and AnchorName twice'
341 2 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
344 def 'Replace whole list content with empty list element.'() {
345 given: 'schema set for given anchor and dataspace references test-tree model'
346 setupSchemaSetMocks('test-tree.yang')
347 when: 'replace list data method is invoked with empty list'
348 def jsonData = '{"branch": []}'
349 objectUnderTest.replaceListContent(dataspaceName, anchorName, '/test-tree', jsonData, observedTimestamp)
350 then: 'invalid data exception is thrown'
351 thrown(DataValidationException)
354 def 'Delete list element under existing node.'() {
355 when: 'delete list data method is invoked with list element json data'
356 objectUnderTest.deleteListOrListElement(dataspaceName, anchorName, '/test-tree/branch', observedTimestamp)
357 then: 'the persistence service method is invoked with correct parameters'
358 1 * mockCpsDataPersistenceService.deleteListDataNode(dataspaceName, anchorName, '/test-tree/branch')
359 and: 'the CpsValidator is called on the dataspaceName and AnchorName'
360 1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
363 def 'Delete multiple list elements under existing node.'() {
364 when: 'delete multiple list data method is invoked with list element json data'
365 objectUnderTest.deleteDataNodes(dataspaceName, anchorName, ['/test-tree/branch[@name="A"]', '/test-tree/branch[@name="B"]'], observedTimestamp)
366 then: 'the persistence service method is invoked with correct parameters'
367 1 * mockCpsDataPersistenceService.deleteDataNodes(dataspaceName, anchorName, ['/test-tree/branch[@name="A"]', '/test-tree/branch[@name="B"]'])
368 and: 'the CpsValidator is called on the dataspaceName and AnchorName'
369 1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
372 def 'Delete data node under anchor and dataspace.'() {
373 when: 'delete data node method is invoked with correct parameters'
374 objectUnderTest.deleteDataNode(dataspaceName, anchorName, '/data-node', observedTimestamp)
375 then: 'the persistence service method is invoked with the correct parameters'
376 1 * mockCpsDataPersistenceService.deleteDataNode(dataspaceName, anchorName, '/data-node')
377 and: 'the CpsValidator is called on the dataspaceName and AnchorName'
378 1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
381 def 'Delete all data nodes for a given anchor and dataspace.'() {
382 when: 'delete data nodes method is invoked with correct parameters'
383 objectUnderTest.deleteDataNodes(dataspaceName, anchorName, observedTimestamp)
384 then: 'the CpsValidator is called on the dataspaceName and AnchorName'
385 1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
386 and: 'the persistence service method is invoked with the correct parameters'
387 1 * mockCpsDataPersistenceService.deleteDataNodes(dataspaceName, anchorName)
390 def 'Delete all data nodes for a given anchor and dataspace with batch exception in persistence layer.'() {
391 given: 'a batch exception in persistence layer'
392 def originalException = new DataNodeNotFoundExceptionBatch('ds1','a1',[])
393 mockCpsDataPersistenceService.deleteDataNodes(*_) >> { throw originalException }
394 when: 'attempt to delete data nodes'
395 objectUnderTest.deleteDataNodes(dataspaceName, anchorName, observedTimestamp)
396 then: 'the original exception is thrown up'
397 def thrownUp = thrown(DataNodeNotFoundExceptionBatch)
398 assert thrownUp == originalException
399 and: 'the exception details contain the expected data'
400 assert thrownUp.details.contains('ds1')
401 assert thrownUp.details.contains('a1')
404 def 'Delete all data nodes for given dataspace and multiple anchors.'() {
405 given: 'schema set for given anchors and dataspace references test tree model'
406 setupSchemaSetMocks('test-tree.yang')
407 mockCpsAnchorService.getAnchors(dataspaceName, ['anchor1', 'anchor2']) >>
408 [new Anchor(name: 'anchor1', dataspaceName: dataspaceName),
409 new Anchor(name: 'anchor2', dataspaceName: dataspaceName)]
410 when: 'delete data node method is invoked with correct parameters'
411 objectUnderTest.deleteDataNodes(dataspaceName, ['anchor1', 'anchor2'], observedTimestamp)
412 then: 'the CpsValidator is called on the dataspace name and the anchor names'
413 2 * mockCpsValidator.validateNameCharacters(_)
414 and: 'the persistence service method is invoked with the correct parameters'
415 1 * mockCpsDataPersistenceService.deleteDataNodes(dataspaceName, _ as Collection<String>)
418 def 'Start session.'() {
419 when: 'start session method is called'
420 objectUnderTest.startSession()
421 then: 'the persistence service method to start session is invoked'
422 1 * mockCpsDataPersistenceService.startSession()
425 def 'Start session with Session Manager Exceptions.'() {
426 given: 'the persistence layer throws an Session Manager Exception'
427 mockCpsDataPersistenceService.startSession() >> { throw originalException }
428 when: 'attempt to start session'
429 objectUnderTest.startSession()
430 then: 'the original exception is thrown up'
431 def thrownUp = thrown(SessionManagerException)
432 assert thrownUp == originalException
433 where: 'variations of Session Manager Exception are used'
434 originalException << [ new SessionManagerException('message','details'),
435 new SessionManagerException('message','details', new Exception('cause')),
436 new SessionTimeoutException('message','details', new Exception('cause'))]
439 def 'Close session.'(){
440 given: 'session Id from calling the start session method'
441 def sessionId = objectUnderTest.startSession()
442 when: 'close session method is called'
443 objectUnderTest.closeSession(sessionId)
444 then: 'the persistence service method to close session is invoked'
445 1 * mockCpsDataPersistenceService.closeSession(sessionId)
448 def 'Lock anchor with no timeout parameter.'(){
449 when: 'lock anchor method with no timeout parameter with details of anchor entity to lock'
450 objectUnderTest.lockAnchor('some-sessionId', 'some-dataspaceName', 'some-anchorName')
451 then: 'the persistence service method to lock anchor is invoked with default timeout'
452 1 * mockCpsDataPersistenceService.lockAnchor('some-sessionId', 'some-dataspaceName', 'some-anchorName', 300L)
455 def 'Lock anchor with timeout parameter.'(){
456 when: 'lock anchor method with timeout parameter is called with details of anchor entity to lock'
457 objectUnderTest.lockAnchor('some-sessionId', 'some-dataspaceName', 'some-anchorName', 250L)
458 then: 'the persistence service method to lock anchor is invoked with the given timeout'
459 1 * mockCpsDataPersistenceService.lockAnchor('some-sessionId', 'some-dataspaceName', 'some-anchorName', 250L)
462 def setupSchemaSetMocks(String... yangResources) {
463 def mockYangTextSchemaSourceSet = Mock(YangTextSchemaSourceSet)
464 mockYangTextSchemaSourceSetCache.get(dataspaceName, schemaSetName) >> mockYangTextSchemaSourceSet
465 def yangResourceNameToContent = TestUtils.getYangResourcesAsMap(yangResources)
466 def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent).getSchemaContext()
467 mockYangTextSchemaSourceSet.getSchemaContext() >> schemaContext