2 * ============LICENSE_START=======================================================
3 * Copyright (C) 2023-2025 Nordix Foundation
4 * ================================================================================
5 * Licensed under the Apache License, Version 2.0 (the 'License');
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an 'AS IS' BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
17 * SPDX-License-Identifier: Apache-2.0
18 * ============LICENSE_END=========================================================
21 package org.onap.cps.integration.functional.cps
23 import org.onap.cps.api.CpsModuleService
24 import org.onap.cps.integration.base.FunctionalSpecBase
25 import org.onap.cps.api.parameters.CascadeDeleteAllowed
26 import org.onap.cps.api.exceptions.AlreadyDefinedException
27 import org.onap.cps.api.exceptions.DataspaceNotFoundException
28 import org.onap.cps.api.exceptions.ModelValidationException
29 import org.onap.cps.api.exceptions.SchemaSetInUseException
30 import org.onap.cps.api.exceptions.SchemaSetNotFoundException
31 import org.onap.cps.api.model.ModuleDefinition
32 import org.onap.cps.api.model.ModuleReference
34 class ModuleServiceIntegrationSpec extends FunctionalSpecBase {
36 CpsModuleService objectUnderTest
38 private static def originalNumberOfModuleReferences = 2 // bookstore has two modules
39 private static def bookStoreModuleReference = new ModuleReference('stores','2024-02-08')
40 private static def bookStoreModuleReferenceWithNamespace = new ModuleReference('stores','2024-02-08', 'org:onap:cps:sample')
41 private static def bookStoreTypesModuleReference = new ModuleReference('bookstore-types','2024-01-30')
42 private static def bookStoreTypesModuleReferenceWithNamespace = new ModuleReference('bookstore-types','2024-01-30', 'org:onap:cps:types:sample')
43 static def NEW_RESOURCE_REVISION = '2023-05-10'
44 static def NEW_RESOURCE_CONTENT = """
47 namespace "org:onap:ccsdk:sample";
49 revision "2023-05-10" {
56 def yangResourceContentPerName = [:]
57 def allModuleReferences = []
58 def noNewModules = [:]
59 def bookstoreModelFileContent = readResourceDataFile('bookstore/bookstore.yang')
60 def bookstoreTypesFileContent = readResourceDataFile('bookstore/bookstore-types.yang')
62 def setup() { objectUnderTest = cpsModuleService }
65 C R E A T E S C H E M A S E T U S E - C A S E S
68 def 'Create new schema set from yang resources with #scenario'() {
69 given: 'a new schema set with #numberOfModules modules'
70 populateYangResourceContentPerNameAndAllModuleReferences(numberOfNewModules)
71 when: 'the new schema set is created'
72 objectUnderTest.createSchemaSet(FUNCTIONAL_TEST_DATASPACE_1, 'newSchemaSet', yangResourceContentPerName)
73 then: 'the number of module references has increased by #numberOfNewModules'
74 def yangResourceModuleReferences = objectUnderTest.getYangResourceModuleReferences(FUNCTIONAL_TEST_DATASPACE_1)
75 originalNumberOfModuleReferences + numberOfNewModules == yangResourceModuleReferences.size()
77 objectUnderTest.deleteSchemaSetsWithCascade(FUNCTIONAL_TEST_DATASPACE_1, [ 'newSchemaSet' ])
78 where: 'the following parameters are used'
79 scenario | numberOfNewModules
80 'two valid new modules' | 2
81 'empty schema set' | 0
82 'over max batch size #modules' | 101
85 def 'Create new schema set with recommended filename format but invalid yang'() {
86 given: 'a filename using RFC6020 recommended format (for coverage only)'
87 def fileName = 'test@2023-05-11.yang'
88 when: 'attempt to create a schema set with invalid Yang'
89 objectUnderTest.createSchemaSet(FUNCTIONAL_TEST_DATASPACE_1, 'newSchemaSet', [(fileName) :'invalid yang'])
90 then: 'a model validation exception'
91 thrown(ModelValidationException)
94 def 'Create new schema set from modules with #scenario'() {
95 given: 'a new schema set with #numberOfNewModules modules'
96 populateYangResourceContentPerNameAndAllModuleReferences(numberOfNewModules)
97 and: 'add existing module references (optional)'
98 allModuleReferences.addAll(existingModuleReferences)
99 when: 'the new schema set is created'
100 def schemaSetName = "NewSchemaWith${numberOfNewModules}Modules"
101 objectUnderTest.createSchemaSetFromModules(FUNCTIONAL_TEST_DATASPACE_1, schemaSetName, yangResourceContentPerName, allModuleReferences)
102 and: 'associated with a new anchor'
103 cpsAnchorService.createAnchor(FUNCTIONAL_TEST_DATASPACE_1, schemaSetName, 'newAnchor')
104 then: 'the new anchor has the correct number of modules'
105 def yangResourceModuleReferences = objectUnderTest.getYangResourcesModuleReferences(FUNCTIONAL_TEST_DATASPACE_1, 'newAnchor')
106 assert expectedNumberOfModulesForAnchor == yangResourceModuleReferences.size()
107 and: 'the schema set has the correct number of modules too'
108 def dataspaceEntity = dataspaceRepository.getByName(FUNCTIONAL_TEST_DATASPACE_1)
109 def schemaSetEntity = schemaSetRepository.getByDataspaceAndName(dataspaceEntity, schemaSetName)
110 assert expectedNumberOfModulesForAnchor == schemaSetEntity.yangResources.size()
112 objectUnderTest.deleteSchemaSetsWithCascade(FUNCTIONAL_TEST_DATASPACE_1, [ schemaSetName.toString() ])
113 where: 'the following module references are provided'
114 scenario | numberOfNewModules | existingModuleReferences || expectedNumberOfModulesForAnchor
115 'empty schema set' | 0 | [ ] || 0
116 'one existing module' | 0 | [ bookStoreModuleReference ] || 1
117 'two new modules' | 2 | [ ] || 2
118 'two new modules, one existing' | 2 | [ bookStoreModuleReference ] || 3
119 'over max batch size #modules' | 101 | [ ] || 101
120 'two valid, one invalid module' | 2 | [ new ModuleReference('NOT EXIST','IRRELEVANT') ] || 2
123 def 'Duplicate schema content.'() {
124 given: 'a map of yang resources'
125 populateYangResourceContentPerNameAndAllModuleReferences(1)
126 when: 'a new schema set is created'
127 objectUnderTest.createSchemaSet(FUNCTIONAL_TEST_DATASPACE_1, 'newSchema1', yangResourceContentPerName)
128 then: 'the dataspace has one new module (reference)'
129 def numberOfModuleReferencesAfterFirstSchemaSetHasBeenAdded = objectUnderTest.getYangResourceModuleReferences(FUNCTIONAL_TEST_DATASPACE_1).size()
130 assert numberOfModuleReferencesAfterFirstSchemaSetHasBeenAdded == originalNumberOfModuleReferences + 1
131 when: 'a second new schema set is created'
132 objectUnderTest.createSchemaSet(FUNCTIONAL_TEST_DATASPACE_1, 'newSchema2', yangResourceContentPerName)
133 then: 'the dataspace has no additional module (reference)'
134 assert numberOfModuleReferencesAfterFirstSchemaSetHasBeenAdded == objectUnderTest.getYangResourceModuleReferences(FUNCTIONAL_TEST_DATASPACE_1).size()
136 objectUnderTest.deleteSchemaSetsWithCascade(FUNCTIONAL_TEST_DATASPACE_1, [ 'newSchema1', 'newSchema2'])
139 def 'Attempt to create schema set, error scenario: #scenario.'() {
140 when: 'attempt to store schema set #schemaSetName in dataspace #dataspaceName'
141 populateYangResourceContentPerNameAndAllModuleReferences(0)
142 objectUnderTest.createSchemaSet(dataspaceName, schemaSetName, yangResourceContentPerName)
143 then: 'an #expectedException is thrown'
144 thrown(expectedException)
145 where: 'the following data is used'
146 scenario | dataspaceName | schemaSetName || expectedException
147 'dataspace does not exist' | 'unknown' | 'not-relevant' || DataspaceNotFoundException
148 'schema set already exists' | FUNCTIONAL_TEST_DATASPACE_1 | BOOKSTORE_SCHEMA_SET || AlreadyDefinedException
151 def 'Attempt to create duplicate schema set from modules.'() {
152 when: 'attempt to store duplicate schema set from modules'
153 objectUnderTest.createSchemaSetFromModules(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_SCHEMA_SET, yangResourceContentPerName, [])
154 then: 'an Already Defined Exception is thrown'
155 thrown(AlreadyDefinedException)
160 R E A D S C H E M A S E T I N F O U S E - C A S E S
163 def 'Retrieving module definitions by anchor.'() {
164 when: 'the module definitions for an anchor are retrieved'
165 def result = objectUnderTest.getModuleDefinitionsByAnchorName(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1)
166 then: 'the correct module definitions are returned'
167 assert result.size() == 2
168 assert result.contains(new ModuleDefinition('stores','2024-02-08',bookstoreModelFileContent))
169 assert result.contains(new ModuleDefinition('bookstore-types','2024-01-30', bookstoreTypesFileContent))
172 def 'Retrieving module definitions: #scenarios'() {
173 when: 'module definitions for module name are retrieved'
174 def result = objectUnderTest.getModuleDefinitionsByAnchorAndModule(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1, moduleName, moduleRevision)
175 then: 'the correct module definitions are returned'
176 if (expectedNumberOfDefinitions > 0) {
177 assert result.size() == expectedNumberOfDefinitions
178 def expectedModuleDefinition = new ModuleDefinition('stores', '2024-02-08', bookstoreModelFileContent)
179 assert result[0] == expectedModuleDefinition
181 where: 'following parameters are used'
182 scenarios | moduleName | moduleRevision || expectedNumberOfDefinitions
183 'correct module name and revision' | 'stores' | '2024-02-08' || 1
184 'correct module name' | 'stores' | null || 1
185 'incorrect module name' | 'other' | null || 0
186 'incorrect revision' | 'stores' | '2025-11-22' || 0
189 def 'Retrieving yang resource module references by anchor.'() {
190 when: 'the yang resource module references for an anchor are retrieved'
191 def result = objectUnderTest.getYangResourcesModuleReferences(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1)
192 then: 'the correct module references are returned'
193 assert result.size() == 2
194 assert result.containsAll(bookStoreModuleReference, bookStoreTypesModuleReference)
197 def 'Identifying new module references with #scenario'() {
198 when: 'identifyNewModuleReferences is called'
199 def result = objectUnderTest.identifyNewModuleReferences(allModuleReferences)
200 then: 'the correct module references are returned'
201 assert result.size() == expectedResult.size()
202 assert result.containsAll(expectedResult)
203 where: 'the following data is used'
204 scenario | allModuleReferences || expectedResult
205 'just new module references' | [new ModuleReference('new1', 'r1'), new ModuleReference('new2', 'r1')] || [new ModuleReference('new1', 'r1'), new ModuleReference('new2', 'r1')]
206 'one new module,one existing reference' | [new ModuleReference('new1', 'r1'), bookStoreModuleReference] || [new ModuleReference('new1', 'r1')]
207 'no new module references' | [bookStoreModuleReference] || []
208 'no module references' | [] || []
209 'module references collection is null' | null || []
212 def 'Retrieve schema set.'() {
213 when: 'a specific schema set is retrieved'
214 def result = objectUnderTest.getSchemaSet(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_SCHEMA_SET)
215 then: 'the result has the correct name and module(s)'
216 assert result.name == 'bookstoreSchemaSet'
217 assert result.moduleReferences.size() == 2
218 assert result.moduleReferences.containsAll(bookStoreModuleReferenceWithNamespace, bookStoreTypesModuleReferenceWithNamespace)
221 def 'Retrieve all schema sets.'() {
222 given: 'an extra schema set is stored'
223 populateYangResourceContentPerNameAndAllModuleReferences(1)
224 objectUnderTest.createSchemaSet(FUNCTIONAL_TEST_DATASPACE_1, 'newSchema1', yangResourceContentPerName)
225 when: 'all schema sets are retrieved'
226 def result = objectUnderTest.getSchemaSets(FUNCTIONAL_TEST_DATASPACE_1)
227 then: 'the result contains all expected schema sets'
228 assert result.name.size() == 2
229 assert result.name.containsAll('bookstoreSchemaSet', 'newSchema1')
231 objectUnderTest.deleteSchemaSetsWithCascade(FUNCTIONAL_TEST_DATASPACE_1, ['newSchema1'])
235 D E L E T E S C H E M A S E T U S E - C A S E S
238 def 'Delete schema sets with(out) cascade.'() {
239 given: 'a schema set'
240 populateYangResourceContentPerNameAndAllModuleReferences(1)
241 objectUnderTest.createSchemaSet(FUNCTIONAL_TEST_DATASPACE_1, 'newSchemaSet', yangResourceContentPerName)
242 and: 'optionally create anchor for the schema set'
243 if (associateWithAnchor) {
244 cpsAnchorService.createAnchor(FUNCTIONAL_TEST_DATASPACE_1, 'newSchemaSet', 'newAnchor')
246 when: 'attempt to delete the schema set'
248 objectUnderTest.deleteSchemaSet(FUNCTIONAL_TEST_DATASPACE_1, 'newSchemaSet', cascadeDeleteAllowedOption)
250 catch (Exception e) { // only accept correct exception when schema set cannot be deleted
251 assert e instanceof SchemaSetInUseException && expectSchemaSetStillPresent
253 then: 'check if the dataspace still contains the new schema set or not'
254 def remainingSchemaSetNames = objectUnderTest.getSchemaSets(FUNCTIONAL_TEST_DATASPACE_1).name
255 assert remainingSchemaSetNames.contains('newSchemaSet') == expectSchemaSetStillPresent
257 objectUnderTest.deleteSchemaSetsWithCascade(FUNCTIONAL_TEST_DATASPACE_1, ['newSchemaSet'])
258 where: 'the following options are used'
259 associateWithAnchor | cascadeDeleteAllowedOption || expectSchemaSetStillPresent
260 false | CascadeDeleteAllowed.CASCADE_DELETE_ALLOWED || false
261 false | CascadeDeleteAllowed.CASCADE_DELETE_PROHIBITED || false
262 true | CascadeDeleteAllowed.CASCADE_DELETE_ALLOWED || false
263 true | CascadeDeleteAllowed.CASCADE_DELETE_PROHIBITED || true
266 def 'Delete schema sets with shared resources.'() {
267 given: 'a new schema set'
268 populateYangResourceContentPerNameAndAllModuleReferences(1)
269 objectUnderTest.createSchemaSet(FUNCTIONAL_TEST_DATASPACE_1, 'newSchemaSet1', yangResourceContentPerName)
270 and: 'another schema set which shares one yang resource (module)'
271 populateYangResourceContentPerNameAndAllModuleReferences(2)
272 objectUnderTest.createSchemaSet(FUNCTIONAL_TEST_DATASPACE_1, 'newSchemaSet2', yangResourceContentPerName)
273 when: 'all schema sets are retrieved'
274 def moduleRevisions = objectUnderTest.getYangResourceModuleReferences(FUNCTIONAL_TEST_DATASPACE_1).revision
275 then: 'both modules (revisions) are present'
276 assert moduleRevisions.containsAll(['2000-01-01', '2000-01-01'])
277 when: 'delete the second schema set that has two resources one of which is a shared resource'
278 objectUnderTest.deleteSchemaSetsWithCascade(FUNCTIONAL_TEST_DATASPACE_1, ['newSchemaSet2'])
279 then: 'only the second schema set is deleted'
280 def remainingSchemaSetNames = objectUnderTest.getSchemaSets(FUNCTIONAL_TEST_DATASPACE_1).name
281 assert remainingSchemaSetNames.contains('newSchemaSet1')
282 assert !remainingSchemaSetNames.contains('newSchemaSet2')
283 and: 'only the shared module (revision) remains'
284 def remainingModuleRevisions = objectUnderTest.getYangResourceModuleReferences(FUNCTIONAL_TEST_DATASPACE_1).revision
285 assert remainingModuleRevisions.contains('2000-01-01')
286 assert !remainingModuleRevisions.contains('2001-01-01')
288 objectUnderTest.deleteSchemaSetsWithCascade(FUNCTIONAL_TEST_DATASPACE_1, ['newSchemaSet1'])
291 def 'Delete schema set error scenario: #scenario.'() {
292 when: 'attempt to delete a schema set where #scenario'
293 objectUnderTest.deleteSchemaSet(dataspaceName, schemaSetName, CascadeDeleteAllowed.CASCADE_DELETE_PROHIBITED)
294 then: 'an #expectedException is thrown'
295 thrown(expectedException)
296 where: 'the following data is used'
297 scenario | dataspaceName | schemaSetName || expectedException
298 'dataspace does not exist' | 'unknown' | 'not-relevant' || DataspaceNotFoundException
299 'schema set does not exists' | FUNCTIONAL_TEST_DATASPACE_1 | 'unknown' || SchemaSetNotFoundException
306 def 'Upgrade schema set [with existing and new modules, no matching module set tag in NCMP]'() {
307 given: 'an anchor and schema set with 2 modules (to be upgraded)'
308 populateYangResourceContentPerNameAndAllModuleReferences('original', 2)
309 objectUnderTest.createSchemaSetFromModules(FUNCTIONAL_TEST_DATASPACE_1, 'targetSchema', yangResourceContentPerName, allModuleReferences)
310 cpsAnchorService.createAnchor(FUNCTIONAL_TEST_DATASPACE_1, 'targetSchema', 'targetAnchor')
311 def yangResourceModuleReferencesBeforeUpgrade = objectUnderTest.getYangResourcesModuleReferences(FUNCTIONAL_TEST_DATASPACE_1, 'targetAnchor')
312 assert yangResourceModuleReferencesBeforeUpgrade.size() == 2
313 assert yangResourceModuleReferencesBeforeUpgrade.containsAll([new ModuleReference('original_0','2000-01-01'),new ModuleReference('original_1','2001-01-01')])
314 and: 'two new 2 modules (from node)'
315 populateYangResourceContentPerNameAndAllModuleReferences('new', 2)
316 def newModuleReferences = [new ModuleReference('new_0','2000-01-01'),new ModuleReference('new_1','2001-01-01')]
317 and: 'a list of all module references (normally retrieved from node)'
318 def allOtherModuleReferences = []
319 allOtherModuleReferences.add(bookStoreModuleReference)
320 allOtherModuleReferences.addAll(newModuleReferences)
321 when: 'the schema set is upgraded'
322 objectUnderTest.upgradeSchemaSetFromModules(FUNCTIONAL_TEST_DATASPACE_1, 'targetSchema', yangResourceContentPerName, allOtherModuleReferences)
323 then: 'the new anchor has the correct new and existing modules'
324 def yangResourceModuleReferencesAfterUpgrade = objectUnderTest.getYangResourcesModuleReferences(FUNCTIONAL_TEST_DATASPACE_1, 'targetAnchor')
325 assert yangResourceModuleReferencesAfterUpgrade.size() == 3
326 assert yangResourceModuleReferencesAfterUpgrade.contains(bookStoreModuleReference)
327 assert yangResourceModuleReferencesAfterUpgrade.containsAll(newModuleReferences);
329 objectUnderTest.deleteSchemaSetsWithCascade(FUNCTIONAL_TEST_DATASPACE_1, ['targetSchema'])
332 def 'Upgrade existing schema set from another anchor [used in NCMP for matching module set tag]'() {
333 given: 'an anchor and schema set with 1 module (target)'
334 populateYangResourceContentPerNameAndAllModuleReferences('target', 1)
335 objectUnderTest.createSchemaSetFromModules(FUNCTIONAL_TEST_DATASPACE_1, 'targetSchema', yangResourceContentPerName, allModuleReferences)
336 cpsAnchorService.createAnchor(FUNCTIONAL_TEST_DATASPACE_1, 'targetSchema', 'targetAnchor')
337 def moduleReferencesBeforeUpgrade = objectUnderTest.getYangResourcesModuleReferences(FUNCTIONAL_TEST_DATASPACE_1, 'targetAnchor')
338 assert moduleReferencesBeforeUpgrade.size() == 1
339 and: 'another anchor and schema set with 2 other modules (source for upgrade)'
340 populateYangResourceContentPerNameAndAllModuleReferences('source', 2)
341 objectUnderTest.createSchemaSetFromModules(FUNCTIONAL_TEST_DATASPACE_1, 'sourceSchema', yangResourceContentPerName, allModuleReferences)
342 cpsAnchorService.createAnchor(FUNCTIONAL_TEST_DATASPACE_1, 'sourceSchema', 'sourceAnchor')
343 def yangResourcesModuleReferences = objectUnderTest.getYangResourcesModuleReferences(FUNCTIONAL_TEST_DATASPACE_1, 'sourceAnchor')
344 assert yangResourcesModuleReferences.size() == 2
345 when: 'the target schema is upgraded using the module references from the source anchor'
346 def moduleReferencesFromSourceAnchor = objectUnderTest.getYangResourcesModuleReferences(FUNCTIONAL_TEST_DATASPACE_1, 'sourceAnchor')
347 objectUnderTest.upgradeSchemaSetFromModules(FUNCTIONAL_TEST_DATASPACE_1, 'targetSchema', noNewModules, moduleReferencesFromSourceAnchor)
348 then: 'the target schema now refers to the source modules (with namespace) modules'
349 def schemaSetModuleReferencesAfterUpgrade = getObjectUnderTest().getSchemaSet(FUNCTIONAL_TEST_DATASPACE_1, 'targetSchema').moduleReferences
350 assert schemaSetModuleReferencesAfterUpgrade.containsAll([new ModuleReference('source_0','2000-01-01','org:onap:ccsdk:sample'),new ModuleReference('source_1','2001-01-01','org:onap:ccsdk:sample')]);
351 and: 'the associated target anchor has the same module references (without namespace but that is a legacy issue)'
352 def anchorModuleReferencesAfterUpgrade = objectUnderTest.getYangResourcesModuleReferences(FUNCTIONAL_TEST_DATASPACE_1, 'targetAnchor')
353 assert anchorModuleReferencesAfterUpgrade.containsAll([new ModuleReference('source_0','2000-01-01'),new ModuleReference('source_1','2001-01-01')]);
355 objectUnderTest.deleteSchemaSetsWithCascade(FUNCTIONAL_TEST_DATASPACE_1, ['sourceSchema', 'targetSchema'])
359 H E L P E R M E T H O D S
362 def populateYangResourceContentPerNameAndAllModuleReferences(numberOfModules) {
363 populateYangResourceContentPerNameAndAllModuleReferences('name', numberOfModules)
366 def populateYangResourceContentPerNameAndAllModuleReferences(namePrefix, numberOfModules) {
367 yangResourceContentPerName.clear()
368 allModuleReferences.clear()
369 numberOfModules.times {
370 def uniqueName = namePrefix + '_' + it
371 def uniqueRevision = String.valueOf(2000 + it) + '-01-01'
372 allModuleReferences.add(new ModuleReference(uniqueName, uniqueRevision))
373 def uniqueContent = NEW_RESOURCE_CONTENT.replace(NEW_RESOURCE_REVISION, uniqueRevision).replace('module test_module', 'module '+uniqueName)
374 yangResourceContentPerName.put(uniqueName, uniqueContent)