Refactoring/ Adding Tests for Validation
[cps.git] / cps-service / src / test / groovy / org / onap / cps / api / impl / CpsDataServiceImplSpec.groovy
1 /*
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
10  *
11  *        http://www.apache.org/licenses/LICENSE-2.0
12  *
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.
18  *
19  *  SPDX-License-Identifier: Apache-2.0
20  *  ============LICENSE_END=========================================================
21  */
22
23 package org.onap.cps.api.impl
24
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.DataNode
34 import org.onap.cps.spi.model.DataNodeBuilder
35 import org.onap.cps.yang.YangTextSchemaSourceSet
36 import org.onap.cps.yang.YangTextSchemaSourceSetBuilder
37 import spock.lang.Specification
38
39 import java.time.OffsetDateTime
40
41 class CpsDataServiceImplSpec extends Specification {
42     def mockCpsDataPersistenceService = Mock(CpsDataPersistenceService)
43     def mockCpsAdminService = Mock(CpsAdminService)
44     def mockYangTextSchemaSourceSetCache = Mock(YangTextSchemaSourceSetCache)
45     def mockNotificationService = Mock(NotificationService)
46
47     def objectUnderTest = new CpsDataServiceImpl(mockCpsDataPersistenceService, mockCpsAdminService,
48             mockYangTextSchemaSourceSetCache, mockNotificationService)
49
50     def setup() {
51         mockCpsAdminService.getAnchor(dataspaceName, anchorName) >> anchor
52     }
53
54     def dataspaceName = 'some-dataspace'
55     def anchorName = 'some-anchor'
56     def schemaSetName = 'some-schema-set'
57     def anchor = Anchor.builder().name(anchorName).schemaSetName(schemaSetName).build()
58     def observedTimestamp = OffsetDateTime.now()
59
60     def 'Saving json data.'() {
61         given: 'schema set for given anchor and dataspace references test-tree model'
62             setupSchemaSetMocks('test-tree.yang')
63         when: 'save data method is invoked with test-tree json data'
64             def jsonData = TestUtils.getResourceFileContent('test-tree.json')
65             objectUnderTest.saveData(dataspaceName, anchorName, jsonData, observedTimestamp)
66         then: 'the persistence service method is invoked with correct parameters'
67             1 * mockCpsDataPersistenceService.storeDataNode(dataspaceName, anchorName,
68                 { dataNode -> dataNode.xpath == '/test-tree' })
69         and: 'data updated event is sent to notification service'
70             1 * mockNotificationService.processDataUpdatedEvent(anchor, observedTimestamp, '/', Operation.CREATE)
71     }
72
73     def 'Saving json data with invalid #scenario.'() {
74         when: 'save data method is invoked with invalid #scenario'
75             objectUnderTest.saveData(dataspaceName, anchorName, _ as String, observedTimestamp)
76         then: 'a data validation exception is thrown'
77             thrown(DataValidationException)
78         and: 'the persistence service method is not invoked'
79             0 * mockCpsDataPersistenceService.storeDataNode(_, _, _)
80         and: 'data updated event is not sent to notification service'
81             0 * mockNotificationService.processDataUpdatedEvent(_, _, _, _)
82         where: 'the following parameters are used'
83             scenario                    | dataspaceName                 | anchorName
84             'dataspace name'            | 'dataspace names with spaces' | 'anchorName'
85             'anchor name'               | 'dataspaceName'               | 'anchor name with spaces'
86             'dataspace and anchor name' | 'dataspace name with spaces'  | 'anchor name with spaces'
87     }
88
89     def 'Saving child data fragment under existing node.'() {
90         given: 'schema set for given anchor and dataspace references test-tree model'
91             setupSchemaSetMocks('test-tree.yang')
92         when: 'save data method is invoked with test-tree json data'
93             def jsonData = '{"branch": [{"name": "New"}]}'
94             objectUnderTest.saveData(dataspaceName, anchorName, '/test-tree', jsonData, observedTimestamp)
95         then: 'the persistence service method is invoked with correct parameters'
96             1 * mockCpsDataPersistenceService.addChildDataNode(dataspaceName, anchorName, '/test-tree',
97                 { dataNode -> dataNode.xpath == '/test-tree/branch[@name=\'New\']' })
98         and: 'data updated event is sent to notification service'
99             1 * mockNotificationService.processDataUpdatedEvent(anchor, observedTimestamp, '/test-tree', Operation.CREATE)
100     }
101
102     def 'Saving child data fragment under existing node with invalid #scenario.'() {
103         when: 'save data method is invoked with test-tree and an invalid #scenario'
104             objectUnderTest.saveData(dataspaceName, anchorName, '/test-tree', _ as String, observedTimestamp)
105         then: 'a data validation exception is thrown'
106             thrown(DataValidationException)
107         and: 'the persistence service method is not invoked'
108             0 * mockCpsDataPersistenceService.addChildDataNode(_, _, _,_)
109         and: 'data updated event is not sent to notification service'
110             0 * mockNotificationService.processDataUpdatedEvent(_, _, _, _)
111         where: 'the following parameters are used'
112             scenario                    | dataspaceName                 | anchorName
113             'dataspace name'            | 'dataspace names with spaces' | 'anchorName'
114             'anchor name'               | 'dataspaceName'               | 'anchor name with spaces'
115             'dataspace and anchor name' | 'dataspace name with spaces'  | 'anchor name with spaces'
116     }
117
118     def 'Saving list element data fragment under existing node.'() {
119         given: 'schema set for given anchor and dataspace references test-tree model'
120             setupSchemaSetMocks('test-tree.yang')
121         when: 'save data method is invoked with list element json data'
122             def jsonData = '{"branch": [{"name": "A"}, {"name": "B"}]}'
123             objectUnderTest.saveListElements(dataspaceName, anchorName, '/test-tree', jsonData, observedTimestamp)
124         then: 'the persistence service method is invoked with correct parameters'
125             1 * mockCpsDataPersistenceService.addListElements(dataspaceName, anchorName, '/test-tree',
126                 { dataNodeCollection ->
127                     {
128                         assert dataNodeCollection.size() == 2
129                         assert dataNodeCollection.collect { it.getXpath() }
130                             .containsAll(['/test-tree/branch[@name=\'A\']', '/test-tree/branch[@name=\'B\']'])
131                     }
132                 }
133             )
134         and: 'data updated event is sent to notification service'
135             1 * mockNotificationService.processDataUpdatedEvent(anchor, observedTimestamp, '/test-tree', Operation.UPDATE)
136     }
137
138     def 'Saving empty list element data fragment.'() {
139         given: 'schema set for given anchor and dataspace references test-tree model'
140             setupSchemaSetMocks('test-tree.yang')
141         when: 'save data method is invoked with an empty list'
142             def jsonData = '{"branch": []}'
143             objectUnderTest.saveListElements(dataspaceName, anchorName, '/test-tree', jsonData, observedTimestamp)
144         then: 'invalid data exception is thrown'
145             thrown(DataValidationException)
146     }
147
148     def 'Saving list element data fragment with invalid #scenario.'() {
149         when: 'save data method is invoked with an invalid #scenario'
150             objectUnderTest.saveListElements(dataspaceName, anchorName, '/test-tree', _ as String, observedTimestamp)
151         then: 'a data validation exception is thrown'
152             thrown(DataValidationException)
153         and: 'add list elements persistence method is not invoked'
154             0 * mockCpsDataPersistenceService.addListElements(_, _, _, _)
155         where: 'the following parameters are used'
156             scenario                    | dataspaceName                 | anchorName
157             'dataspace name'            | 'dataspace names with spaces' | 'anchorName'
158             'anchor name'               | 'dataspaceName'               | 'anchor name with spaces'
159             'dataspace and anchor name' | 'dataspace name with spaces'  | 'anchor name with spaces'
160     }
161
162     def 'Get data node with option #fetchDescendantsOption.'() {
163         def xpath = '/xpath'
164         def dataNode = new DataNodeBuilder().withXpath(xpath).build()
165         given: 'persistence service returns data for get data request'
166             mockCpsDataPersistenceService.getDataNode(dataspaceName, anchorName, xpath, fetchDescendantsOption) >> dataNode
167         expect: 'service returns same data if uses same parameters'
168             objectUnderTest.getDataNode(dataspaceName, anchorName, xpath, fetchDescendantsOption) == dataNode
169         where: 'all fetch options are supported'
170             fetchDescendantsOption << FetchDescendantsOption.values()
171     }
172
173     def 'Get data node with option invalid #scenario.'() {
174         when: 'get data node is invoked with #scenario'
175             objectUnderTest.getDataNode(dataspaceName, anchorName, '/test-tree', FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS)
176         then: 'a data validation exception is thrown'
177             thrown(DataValidationException)
178         and: 'get data node persistence service is not invoked'
179             0 * mockCpsDataPersistenceService.getDataNode(_, _, _, _)
180         where: 'the following parameters are used'
181             scenario                    | dataspaceName                 | anchorName
182             'dataspace name'            | 'dataspace names with spaces' | 'anchorName'
183             'anchor name'               | 'dataspaceName'               | 'anchor name with spaces'
184             'dataspace and anchor name' | 'dataspace name with spaces'  | 'anchor name with spaces'
185     }
186
187     def 'Update data node leaves: #scenario.'() {
188         given: 'schema set for given anchor and dataspace references test-tree model'
189             setupSchemaSetMocks('test-tree.yang')
190         when: 'update data method is invoked with json data #jsonData and parent node xpath #parentNodeXpath'
191             objectUnderTest.updateNodeLeaves(dataspaceName, anchorName, parentNodeXpath, jsonData, observedTimestamp)
192         then: 'the persistence service method is invoked with correct parameters'
193             1 * mockCpsDataPersistenceService.updateDataLeaves(dataspaceName, anchorName, expectedNodeXpath, leaves)
194         and: 'data updated event is sent to notification service'
195             1 * mockNotificationService.processDataUpdatedEvent(anchor, observedTimestamp, parentNodeXpath, Operation.UPDATE)
196         where: 'following parameters were used'
197             scenario         | parentNodeXpath | jsonData                        || expectedNodeXpath                   | leaves
198             'top level node' | '/'             | '{"test-tree": {"branch": []}}' || '/test-tree'                        | Collections.emptyMap()
199             'level 2 node'   | '/test-tree'    | '{"branch": [{"name":"Name"}]}' || '/test-tree/branch[@name=\'Name\']' | ['name': 'Name']
200     }
201
202     def 'Update data node with invalid #scenario.'() {
203         when: 'update data method is invoked with json data #jsonData and parent node xpath #parentNodeXpath'
204             objectUnderTest.updateNodeLeaves(dataspaceName, anchorName, '/', '{"test-tree": {"branch": []}}', observedTimestamp)
205         then: 'a data validation exception is thrown'
206             thrown(DataValidationException)
207         and: 'the persistence service method is not invoked'
208             0 * mockCpsDataPersistenceService.updateDataLeaves(_, _, _, _)
209         and: 'data updated event is not sent to notification service'
210             0 * mockNotificationService.processDataUpdatedEvent(_, _, _, _)
211         where: 'the following parameters are used'
212             scenario                    | dataspaceName                 | anchorName
213             'dataspace name'            | 'dataspace names with spaces' | 'anchorName'
214             'anchor name'               | 'dataspaceName'               | 'anchor name with spaces'
215             'dataspace and anchor name' | 'dataspace name with spaces'  | 'anchor name with spaces'
216     }
217
218     def 'Update list-element data node with : #scenario.'() {
219         given: 'schema set for given anchor and dataspace references bookstore model'
220             setupSchemaSetMocks('bookstore.yang')
221         when: 'update data method is invoked with json data #jsonData and parent node xpath'
222             objectUnderTest.updateNodeLeaves(dataspaceName, anchorName, '/bookstore/categories[@code=2]',
223                 jsonData, observedTimestamp)
224         then: 'the persistence service method is invoked with correct parameters'
225             thrown(DataValidationException)
226         where: 'following parameters were used'
227             scenario          | jsonData
228             'multiple expectedLeaves' | '{"code": "01","name": "some-name"}'
229             'one leaf'        | '{"name": "some-name"}'
230     }
231
232     def 'Update Bookstore node leaves' () {
233         given: 'a DMI registry model'
234             setupSchemaSetMocks('bookstore.yang')
235         and: 'the expected json string'
236             def jsonData = '{"categories":[{"code":01,"name":"Romance"}]}'
237         when: 'update data method is invoked with json data and parent node xpath'
238             objectUnderTest.updateNodeLeavesAndExistingDescendantLeaves(dataspaceName, anchorName,
239                 '/bookstore', jsonData, observedTimestamp)
240         then: 'the persistence service method is invoked with correct parameters'
241             1 * mockCpsDataPersistenceService.updateDataLeaves(dataspaceName, anchorName,
242                 "/bookstore/categories[@code='01']", ['name':'Romance', 'code': '01'])
243         and: 'the data updated event is sent to the notification service'
244             1 * mockNotificationService.processDataUpdatedEvent(anchor, observedTimestamp, '/bookstore', Operation.UPDATE)
245     }
246
247     def 'Update Bookstore node leaves with invalid #scenario' () {
248         when: 'update data method is invoked with an invalid #scenario'
249             objectUnderTest.updateNodeLeavesAndExistingDescendantLeaves(dataspaceName, anchorName,
250                 '/bookstore', _ as String, observedTimestamp)
251         then: 'a data validation exception is thrown'
252             thrown(DataValidationException)
253         and: 'the persistence service method is not invoked'
254             0 * mockCpsDataPersistenceService.updateDataLeaves(_, _, _, _)
255         and: 'the data updated event is not sent to the notification service'
256             0 * mockNotificationService.processDataUpdatedEvent(_, _, _, _)
257         where: 'the following parameters are used'
258             scenario                    | dataspaceName                 | anchorName
259             'dataspace name'            | 'dataspace names with spaces' | 'anchorName'
260             'anchor name'               | 'dataspaceName'               | 'anchor name with spaces'
261             'dataspace and anchor name' | 'dataspace name with spaces'  | 'anchor name with spaces'
262     }
263
264
265     def 'Replace data node: #scenario.'() {
266         given: 'schema set for given anchor and dataspace references test-tree model'
267             setupSchemaSetMocks('test-tree.yang')
268         when: 'replace data method is invoked with json data #jsonData and parent node xpath #parentNodeXpath'
269             objectUnderTest.replaceNodeTree(dataspaceName, anchorName, parentNodeXpath, jsonData, observedTimestamp)
270         then: 'the persistence service method is invoked with correct parameters'
271             1 * mockCpsDataPersistenceService.replaceDataNodeTree(dataspaceName, anchorName,
272                 { dataNode -> dataNode.xpath == expectedNodeXpath })
273         and: 'data updated event is sent to notification service'
274             1 * mockNotificationService.processDataUpdatedEvent(anchor, observedTimestamp, parentNodeXpath, Operation.UPDATE)
275         where: 'following parameters were used'
276             scenario         | parentNodeXpath | jsonData                        || expectedNodeXpath
277             'top level node' | '/'             | '{"test-tree": {"branch": []}}' || '/test-tree'
278             'level 2 node'   | '/test-tree'    | '{"branch": [{"name":"Name"}]}' || '/test-tree/branch[@name=\'Name\']'
279     }
280
281     def 'Replace data node with invalid #scenario.'() {
282         when: 'replace data method is invoked with invalid #scenario'
283             objectUnderTest.replaceNodeTree(dataspaceName, anchorName, '/', _ as String, observedTimestamp)
284         then: 'a data validation exception is thrown'
285             thrown(DataValidationException)
286         and: 'the persistence service method is not invoked'
287             0 * mockCpsDataPersistenceService.replaceDataNodeTree(_, _,_)
288         and: 'data updated event is not sent to notification service'
289             0 * mockNotificationService.processDataUpdatedEvent(_, _, _, _)
290         where: 'the following parameters are used'
291             scenario                    | dataspaceName                 | anchorName
292             'dataspace name'            | 'dataspace names with spaces' | 'anchorName'
293             'anchor name'               | 'dataspaceName'               | 'anchor name with spaces'
294             'dataspace and anchor name' | 'dataspace name with spaces'  | 'anchor name with spaces'
295     }
296
297     def 'Replace list content data fragment under parent node.'() {
298         given: 'schema set for given anchor and dataspace references test-tree model'
299             setupSchemaSetMocks('test-tree.yang')
300         when: 'replace list data method is invoked with list element json data'
301             def jsonData = '{"branch": [{"name": "A"}, {"name": "B"}]}'
302             objectUnderTest.replaceListContent(dataspaceName, anchorName, '/test-tree', jsonData, observedTimestamp)
303         then: 'the persistence service method is invoked with correct parameters'
304             1 * mockCpsDataPersistenceService.replaceListContent(dataspaceName, anchorName, '/test-tree',
305                 { dataNodeCollection ->
306                     {
307                         assert dataNodeCollection.size() == 2
308                         assert dataNodeCollection.collect { it.getXpath() }
309                             .containsAll(['/test-tree/branch[@name=\'A\']', '/test-tree/branch[@name=\'B\']'])
310                     }
311                 }
312             )
313         and: 'data updated event is sent to notification service'
314             1 * mockNotificationService.processDataUpdatedEvent(anchor, observedTimestamp, '/test-tree', Operation.UPDATE)
315     }
316
317     def 'Replace whole list content with empty list element.'() {
318         given: 'schema set for given anchor and dataspace references test-tree model'
319             setupSchemaSetMocks('test-tree.yang')
320         when: 'replace list data method is invoked with empty list'
321             def jsonData = '{"branch": []}'
322             objectUnderTest.replaceListContent(dataspaceName, anchorName, '/test-tree', jsonData, observedTimestamp)
323         then: 'invalid data exception is thrown'
324             thrown(DataValidationException)
325     }
326
327     def 'Replace whole list content with an invalid #scenario.'() {
328         when: 'replace list data method is invoked with invalid #scenario'
329             objectUnderTest.replaceListContent(dataspaceName, anchorName, '/test-tree', _ as Collection<DataNode>, observedTimestamp)
330         then: 'a data validation exception is thrown'
331             thrown(DataValidationException)
332         and: 'the persistence service method is not invoked'
333             0 * mockCpsDataPersistenceService.replaceListContent(_, _,_)
334         and: 'data updated event is not sent to notification service'
335             0 * mockNotificationService.processDataUpdatedEvent(_, _, _, _)
336         where: 'the following parameters are used'
337             scenario                    | dataspaceName                 | anchorName
338             'dataspace name'            | 'dataspace names with spaces' | 'anchorName'
339             'anchor name'               | 'dataspaceName'               | 'anchor name with spaces'
340             'dataspace and anchor name' | 'dataspace name with spaces'  | 'anchor name with spaces'
341     }
342
343     def 'Delete list element under existing node.'() {
344         given: 'schema set for given anchor and dataspace references test-tree model'
345             setupSchemaSetMocks('test-tree.yang')
346         when: 'delete list data method is invoked with list element json data'
347             objectUnderTest.deleteListOrListElement(dataspaceName, anchorName, '/test-tree/branch', observedTimestamp)
348         then: 'the persistence service method is invoked with correct parameters'
349             1 * mockCpsDataPersistenceService.deleteListDataNode(dataspaceName, anchorName, '/test-tree/branch')
350         and: 'data updated event is sent to notification service'
351             1 * mockNotificationService.processDataUpdatedEvent(anchor, observedTimestamp, '/test-tree/branch', Operation.DELETE)
352     }
353
354
355     def 'Delete list element with an invalid #scenario.'() {
356         when: 'delete list data method is invoked with with invalid #scenario'
357             objectUnderTest.deleteDataNode(dataspaceName, anchorName, '/data-node', observedTimestamp)
358         then: 'a data validation exception is thrown'
359             thrown(DataValidationException)
360         and: 'the persistence service method is not invoked'
361             0 * mockCpsDataPersistenceService.deleteListDataNode(_, _, _)
362         and: 'data updated event is not sent to notification service'
363             0 * mockNotificationService.processDataUpdatedEvent(_, _, _, _)
364         where: 'the following parameters are used'
365             scenario                    | dataspaceName                 | anchorName
366             'dataspace name'            | 'dataspace names with spaces' | 'anchorName'
367             'anchor name'               | 'dataspaceName'               | 'anchor name with spaces'
368             'dataspace and anchor name' | 'dataspace name with spaces'  | 'anchor name with spaces'
369     }
370
371     def 'Delete data node under anchor and dataspace.'() {
372         given: 'schema set for given anchor and dataspace references test tree model'
373             setupSchemaSetMocks('test-tree.yang')
374         when: 'delete data node method is invoked with correct parameters'
375             objectUnderTest.deleteDataNode(dataspaceName, anchorName, '/data-node', observedTimestamp)
376         then: 'the persistence service method is invoked with the correct parameters'
377             1 * mockCpsDataPersistenceService.deleteDataNode(dataspaceName, anchorName, '/data-node')
378         and: 'data updated event is sent to notification service'
379             1 * mockNotificationService.processDataUpdatedEvent(anchor, observedTimestamp, '/data-node', Operation.DELETE)
380     }
381
382     def 'Delete data node with an invalid #scenario.'() {
383         when: 'delete data node method is invoked with invalid #scenario'
384             objectUnderTest.deleteDataNode(dataspaceName, anchorName, '/data-node', observedTimestamp)
385         then: 'a data validation exception is thrown'
386             thrown(DataValidationException)
387         and: 'the persistence service method is not invoked'
388             0 * mockCpsDataPersistenceService.deleteDataNode(_, _, _)
389         and: 'data updated event is not sent to notification service'
390             0 * mockNotificationService.processDataUpdatedEvent(_, _, _, _)
391         where: 'the following parameters are used'
392             scenario                    | dataspaceName                 | anchorName
393             'dataspace name'            | 'dataspace names with spaces' | 'anchorName'
394             'anchor name'               | 'dataspaceName'               | 'anchor name with spaces'
395             'dataspace and anchor name' | 'dataspace name with spaces'  | 'anchor name with spaces'
396     }
397
398     def 'Delete all data nodes for a given anchor and dataspace.'() {
399         given: 'schema set for given anchor and dataspace references test tree model'
400             setupSchemaSetMocks('test-tree.yang')
401         when: 'delete data node method is invoked with correct parameters'
402             objectUnderTest.deleteDataNodes(dataspaceName, anchorName, observedTimestamp)
403         then: 'the persistence service method is invoked with the correct parameters'
404             1 * mockCpsDataPersistenceService.deleteDataNodes(dataspaceName, anchorName)
405         and: 'data updated event is sent to notification service'
406             1 * mockNotificationService.processDataUpdatedEvent(anchor, observedTimestamp, '/', Operation.DELETE)
407
408     }
409
410     def setupSchemaSetMocks(String... yangResources) {
411         def mockYangTextSchemaSourceSet = Mock(YangTextSchemaSourceSet)
412         mockYangTextSchemaSourceSetCache.get(dataspaceName, schemaSetName) >> mockYangTextSchemaSourceSet
413         def yangResourceNameToContent = TestUtils.getYangResourcesAsMap(yangResources)
414         def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent).getSchemaContext()
415         mockYangTextSchemaSourceSet.getSchemaContext() >> schemaContext
416     }
417
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()
423     }
424
425     def 'close session'(){
426         given: 'session Id from calling the start session method'
427             def sessionId = objectUnderTest.startSession()
428         when: 'close session method is called'
429             objectUnderTest.closeSession(sessionId)
430         then: 'the persistence service method to close session is invoked'
431             1 * mockCpsDataPersistenceService.closeSession(sessionId)
432     }
433 }