diff options
Diffstat (limited to 'cps-ncmp-service/src/test/groovy/org')
84 files changed, 2080 insertions, 1265 deletions
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/data/models/DatastoreTypeSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/data/models/DatastoreTypeSpec.groovy new file mode 100644 index 000000000..3a6737d3c --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/data/models/DatastoreTypeSpec.groovy @@ -0,0 +1,46 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2024 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.api.data.models + +import org.onap.cps.ncmp.api.data.exceptions.InvalidDatastoreException +import spock.lang.Specification + +class DatastoreTypeSpec extends Specification { + + def 'Converting string to enum.'() { + expect: 'converting string to enum results in the correct enum value' + DatastoreType.fromDatastoreName(datastoreName) == expectedEnum + where: 'the following datastore names are used' + datastoreName || expectedEnum + 'ncmp-datastore:operational' || DatastoreType.OPERATIONAL + 'ncmp-datastore:passthrough-running' || DatastoreType.PASSTHROUGH_RUNNING + 'ncmp-datastore:passthrough-operational' || DatastoreType.PASSTHROUGH_OPERATIONAL + } + + def 'Converting unknown name string to enum.'() { + when: 'attempt converting unknown datastore name' + DatastoreType.fromDatastoreName('unknown') + then: 'an invalid datastore exception is thrown' + def thrown = thrown(InvalidDatastoreException) + assert thrown.message.contains('unknown is an invalid datastore') + } + +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/data/models/OperationTypeSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/data/models/OperationTypeSpec.groovy new file mode 100644 index 000000000..f5c6d0f1e --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/data/models/OperationTypeSpec.groovy @@ -0,0 +1,48 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2024 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.api.data.models + +import org.onap.cps.ncmp.impl.data.exceptions.InvalidOperationException +import spock.lang.Specification + +class OperationTypeSpec extends Specification { + + def 'Converting string to enum.'() { + expect: 'converting string to enum results in the correct enum value' + OperationType.fromOperationName(operationName) == expectedEnum + where: 'the following datastore names are used' + operationName || expectedEnum + 'read' || OperationType.READ + 'create' || OperationType.CREATE + 'update' || OperationType.UPDATE + 'patch' || OperationType.PATCH + 'delete' || OperationType.DELETE + } + + def 'Converting unknown name string to enum.'() { + when: 'attempt converting unknown datastore name' + OperationType.fromOperationName('unknown') + then: 'an invalid operation exception is thrown' + def thrown = thrown(InvalidOperationException) + assert thrown.message.contains('unknown is an invalid operation') + } + +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/DataJobServiceImplSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/DataJobServiceImplSpec.groovy index 43787640a..9cee2bdba 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/DataJobServiceImplSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/DataJobServiceImplSpec.groovy @@ -24,17 +24,25 @@ import ch.qos.logback.classic.Level import ch.qos.logback.classic.Logger import ch.qos.logback.classic.spi.ILoggingEvent import ch.qos.logback.core.read.ListAppender +import org.onap.cps.ncmp.impl.datajobs.DataJobServiceImpl +import org.onap.cps.ncmp.impl.datajobs.DmiSubJobRequestHandler +import org.onap.cps.ncmp.impl.datajobs.WriteRequestExaminer import org.slf4j.LoggerFactory -import org.onap.cps.ncmp.api.models.datajob.DataJobReadRequest -import org.onap.cps.ncmp.api.models.datajob.DataJobWriteRequest -import org.onap.cps.ncmp.api.models.datajob.DataJobMetadata -import org.onap.cps.ncmp.api.models.datajob.ReadOperation -import org.onap.cps.ncmp.api.models.datajob.WriteOperation +import org.onap.cps.ncmp.api.datajobs.models.DataJobReadRequest +import org.onap.cps.ncmp.api.datajobs.models.DataJobWriteRequest +import org.onap.cps.ncmp.api.datajobs.models.DataJobMetadata +import org.onap.cps.ncmp.api.datajobs.models.ReadOperation +import org.onap.cps.ncmp.api.datajobs.models.WriteOperation import spock.lang.Specification -class DataJobServiceImplSpec extends Specification{ +class DataJobServiceImplSpec extends Specification { - def objectUnderTest = new DataJobServiceImpl() + def mockWriteRequestExaminer = Mock(WriteRequestExaminer) + def mockDmiSubJobRequestHandler = Mock(DmiSubJobRequestHandler) + + def objectUnderTest = new DataJobServiceImpl(mockDmiSubJobRequestHandler, mockWriteRequestExaminer) + + def myDataJobMetadata = new DataJobMetadata('', '', '') def logger = Spy(ListAppender<ILoggingEvent>) @@ -46,28 +54,27 @@ class DataJobServiceImplSpec extends Specification{ ((Logger) LoggerFactory.getLogger(DataJobServiceImpl.class)).detachAndStopAllAppenders() } - def '#operation data job request.'() { - given: 'data job metadata' - def dataJobMetadata = new DataJobMetadata('client-topic', 'application/vnd.3gpp.object-tree-hierarchical+json', 'application/3gpp-json-patch+json') - when: 'read/write data job request is processed' - if (operation == 'read') { - objectUnderTest.readDataJob('some-job-id', dataJobMetadata, new DataJobReadRequest([getWriteOrReadOperationRequest(operation)])) - } else { - objectUnderTest.writeDataJob('some-job-id', dataJobMetadata, new DataJobWriteRequest([getWriteOrReadOperationRequest(operation)])) - } + def 'Read data job request.'() { + when: 'read data job request is processed' + def readOperation = new ReadOperation('', '', '', [], [], '', '', 1) + objectUnderTest.readDataJob('my-job-id', myDataJobMetadata, new DataJobReadRequest([readOperation])) then: 'the data job id is correctly logged' def loggingEvent = logger.list[0] assert loggingEvent.level == Level.INFO - assert loggingEvent.formattedMessage.contains('data job id for ' + operation + ' operation is: some-job-id') - where: 'the following data job operations are used' - operation << ['read', 'write'] + assert loggingEvent.formattedMessage.contains('data job id for read operation is: my-job-id') } - def getWriteOrReadOperationRequest(operation) { - if (operation == 'write') { - return new WriteOperation('some/write/path', 'add', 'some-operation-id', 'some-value') - } - return new ReadOperation('some/read/path', 'read', 'some-operation-id', ['some-attrib-1'], ['some-field-1'], 'some-filter', 'some-scope-type', 1) + def 'Write data-job request.'() { + given: 'data job metadata and write request' + def dataJobWriteRequest = new DataJobWriteRequest([new WriteOperation('', '', '', null)]) + and: 'a map of producer key and dmi 3gpp write operation' + def dmiWriteOperationsPerProducerKey = [:] + when: 'write data job request is processed' + objectUnderTest.writeDataJob('my-job-id', myDataJobMetadata, dataJobWriteRequest) + then: 'the examiner service is called and a map is returned' + 1 * mockWriteRequestExaminer.splitDmiWriteOperationsFromRequest('my-job-id', dataJobWriteRequest) >> dmiWriteOperationsPerProducerKey + and: 'the dmi request handler is called with the result from the examiner' + 1 * mockDmiSubJobRequestHandler.sendRequestsToDmi('my-job-id', myDataJobMetadata, dmiWriteOperationsPerProducerKey) } def setupLogger() { @@ -76,4 +83,4 @@ class DataJobServiceImplSpec extends Specification{ setupLogger.addAppender(logger) logger.start() } -} +}
\ No newline at end of file diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/DmiSubJobRequestHandlerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/DmiSubJobRequestHandlerSpec.groovy new file mode 100644 index 000000000..6dcd022c0 --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/DmiSubJobRequestHandlerSpec.groovy @@ -0,0 +1,41 @@ +package org.onap.cps.ncmp.api.impl + +import com.fasterxml.jackson.databind.ObjectMapper +import org.onap.cps.ncmp.api.data.models.OperationType +import org.onap.cps.ncmp.api.datajobs.models.DataJobMetadata +import org.onap.cps.ncmp.api.datajobs.models.DmiWriteOperation +import org.onap.cps.ncmp.api.datajobs.models.ProducerKey +import org.onap.cps.ncmp.api.datajobs.models.SubJobWriteResponse +import org.onap.cps.ncmp.api.impl.client.DmiRestClient +import org.onap.cps.ncmp.api.impl.config.DmiProperties +import org.onap.cps.ncmp.impl.datajobs.DmiSubJobRequestHandler +import org.onap.cps.ncmp.impl.models.RequiredDmiService +import org.onap.cps.utils.JsonObjectMapper +import org.springframework.http.HttpStatus +import org.springframework.http.ResponseEntity +import spock.lang.Specification + +class DmiSubJobRequestHandlerSpec extends Specification { + + def mockDmiRestClient = Mock(DmiRestClient) + def jsonObjectMapper = new JsonObjectMapper(new ObjectMapper()) + def mockDmiProperties = Mock(DmiProperties) + def static NO_AUTH = null + def objectUnderTest = new DmiSubJobRequestHandler(mockDmiRestClient, mockDmiProperties, jsonObjectMapper) + + def 'Send a sub-job request to the DMI Plugin.'() { + given: 'a data job id, metadata and a map of producer keys and write operations to create a request' + def dataJobId = 'some-job-id' + def dataJobMetadata = new DataJobMetadata('', '', '') + def dmiWriteOperation = new DmiWriteOperation('', '', '', null, '', [:]) + def dmiWriteOperationsPerProducerKey = [new ProducerKey('', ''): [dmiWriteOperation]] + def response = new ResponseEntity<>(new SubJobWriteResponse('my-sub-job-id', '', ''), HttpStatus.OK) + when: 'sending request to DMI invoked' + objectUnderTest.sendRequestsToDmi(dataJobId, dataJobMetadata, dmiWriteOperationsPerProducerKey) + then: 'the dmi rest client is called' + 1 * mockDmiRestClient.synchronousPostOperationWithJsonData(RequiredDmiService.DATA, _, _, OperationType.CREATE, NO_AUTH) >> response + and: 'the result contains the expected sub-job write responses' + def result = response.body + assert result.subJobId() == 'my-sub-job-id' + } +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplSpec.groovy deleted file mode 100644 index 4d0af6f49..000000000 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplSpec.groovy +++ /dev/null @@ -1,411 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2021-2024 Nordix Foundation - * Modifications Copyright (C) 2021 Pantheon.tech - * Modifications Copyright (C) 2021-2022 Bell Canada - * Modifications Copyright (C) 2023 TechMahindra Ltd. - * ================================================================================ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * ============LICENSE_END========================================================= - */ - -package org.onap.cps.ncmp.api.impl - -import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME -import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DATASPACE_NAME -import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR -import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.OPERATIONAL -import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_OPERATIONAL -import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_RUNNING -import static org.onap.cps.ncmp.api.impl.operations.OperationType.CREATE -import static org.onap.cps.ncmp.api.impl.operations.OperationType.UPDATE - -import org.onap.cps.ncmp.api.models.DmiPluginRegistrationResponse -import org.onap.cps.ncmp.api.models.CmResourceAddress -import org.onap.cps.ncmp.api.impl.utils.AlternateIdChecker -import com.hazelcast.map.IMap -import org.onap.cps.ncmp.api.NetworkCmProxyCmHandleQueryService -import org.onap.cps.ncmp.api.impl.events.lcm.LcmEventsCmHandleStateHandler -import org.onap.cps.ncmp.api.impl.trustlevel.TrustLevel -import org.onap.cps.ncmp.api.impl.trustlevel.TrustLevelManager -import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle -import org.onap.cps.ncmp.api.impl.inventory.CmHandleQueries -import org.onap.cps.ncmp.api.impl.inventory.CmHandleState -import org.onap.cps.ncmp.api.impl.inventory.CompositeState -import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence -import org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory -import org.onap.cps.ncmp.api.impl.inventory.DataStoreSyncState -import org.onap.cps.ncmp.api.models.DataOperationDefinition -import org.onap.cps.ncmp.api.models.CmHandleQueryApiParameters -import org.onap.cps.ncmp.api.models.CmHandleQueryServiceParameters -import org.onap.cps.ncmp.api.models.ConditionApiProperties -import org.onap.cps.ncmp.api.models.DmiPluginRegistration -import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle -import org.onap.cps.ncmp.api.models.DataOperationRequest -import org.onap.cps.spi.exceptions.CpsException -import org.onap.cps.spi.model.ConditionProperties -import java.util.stream.Collectors -import org.onap.cps.utils.JsonObjectMapper -import com.fasterxml.jackson.databind.ObjectMapper -import org.onap.cps.api.CpsDataService -import org.onap.cps.ncmp.api.impl.operations.DmiDataOperations -import org.onap.cps.spi.FetchDescendantsOption -import org.onap.cps.spi.model.DataNode -import org.springframework.http.HttpStatus -import org.springframework.http.ResponseEntity -import spock.lang.Specification - -class NetworkCmProxyDataServiceImplSpec extends Specification { - - def mockCpsDataService = Mock(CpsDataService) - def spiedJsonObjectMapper = Spy(new JsonObjectMapper(new ObjectMapper())) - def mockDmiDataOperations = Mock(DmiDataOperations) - def nullNetworkCmProxyDataServicePropertyHandler = null - def mockInventoryPersistence = Mock(InventoryPersistence) - def mockCmHandleQueries = Mock(CmHandleQueries) - def mockDmiPluginRegistration = Mock(DmiPluginRegistration) - def mockCpsCmHandlerQueryService = Mock(NetworkCmProxyCmHandleQueryService) - def mockLcmEventsCmHandleStateHandler = Mock(LcmEventsCmHandleStateHandler) - def stubModuleSyncStartedOnCmHandles = Stub(IMap<String, Object>) - def stubTrustLevelPerDmiPlugin = Stub(Map<String, TrustLevel>) - def mockTrustLevelManager = Mock(TrustLevelManager) - def mockAlternateIdChecker = Mock(AlternateIdChecker) - - def NO_TOPIC = null - def NO_REQUEST_ID = null - def NO_AUTH_HEADER = null - def OPTIONS_PARAM = '(a=1,b=2)' - def ncmpServiceCmHandle = new NcmpServiceCmHandle(cmHandleId: 'test-cm-handle-id') - - def objectUnderTest = new NetworkCmProxyDataServiceImpl( - spiedJsonObjectMapper, - mockDmiDataOperations, - nullNetworkCmProxyDataServicePropertyHandler, - mockInventoryPersistence, - mockCmHandleQueries, - mockCpsCmHandlerQueryService, - mockLcmEventsCmHandleStateHandler, - mockCpsDataService, - stubModuleSyncStartedOnCmHandles, - stubTrustLevelPerDmiPlugin, - mockTrustLevelManager, - mockAlternateIdChecker) - - def cmHandleXPath = "/dmi-registry/cm-handles[@id='testCmHandle']" - - def dataNode = [new DataNode(leaves: ['id': 'some-cm-handle', 'dmi-service-name': 'testDmiService'])] - - def 'Write resource data for pass-through running from DMI using POST.'() { - given: 'cpsDataService returns valid datanode' - mockDataNode() - when: 'write resource data is called' - objectUnderTest.writeResourceDataPassThroughRunningForCmHandle('testCmHandle', - 'testResourceId', CREATE, - '{some-json}', 'application/json', null) - then: 'DMI called with correct data' - 1 * mockDmiDataOperations.writeResourceDataPassThroughRunningFromDmi('testCmHandle', 'testResourceId', - CREATE, '{some-json}', 'application/json', null) - >> { new ResponseEntity<>(HttpStatus.CREATED) } - } - - def 'Get resource data for from DMI.'() { - given: 'cpsDataService returns valid data node' - mockDataNode() - and: 'some cm resource address' - def cmResourceAddress = new CmResourceAddress('some datastore','some CM Handle', 'some resource Id') - and: 'get resource data from DMI is called' - mockDmiDataOperations.getResourceDataFromDmi(cmResourceAddress, OPTIONS_PARAM, NO_TOPIC, NO_REQUEST_ID, NO_AUTH_HEADER) >> - new ResponseEntity<>('dmi-response', HttpStatus.OK) - when: 'get resource data operational for the given cm resource address is called' - def response = objectUnderTest.getResourceDataForCmHandle(cmResourceAddress, OPTIONS_PARAM, NO_TOPIC, NO_REQUEST_ID, NO_AUTH_HEADER) - then: 'DMI returns a json response' - assert response == 'dmi-response' - } - - def 'Get resource data for operational (cached) data.'() { - given: 'CPS Data service returns some object(s)' - mockCpsDataService.getDataNodes(OPERATIONAL.datastoreName, 'testCmHandle', 'testResourceId', FetchDescendantsOption.OMIT_DESCENDANTS) >> ['First Object', 'other Object'] - and: 'a cm resource address for the same datastore, cm handle and resource id' - def cmResourceAddress = new CmResourceAddress(OPERATIONAL.datastoreName, 'testCmHandle', 'testResourceId') - when: 'get resource data is called' - def response = objectUnderTest.getResourceDataForCmHandle(cmResourceAddress, FetchDescendantsOption.OMIT_DESCENDANTS) - then: 'get resource data returns teh first object from the data service' - assert response == 'First Object' - } - - def 'Execute (async) data operation for #datastoreName from DMI.'() { - given: 'cpsDataService returns valid data node' - def dataOperationRequest = getDataOperationRequest(datastoreName) - when: 'request resource data for data operation is called' - objectUnderTest.executeDataOperationForCmHandles('some topic', dataOperationRequest, 'requestId', NO_AUTH_HEADER) - then: 'request resource data for data operation returns expected response' - 1 * mockDmiDataOperations.requestResourceDataFromDmi('some topic', dataOperationRequest, 'requestId', NO_AUTH_HEADER) - where: 'the following data stores are used' - datastoreName << [PASSTHROUGH_RUNNING.datastoreName, PASSTHROUGH_OPERATIONAL.datastoreName] - } - - def 'Getting Yang Resources.'() { - when: 'yang resources is called' - objectUnderTest.getYangResourcesModuleReferences('some-cm-handle') - then: 'CPS module services is invoked for the correct dataspace and cm handle' - 1 * mockInventoryPersistence.getYangResourcesModuleReferences('some-cm-handle') - } - - def 'Get a cm handle.'() { - given: 'the system returns a yang modelled cm handle' - def dmiServiceName = 'some service name' - def compositeState = new CompositeState(cmHandleState: CmHandleState.ADVISED, - lockReason: CompositeState.LockReason.builder().lockReasonCategory(LockReasonCategory.MODULE_SYNC_FAILED).details("lock details").build(), - lastUpdateTime: 'some-timestamp', - dataSyncEnabled: false, - dataStores: dataStores()) - def dmiProperties = [new YangModelCmHandle.Property('Book', 'Romance Novel')] - def publicProperties = [new YangModelCmHandle.Property('Public Book', 'Public Romance Novel')] - def moduleSetTag = 'some-module-set-tag' - def alternateId = 'some-alternate-id' - def yangModelCmHandle = new YangModelCmHandle(id: 'some-cm-handle', dmiServiceName: dmiServiceName, - dmiProperties: dmiProperties, publicProperties: publicProperties, compositeState: compositeState, - moduleSetTag: moduleSetTag, alternateId: alternateId) - 1 * mockInventoryPersistence.getYangModelCmHandle('some-cm-handle') >> yangModelCmHandle - when: 'getting cm handle details for a given cm handle id from ncmp service' - def result = objectUnderTest.getNcmpServiceCmHandle('some-cm-handle') - then: 'the result is a ncmpServiceCmHandle' - result.class == NcmpServiceCmHandle.class - and: 'the cm handle contains the cm handle id' - result.cmHandleId == 'some-cm-handle' - and: 'the cm handle contains the alternate id' - result.alternateId == 'some-alternate-id' - and: 'the cm handle contains the module-set-tag' - result.moduleSetTag == 'some-module-set-tag' - and: 'the cm handle contains the DMI Properties' - result.dmiProperties ==[ Book:'Romance Novel' ] - and: 'the cm handle contains the public Properties' - result.publicProperties == [ "Public Book":'Public Romance Novel' ] - and: 'the cm handle contains the cm handle composite state' - result.compositeState == compositeState - } - - def 'Get cm handle public properties'() { - given: 'a yang modelled cm handle' - def dmiProperties = [new YangModelCmHandle.Property('prop', 'some DMI property')] - def publicProperties = [new YangModelCmHandle.Property('public prop', 'some public prop')] - def yangModelCmHandle = new YangModelCmHandle(id:'some-cm-handle', dmiServiceName: 'some service name', dmiProperties: dmiProperties, publicProperties: publicProperties) - and: 'the system returns this yang modelled cm handle' - 1 * mockInventoryPersistence.getYangModelCmHandle('some-cm-handle') >> yangModelCmHandle - when: 'getting cm handle public properties for a given cm handle id from ncmp service' - def result = objectUnderTest.getCmHandlePublicProperties('some-cm-handle') - then: 'the result returns the correct data' - result == [ 'public prop' : 'some public prop' ] - } - - def 'Execute cm handle id search for inventory'() { - given: 'a ConditionApiProperties object' - def conditionProperties = new ConditionProperties() - conditionProperties.conditionName = 'hasAllProperties' - conditionProperties.conditionParameters = [ [ 'some-key' : 'some-value' ] ] - def conditionServiceProps = new CmHandleQueryServiceParameters() - conditionServiceProps.cmHandleQueryParameters = [conditionProperties] as List<ConditionProperties> - and: 'the system returns an set of cmHandle ids' - mockCpsCmHandlerQueryService.queryCmHandleIdsForInventory(*_) >> [ 'cmHandle1', 'cmHandle2' ] - when: 'getting cm handle id set for a given dmi property' - def result = objectUnderTest.executeCmHandleIdSearchForInventory(conditionServiceProps) - then: 'the result returns the correct 2 elements' - assert result.size() == 2 - assert result.contains('cmHandle1') - assert result.contains('cmHandle2') - } - - def 'Get cm handle composite state'() { - given: 'a yang modelled cm handle' - def compositeState = new CompositeState(cmHandleState: CmHandleState.ADVISED, - lockReason: CompositeState.LockReason.builder().lockReasonCategory(LockReasonCategory.MODULE_SYNC_FAILED).details("lock details").build(), - lastUpdateTime: 'some-timestamp', - dataSyncEnabled: false, - dataStores: dataStores()) - def dmiProperties = [new YangModelCmHandle.Property('prop', 'some DMI property')] - def publicProperties = [new YangModelCmHandle.Property('public prop', 'some public prop')] - def yangModelCmHandle = new YangModelCmHandle(id:'some-cm-handle', dmiServiceName: 'some service name', dmiProperties: dmiProperties, publicProperties: publicProperties, compositeState: compositeState) - and: 'the system returns this yang modelled cm handle' - 1 * mockInventoryPersistence.getYangModelCmHandle('some-cm-handle') >> yangModelCmHandle - when: 'getting cm handle composite state for a given cm handle id from ncmp service' - def result = objectUnderTest.getCmHandleCompositeState('some-cm-handle') - then: 'the result returns the correct data' - result == compositeState - } - - def 'Update resource data for pass-through running from dmi using POST #scenario DMI properties.'() { - given: 'cpsDataService returns valid datanode' - mockCpsDataService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode - when: 'get resource data is called' - objectUnderTest.writeResourceDataPassThroughRunningForCmHandle('testCmHandle', - 'testResourceId', UPDATE, - '{some-json}', 'application/json', NO_AUTH_HEADER) - then: 'DMI called with correct data' - 1 * mockDmiDataOperations.writeResourceDataPassThroughRunningFromDmi('testCmHandle', 'testResourceId', - UPDATE, '{some-json}', 'application/json', NO_AUTH_HEADER) - >> { new ResponseEntity<>(HttpStatus.OK) } - } - - def 'Verify modules and create anchor params.'() { - given: 'dmi plugin registration return created cm handles' - def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'service1', dmiModelPlugin: 'service1', - dmiDataPlugin: 'service2') - dmiPluginRegistration.createdCmHandles = [ncmpServiceCmHandle] - mockDmiPluginRegistration.getCreatedCmHandles() >> [ncmpServiceCmHandle] - and: 'no rejected cm handles because of alternate ids' - mockAlternateIdChecker.getIdsOfCmHandlesWithRejectedAlternateId(*_) >> [] - when: 'parse and create cm handle in dmi registration then sync module' - mockDmiPluginRegistration.createdCmHandles = ['test-cm-handle-id'] - objectUnderTest.processCreatedCmHandles(mockDmiPluginRegistration, new DmiPluginRegistrationResponse()) - then: 'system persists the cm handle state' - 1 * mockLcmEventsCmHandleStateHandler.initiateStateAdvised(_) >> { - args -> { - def cmHandleStatePerCmHandle = (args[0] as Collection) - cmHandleStatePerCmHandle.each { - assert it.id == 'test-cm-handle-id' - } - } - } - } - - def 'Execute cm handle id search'() { - given: 'valid CmHandleQueryApiParameters input' - def cmHandleQueryApiParameters = new CmHandleQueryApiParameters() - def conditionApiProperties = new ConditionApiProperties() - conditionApiProperties.conditionName = 'hasAllModules' - conditionApiProperties.conditionParameters = [[moduleName: 'module-name-1']] - cmHandleQueryApiParameters.cmHandleQueryParameters = [conditionApiProperties] - and: 'query cm handle method return with a data node list' - mockCpsCmHandlerQueryService.queryCmHandleIds( - spiedJsonObjectMapper.convertToValueType(cmHandleQueryApiParameters, CmHandleQueryServiceParameters.class)) - >> ['cm-handle-id-1'] - when: 'execute cm handle search is called' - def result = objectUnderTest.executeCmHandleIdSearch(cmHandleQueryApiParameters) - then: 'result is the same collection as returned by the CPS Data Service' - assert result == ['cm-handle-id-1'] - } - - def 'Getting module definitions by module'() { - when: 'get module definitions is performed with module name' - objectUnderTest.getModuleDefinitionsByCmHandleAndModule('some-cm-handle', 'some-module', '2021-08-04') - then: 'ncmp inventory persistence service is invoked once with correct parameters' - 1 * mockInventoryPersistence.getModuleDefinitionsByCmHandleAndModule('some-cm-handle', 'some-module', '2021-08-04') - } - - def 'Getting module definitions by cm handle id'() { - when: 'get module definitions is performed with cm handle id' - objectUnderTest.getModuleDefinitionsByCmHandleId('some-cm-handle') - then: 'ncmp inventory persistence service is invoked once with correct parameter' - 1 * mockInventoryPersistence.getModuleDefinitionsByCmHandleId('some-cm-handle') - } - - def 'Execute cm handle search'() { - given: 'valid CmHandleQueryApiParameters input' - def cmHandleQueryApiParameters = new CmHandleQueryApiParameters() - def conditionApiProperties = new ConditionApiProperties() - conditionApiProperties.conditionName = 'hasAllModules' - conditionApiProperties.conditionParameters = [[moduleName: 'module-name-1']] - cmHandleQueryApiParameters.cmHandleQueryParameters = [conditionApiProperties] - and: 'query cm handle method return with a data node list' - mockCpsCmHandlerQueryService.queryCmHandles( - spiedJsonObjectMapper.convertToValueType(cmHandleQueryApiParameters, CmHandleQueryServiceParameters.class)) - >> [new NcmpServiceCmHandle(cmHandleId: 'cm-handle-id-1')] - when: 'execute cm handle search is called' - def result = objectUnderTest.executeCmHandleSearch(cmHandleQueryApiParameters) - then: 'result is the same collection as returned by the CPS Data Service' - assert result.stream().map(d -> d.cmHandleId).collect(Collectors.toSet()) == ['cm-handle-id-1'] as Set - } - - def 'Set Cm Handle Data Sync Enabled Flag where data sync flag is #scenario'() { - given: 'an existing cm handle composite state' - def compositeState = new CompositeState(cmHandleState: CmHandleState.READY, dataSyncEnabled: initialDataSyncEnabledFlag, - dataStores: CompositeState.DataStores.builder() - .operationalDataStore(CompositeState.Operational.builder() - .dataStoreSyncState(initialDataSyncState) - .build()).build()) - and: 'get cm handle state returns the composite state for the given cm handle id' - mockInventoryPersistence.getCmHandleState('some-cm-handle-id') >> compositeState - when: 'set data sync enabled is called with the data sync enabled flag set to #dataSyncEnabledFlag' - objectUnderTest.setDataSyncEnabled('some-cm-handle-id', dataSyncEnabledFlag) - then: 'the data sync enabled flag is set to #dataSyncEnabled' - compositeState.dataSyncEnabled == dataSyncEnabledFlag - and: 'the data store sync state is set to #expectedDataStoreSyncState' - compositeState.dataStores.operationalDataStore.dataStoreSyncState == expectedDataStoreSyncState - and: 'the cps data service to delete data nodes is invoked the expected number of times' - deleteDataNodeExpectedNumberOfInvocation * mockCpsDataService.deleteDataNode(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, 'some-cm-handle-id', '/netconf-state', _) - and: 'the inventory persistence service to update node leaves is called with the correct values' - saveCmHandleStateExpectedNumberOfInvocations * mockInventoryPersistence.saveCmHandleState('some-cm-handle-id', compositeState) - where: 'the following data sync enabled flag is used' - scenario | dataSyncEnabledFlag | initialDataSyncEnabledFlag | initialDataSyncState || expectedDataStoreSyncState | deleteDataNodeExpectedNumberOfInvocation | saveCmHandleStateExpectedNumberOfInvocations - 'enabled' | true | false | DataStoreSyncState.NONE_REQUESTED || DataStoreSyncState.UNSYNCHRONIZED | 0 | 1 - 'disabled' | false | true | DataStoreSyncState.UNSYNCHRONIZED || DataStoreSyncState.NONE_REQUESTED | 0 | 1 - 'disabled where sync-state is currently SYNCHRONIZED' | false | true | DataStoreSyncState.SYNCHRONIZED || DataStoreSyncState.NONE_REQUESTED | 1 | 1 - 'is set to existing flag state' | true | true | DataStoreSyncState.UNSYNCHRONIZED || DataStoreSyncState.UNSYNCHRONIZED | 0 | 0 - } - - def 'Set cm Handle Data Sync Enabled flag with following cm handle not in ready state exception' () { - given: 'a cm handle composite state' - def compositeState = new CompositeState(cmHandleState: CmHandleState.ADVISED, dataSyncEnabled: false) - and: 'get cm handle state returns the composite state for the given cm handle id' - mockInventoryPersistence.getCmHandleState('some-cm-handle-id') >> compositeState - when: 'set data sync enabled is called with the data sync enabled flag set to true' - objectUnderTest.setDataSyncEnabled('some-cm-handle-id', true) - then: 'the expected exception is thrown' - thrown(CpsException) - and: 'the inventory persistence service to update node leaves is not invoked' - 0 * mockInventoryPersistence.saveCmHandleState(_, _) - } - - def 'Get all cm handle IDs by DMI plugin identifier.' () { - given: 'cm handle queries service returns cm handles' - 1 * mockCmHandleQueries.getCmHandleIdsByDmiPluginIdentifier('some-dmi-plugin-identifier') >> ['cm-handle-1','cm-handle-2'] - when: 'cm handle Ids are requested with dmi plugin identifier' - def result = objectUnderTest.getAllCmHandleIdsByDmiPluginIdentifier('some-dmi-plugin-identifier') - then: 'the result size is correct' - assert result.size() == 2 - and: 'the result returns the correct details' - assert result.containsAll('cm-handle-1','cm-handle-2') - } - - def dataStores() { - CompositeState.DataStores.builder() - .operationalDataStore(CompositeState.Operational.builder() - .dataStoreSyncState(DataStoreSyncState.NONE_REQUESTED) - .lastSyncTime('some-timestamp').build()).build() - } - - def mockDataNode() { - mockCpsDataService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode - } - - def getDataOperationRequest(datastore) { - def dataOperationRequest = new DataOperationRequest() - def dataOperationDefinitions = new ArrayList() - dataOperationDefinitions.add(getDataOperationDefinition(datastore)) - dataOperationRequest.setDataOperationDefinitions(dataOperationDefinitions) - return dataOperationRequest - } - - def getDataOperationDefinition(datastore) { - def dataOperationDefinition = new DataOperationDefinition() - dataOperationDefinition.setOperation("read") - dataOperationDefinition.setOperationId("operational-12") - dataOperationDefinition.setDatastore(datastore) - def targetIds = new ArrayList() - targetIds.add("some-cm-handle") - dataOperationDefinition.setCmHandleIds(targetIds) - return dataOperationDefinition - } -} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/WriteRequestExaminerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/WriteRequestExaminerSpec.groovy new file mode 100644 index 000000000..84eb78b75 --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/WriteRequestExaminerSpec.groovy @@ -0,0 +1,63 @@ + +package org.onap.cps.ncmp.impl.datajobs + +import org.onap.cps.ncmp.api.datajobs.models.DataJobWriteRequest +import org.onap.cps.ncmp.api.datajobs.models.WriteOperation +import org.onap.cps.ncmp.impl.utils.AlternateIdMatcher +import org.onap.cps.spi.model.DataNode +import spock.lang.Specification + +class WriteRequestExaminerSpec extends Specification { + + def mockAlternateIdMatcher = Mock(AlternateIdMatcher) + def objectUnderTest = new WriteRequestExaminer(mockAlternateIdMatcher) + + def setup() { + def ch1 = new DataNode(leaves: [id: 'ch1', 'dmi-service-name': 'dmiA', 'data-producer-identifier': 'p1']) + def ch2 = new DataNode(leaves: [id: 'ch2', 'dmi-service-name': 'dmiA', 'data-producer-identifier': 'p1']) + def ch3 = new DataNode(leaves: [id: 'ch3', 'dmi-service-name': 'dmiA', 'data-producer-identifier': 'p2']) + def ch4 = new DataNode(leaves: [id: 'ch4', 'dmi-service-name': 'dmiB', 'data-producer-identifier': 'p1']) + mockAlternateIdMatcher.getCmHandleDataNodeByLongestMatchingAlternateId('fdn1', '/') >> ch1 + mockAlternateIdMatcher.getCmHandleDataNodeByLongestMatchingAlternateId('fdn2', '/') >> ch2 + mockAlternateIdMatcher.getCmHandleDataNodeByLongestMatchingAlternateId('fdn3', '/') >> ch3 + mockAlternateIdMatcher.getCmHandleDataNodeByLongestMatchingAlternateId('fdn4', '/') >> ch4 + } + + def 'Create a map of dmi write requests per producer key with #scenario.'() { + given: 'a write request with some write operations' + def writeOperations = writeOperationFdns.collect { + new WriteOperation(it, '', '', null) + } + and: 'operations are wrapped in a write request' + def dataJobWriteRequest = new DataJobWriteRequest(writeOperations) + when: 'the DMI write operations are split from the request' + def dmiWriteOperationsPerProducerKey = objectUnderTest.splitDmiWriteOperationsFromRequest('some id', dataJobWriteRequest) + then: 'we get the expected number of keys and values.' + def producerKeysAsStrings = dmiWriteOperationsPerProducerKey.keySet().collect { + it.toString() + } + assert producerKeysAsStrings.size() == expectedKeys.size() + assert expectedKeys.containsAll(producerKeysAsStrings) + where: + scenario | writeOperationFdns || expectedKeys + 'one fdn' | ['fdn1'] || ['dmiA#p1'] + 'a duplicated target' | ['fdn1','fdn1'] || ['dmiA#p1'] + 'two different targets' | ['fdn1','fdn2'] || ['dmiA#p1'] + 'two different targets and different producer keys' | ['fdn1','fdn3'] || ['dmiA#p1', 'dmiA#p2'] + 'two different targets and different DMI services' | ['fdn1','fdn4'] || ['dmiA#p1', 'dmiB#p1'] + 'many targets with different dmi service names and producer keys' | ['fdn1', 'fdn2', 'fdn3', 'fdn4'] || ['dmiA#p1', 'dmiA#p2', 'dmiB#p1'] + } + + def 'Validate the ordering of the created sub jobs.'() { + given: 'a few write operations for the same producer' + def writeOperations = (1..3).collect { + new WriteOperation('fdn1', '', it.toString(), null) + } + and: 'operation is wrapped in a write request' + def dataJobWriteRequest = new DataJobWriteRequest(writeOperations) + when: 'the DMI write operations are split from the request' + def dmiWriteOperations = objectUnderTest.splitDmiWriteOperationsFromRequest('some id', dataJobWriteRequest).values().iterator().next() + then: 'we get the operation ids in the expected order.' + assert dmiWriteOperations.operationId == ['1', '2', '3'] + } +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/client/DmiRestClientSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/client/DmiRestClientSpec.groovy index c8e34b1a5..9798040a6 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/client/DmiRestClientSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/client/DmiRestClientSpec.groovy @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2021-2023 Nordix Foundation + * Copyright (C) 2021-2024 Nordix Foundation * Modifications Copyright (C) 2022 Bell Canada * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,89 +24,125 @@ package org.onap.cps.ncmp.api.impl.client import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.node.ObjectNode -import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration -import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration.DmiProperties; -import org.onap.cps.ncmp.api.impl.exception.HttpClientRequestException +import org.onap.cps.ncmp.api.impl.config.DmiProperties +import org.onap.cps.ncmp.api.impl.exception.DmiClientRequestException +import org.onap.cps.ncmp.api.impl.exception.InvalidDmiResourceUrlException import org.onap.cps.ncmp.utils.TestUtils -import org.spockframework.spring.SpringBean -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.context.SpringBootTest -import org.springframework.http.HttpEntity +import org.onap.cps.utils.JsonObjectMapper import org.springframework.http.HttpHeaders import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity -import org.springframework.test.context.ContextConfiguration -import org.springframework.web.client.HttpServerErrorException -import org.springframework.web.client.RestTemplate +import org.springframework.web.reactive.function.client.WebClient +import org.springframework.web.reactive.function.client.WebClientRequestException +import org.springframework.web.reactive.function.client.WebClientResponseException +import reactor.core.publisher.Mono import spock.lang.Specification -import static org.onap.cps.ncmp.api.impl.operations.OperationType.READ -import static org.onap.cps.ncmp.api.impl.operations.OperationType.PATCH -import static org.onap.cps.ncmp.api.impl.operations.OperationType.CREATE +import static org.onap.cps.ncmp.api.NcmpResponseStatus.DMI_SERVICE_NOT_RESPONDING +import static org.onap.cps.ncmp.api.NcmpResponseStatus.UNABLE_TO_READ_RESOURCE_DATA +import static org.onap.cps.ncmp.api.NcmpResponseStatus.UNKNOWN_ERROR +import static org.onap.cps.ncmp.api.data.models.OperationType.CREATE +import static org.onap.cps.ncmp.api.data.models.OperationType.PATCH +import static org.onap.cps.ncmp.api.data.models.OperationType.READ +import static org.onap.cps.ncmp.impl.models.RequiredDmiService.DATA +import static org.onap.cps.ncmp.impl.models.RequiredDmiService.MODEL -@SpringBootTest -@ContextConfiguration(classes = [DmiProperties, DmiRestClient, ObjectMapper]) class DmiRestClientSpec extends Specification { static final NO_AUTH_HEADER = null - static final BASIC_AUTH_HEADER = 'Basic c29tZS11c2VyOnNvbWUtcGFzc3dvcmQ=' + static final BASIC_AUTH_HEADER = 'Basic c29tZSB1c2VyOnNvbWUgcGFzc3dvcmQ=' static final BEARER_AUTH_HEADER = 'Bearer my-bearer-token' - @SpringBean - RestTemplate mockRestTemplate = Mock(RestTemplate) + def mockDataServicesWebClient = Mock(WebClient) + def mockModelServicesWebClient = Mock(WebClient) + def mockHealthChecksWebClient = Mock(WebClient) - @Autowired - NcmpConfiguration.DmiProperties dmiProperties + def mockRequestBody = Mock(WebClient.RequestBodyUriSpec) + def mockResponse = Mock(WebClient.ResponseSpec) - @Autowired - DmiRestClient objectUnderTest + def mockDmiProperties = Mock(DmiProperties) - @Autowired - ObjectMapper objectMapper + JsonObjectMapper jsonObjectMapper = new JsonObjectMapper(new ObjectMapper()) - def responseFromRestTemplate = Mock(ResponseEntity) + DmiRestClient objectUnderTest = new DmiRestClient(mockDmiProperties, jsonObjectMapper, mockDataServicesWebClient, mockModelServicesWebClient, mockHealthChecksWebClient) - def 'DMI POST operation with JSON.'() { - given: 'the rest template returns a valid response entity for the expected parameters' - mockRestTemplate.postForEntity('my url', _ as HttpEntity, Object.class) >> responseFromRestTemplate - when: 'POST operation is invoked' - def result = objectUnderTest.postOperationWithJsonData('my url', 'some json', READ, null) + def setup() { + mockRequestBody.uri(_) >> mockRequestBody + mockRequestBody.headers(_) >> mockRequestBody + mockRequestBody.body(_) >> mockRequestBody + mockRequestBody.retrieve() >> mockResponse + } + + def 'DMI POST Operation with JSON for DMI Data Service '() { + given: 'the Data web client returns a valid response entity for the expected parameters' + mockDataServicesWebClient.post() >> mockRequestBody + mockResponse.toEntity(Object.class) >> Mono.just(new ResponseEntity<>('from Data service', HttpStatus.I_AM_A_TEAPOT)) + when: 'POST operation is invoked fro Data Service' + def response = objectUnderTest.synchronousPostOperationWithJsonData(DATA, '/my/url', 'some json', READ, NO_AUTH_HEADER) then: 'the output of the method is equal to the output from the test template' - result == responseFromRestTemplate + assert response.statusCode == HttpStatus.I_AM_A_TEAPOT + assert response.body == 'from Data service' } - def 'Failing DMI POST operation.'() { - given: 'the rest template returns a valid response entity' - def serverResponse = 'server response'.getBytes() - def httpServerErrorException = new HttpServerErrorException(HttpStatus.FORBIDDEN, 'status text', serverResponse, null) - mockRestTemplate.postForEntity(*_) >> { throw httpServerErrorException } - when: 'POST operation is invoked' - def result = objectUnderTest.postOperationWithJsonData('some url', 'some json', operation, null) - then: 'a Http Client Exception is thrown' - def thrown = thrown(HttpClientRequestException) + def 'DMI POST Operation with JSON for DMI Model Service '() { + given: 'the Model web client returns a valid response entity for the expected parameters' + mockModelServicesWebClient.post() >> mockRequestBody + mockResponse.toEntity(Object.class) >> Mono.just(new ResponseEntity<>('from Model service', HttpStatus.I_AM_A_TEAPOT)) + when: 'POST operation is invoked for Model Service' + def response = objectUnderTest.synchronousPostOperationWithJsonData(MODEL, '/my/url', 'some json', READ, NO_AUTH_HEADER) + then: 'the output of the method is equal to the output from the test template' + assert response.statusCode == HttpStatus.I_AM_A_TEAPOT + assert response.body == 'from Model service' + } + + def 'Failing DMI POST operation due to invalid dmi resource url.'() { + when: 'POST operation is invoked with invalid dmi resource url' + objectUnderTest.synchronousPostOperationWithJsonData(DATA, '/invalid dmi url', null, null, NO_AUTH_HEADER) + then: 'invalid dmi resource url exception is thrown' + def thrown = thrown(InvalidDmiResourceUrlException) and: 'the exception has the relevant details from the error response' - assert thrown.httpStatus == 403 - assert thrown.message == "Unable to ${operation} resource data." - assert thrown.details == 'server response' - where: 'the following operation is executed' + thrown.httpStatus == 400 + thrown.message == 'Invalid dmi resource url: /invalid dmi url' + where: 'the following operations are executed' operation << [CREATE, READ, PATCH] } + def 'Dmi service sends client error response when #scenario'() { + given: 'the web client unable to return response entity but error' + mockDataServicesWebClient.post() >> mockRequestBody + mockResponse.toEntity(Object.class) >> Mono.error(exceptionType) + when: 'POST operation is invoked' + objectUnderTest.synchronousPostOperationWithJsonData(DATA, '/my/url', 'some json', READ, NO_AUTH_HEADER) + then: 'a http client exception is thrown' + def thrown = thrown(DmiClientRequestException) + and: 'the exception has the relevant details from the error response' + assert thrown.ncmpResponseStatus == expectedNcmpResponseStatusCode + assert thrown.httpStatusCode == httpStatusCode + where: 'the following errors occur' + scenario | httpStatusCode | exceptionType || expectedNcmpResponseStatusCode + 'dmi service unavailable' | 503 | new WebClientRequestException(new RuntimeException('some-error'), null, null, new HttpHeaders()) || DMI_SERVICE_NOT_RESPONDING + 'dmi request timeout' | 408 | new WebClientResponseException('message', httpStatusCode, 'statusText', null, null, null) || DMI_SERVICE_NOT_RESPONDING + 'dmi server error' | 500 | new WebClientResponseException('message', httpStatusCode, 'statusText', null, null, null) || UNABLE_TO_READ_RESOURCE_DATA + 'unknown error' | 500 | new Throwable('message') || UNKNOWN_ERROR + } + def 'Dmi trust level is determined by spring boot health status'() { given: 'a health check response' def dmiPluginHealthCheckResponseJsonData = TestUtils.getResourceFileContent('dmiPluginHealthCheckResponse.json') - def jsonNode = objectMapper.readValue(dmiPluginHealthCheckResponseJsonData, JsonNode.class) + def jsonNode = jsonObjectMapper.convertJsonString(dmiPluginHealthCheckResponseJsonData, JsonNode.class) ((ObjectNode) jsonNode).put('status', 'my status') - mockRestTemplate.getForObject(*_) >> {jsonNode} + mockHealthChecksWebClient.get() >> mockRequestBody + mockResponse.bodyToMono(JsonNode.class) >> Mono.just(jsonNode) when: 'get trust level of the dmi plugin' - def result = objectUnderTest.getDmiHealthStatus('some url') - then: 'the status value from the json is return' + def result = objectUnderTest.getDmiHealthStatus('some/url') + then: 'the status value from the json is returned' assert result == 'my status' } def 'Failing to get dmi plugin health status #scenario'() { given: 'rest template with #scenario' - mockRestTemplate.getForObject(*_) >> healthStatusResponse + mockHealthChecksWebClient.get() >> mockRequestBody + mockResponse.bodyToMono(_) >> healthStatusResponse when: 'attempt to get health status of the dmi plugin' def result = objectUnderTest.getDmiHealthStatus('some url') then: 'result will be empty' @@ -114,15 +150,18 @@ class DmiRestClientSpec extends Specification { where: 'the following responses are used' scenario | healthStatusResponse 'null' | null - 'exception' | {throw new Exception()} + 'exception' | { throw new Exception() } } def 'DMI auth header #scenario'() { when: 'Specific dmi properties are provided' - dmiProperties.dmiBasicAuthEnabled = authEnabled + mockDmiProperties.dmiBasicAuthEnabled >> authEnabled + mockDmiProperties.authUsername >> 'some user' + mockDmiProperties.authPassword >> 'some password' then: 'http headers to conditionally have Authorization header' - def authHeaderValues = objectUnderTest.configureHttpHeaders(new HttpHeaders(), ncmpAuthHeader).getOrEmpty('Authorization') - def outputAuthHeader = (authHeaderValues == null ? null : authHeaderValues[0]) + def httpHeaders = new HttpHeaders() + objectUnderTest.configureHttpHeaders(httpHeaders, ncmpAuthHeader) + def outputAuthHeader = (httpHeaders.Authorization == null ? null : httpHeaders.Authorization[0]) assert outputAuthHeader == expectedAuthHeader where: 'the following configurations are used' scenario | authEnabled | ncmpAuthHeader || expectedAuthHeader @@ -132,5 +171,4 @@ class DmiRestClientSpec extends Specification { 'DMI basic auth disabled, with NCMP bearer token' | false | BEARER_AUTH_HEADER || BEARER_AUTH_HEADER 'DMI basic auth disabled, with NCMP basic auth' | false | BASIC_AUTH_HEADER || NO_AUTH_HEADER } - } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/DmiPropertiesSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/DmiPropertiesSpec.groovy new file mode 100644 index 000000000..c763c522c --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/DmiPropertiesSpec.groovy @@ -0,0 +1,37 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2024 Nordix Foundation. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.api.impl.config + +import spock.lang.Specification + +class DmiPropertiesSpec extends Specification { + + def objectUnderTest = new DmiProperties() + + def 'Geting dmi base path.'() { + given: 'base path of #dmiBasePath' + objectUnderTest.dmiBasePath = dmiBasePath + expect: 'Preceding and trailing slash wil be removed' + assert objectUnderTest.getDmiBasePath() == 'test' + where: 'the following dmi base paths are used' + dmiBasePath << [ 'test' , '/test', 'test/', '/test/' ] + } +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/DmiWebClientConfigurationSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/DmiWebClientConfigurationSpec.groovy new file mode 100644 index 000000000..05ecaa11b --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/DmiWebClientConfigurationSpec.groovy @@ -0,0 +1,68 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2024 Nordix Foundation. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.api.impl.config + +import org.springframework.boot.context.properties.EnableConfigurationProperties +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.test.context.ContextConfiguration +import org.springframework.test.context.TestPropertySource +import org.springframework.web.reactive.function.client.WebClient +import spock.lang.Specification + +@SpringBootTest +@ContextConfiguration(classes = [HttpClientConfiguration]) +@TestPropertySource(properties = ['ncmp.dmi.httpclient.data-services.connectionTimeoutInSeconds=1', 'ncmp.dmi.httpclient.model-services.maximumInMemorySizeInMegabytes=1']) +@EnableConfigurationProperties +class DmiWebClientConfigurationSpec extends Specification { + + def httpClientConfiguration = Spy(HttpClientConfiguration.class) + + def objectUnderTest = new DmiWebClientConfiguration(httpClientConfiguration) + + def 'Web Client Configuration construction.'() { + expect: 'the system can create an instance' + new DmiWebClientConfiguration(httpClientConfiguration) != null + } + + def 'Creating a web client instance data service.'() { + given: 'Web client configuration is invoked' + def dataServicesWebClient = objectUnderTest.dataServicesWebClient() + expect: 'the system can create an instance for data service' + assert dataServicesWebClient != null + assert dataServicesWebClient instanceof WebClient + } + + def 'Creating a web client instance model service.'() { + given: 'Web client configuration invoked' + def modelServicesWebClient = objectUnderTest.modelServicesWebClient() + expect: 'the system can create an instance for model service' + assert modelServicesWebClient != null + assert modelServicesWebClient instanceof WebClient + } + + def 'Creating a web client instance health service.'() { + given: 'Web client configuration invoked' + def healthChecksWebClient = objectUnderTest.healthChecksWebClient() + expect: 'the system can create an instance for health service' + assert healthChecksWebClient != null + assert healthChecksWebClient instanceof WebClient + } +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/HttpClientConfigurationSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/HttpClientConfigurationSpec.groovy index 2c76b5bb4..228f41277 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/HttpClientConfigurationSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/HttpClientConfigurationSpec.groovy @@ -1,6 +1,6 @@ /*- * ============LICENSE_START======================================================= - * Copyright (C) 2023 Nordix Foundation. + * Copyright (C) 2023-2024 Nordix Foundation. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,32 +17,58 @@ * SPDX-License-Identifier: Apache-2.0 * ============LICENSE_END========================================================= */ + package org.onap.cps.ncmp.api.impl.config -import java.time.Duration import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.context.properties.EnableConfigurationProperties import org.springframework.boot.test.context.SpringBootTest import org.springframework.test.context.ContextConfiguration import org.springframework.test.context.TestPropertySource -import org.springframework.test.context.support.AnnotationConfigContextLoader import spock.lang.Specification @SpringBootTest @ContextConfiguration(classes = [HttpClientConfiguration]) @EnableConfigurationProperties(HttpClientConfiguration.class) -@TestPropertySource(properties = ["ncmp.dmi.httpclient.connectionTimeoutInSeconds=1", "ncmp.dmi.httpclient.maximumConnectionsTotal=200"]) +@TestPropertySource(properties = ["ncmp.dmi.httpclient.data-services.readTimeoutInSeconds=789", "ncmp.dmi.httpclient.model-services.maximumConnectionsTotal=111"]) class HttpClientConfigurationSpec extends Specification { @Autowired private HttpClientConfiguration httpClientConfiguration - def 'Test HttpClientConfiguration properties with custom and default values'() { - expect: 'custom property values' - assert httpClientConfiguration.getConnectionTimeoutInSeconds() == Duration.ofSeconds(1) - assert httpClientConfiguration.getMaximumConnectionsTotal() == 200 - and: 'default property values' - assert httpClientConfiguration.getMaximumConnectionsPerRoute() == 50 - assert httpClientConfiguration.getIdleConnectionEvictionThresholdInSeconds() == Duration.ofSeconds(5) + def 'Test http client configuration properties of data with custom and default values'() { + expect: 'properties are populated correctly for data' + with(httpClientConfiguration.dataServices) { + assert connectionTimeoutInSeconds == 123 + assert readTimeoutInSeconds == 789 + assert writeTimeoutInSeconds == 30 + assert maximumConnectionsTotal == 100 + assert pendingAcquireMaxCount == 22 + assert maximumInMemorySizeInMegabytes == 7 + } + } + + def 'Test http client configuration properties of model with custom and default values'() { + expect: 'properties are populated correctly for model' + with(httpClientConfiguration.modelServices) { + assert connectionTimeoutInSeconds == 456 + assert readTimeoutInSeconds == 30 + assert writeTimeoutInSeconds == 30 + assert maximumConnectionsTotal == 111 + assert pendingAcquireMaxCount == 44 + assert maximumInMemorySizeInMegabytes == 8 + } + } + + def 'Test http client configuration properties of health with default values'() { + expect: 'properties are populated correctly for health' + with(httpClientConfiguration.healthCheckServices) { + assert connectionTimeoutInSeconds == 30 + assert readTimeoutInSeconds == 30 + assert writeTimeoutInSeconds == 30 + assert maximumConnectionsTotal == 10 + assert pendingAcquireMaxCount == 5 + assert maximumInMemorySizeInMegabytes == 1 + } } } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/NcmpConfigurationSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/NcmpConfigurationSpec.groovy deleted file mode 100644 index 74e342405..000000000 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/NcmpConfigurationSpec.groovy +++ /dev/null @@ -1,70 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2021-2023 Nordix Foundation - * ================================================================================ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * ============LICENSE_END========================================================= - */ -package org.onap.cps.ncmp.api.impl.config - -import org.apache.hc.client5.http.impl.classic.CloseableHttpClient -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.context.SpringBootTest -import org.springframework.boot.web.client.RestTemplateBuilder -import org.springframework.http.MediaType -import org.springframework.http.client.HttpComponentsClientHttpRequestFactory -import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter -import org.springframework.test.context.ContextConfiguration -import org.springframework.web.client.RestTemplate -import spock.lang.Specification - -@SpringBootTest -@ContextConfiguration(classes = [NcmpConfiguration.DmiProperties, HttpClientConfiguration]) -class NcmpConfigurationSpec extends Specification{ - - @Autowired - NcmpConfiguration.DmiProperties dmiProperties - - @Autowired - HttpClientConfiguration httpClientConfiguration - - def mockRestTemplateBuilder = new RestTemplateBuilder() - - def 'NcmpConfiguration Construction.'() { - expect: 'the system can create an instance' - new NcmpConfiguration() != null - } - - def 'DMI Properties.'() { - expect: 'properties are set to values in test configuration yaml file' - dmiProperties.authUsername == 'some-user' - dmiProperties.authPassword == 'some-password' - } - - def 'Rest Template creation with CloseableHttpClient and MappingJackson2HttpMessageConverter.'() { - when: 'a rest template is created' - def result = NcmpConfiguration.restTemplate(mockRestTemplateBuilder, httpClientConfiguration) - then: 'the rest template is returned' - assert result instanceof RestTemplate - and: 'the rest template is created with httpclient5' - assert result.getRequestFactory() instanceof HttpComponentsClientHttpRequestFactory - assert ((HttpComponentsClientHttpRequestFactory) result.getRequestFactory()).getHttpClient() instanceof CloseableHttpClient; - and: 'a jackson media converter has been added' - def lastMessageConverter = result.getMessageConverters().get(result.getMessageConverters().size()-1) - lastMessageConverter instanceof MappingJackson2HttpMessageConverter - and: 'the jackson media converters supports the expected media types' - lastMessageConverter.getSupportedMediaTypes() == [MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN]; - } -} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/OpenTelemetryConfigSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/OpenTelemetryConfigSpec.groovy new file mode 100644 index 000000000..07395cf5b --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/OpenTelemetryConfigSpec.groovy @@ -0,0 +1,81 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2024 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.api.impl.config + +import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter +import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter +import io.opentelemetry.sdk.extension.trace.jaeger.sampler.JaegerRemoteSampler +import org.spockframework.spring.SpringBean +import org.springframework.boot.actuate.autoconfigure.observation.ObservationRegistryCustomizer +import spock.lang.Shared +import spock.lang.Specification + +class OpenTelemetryConfigSpec extends Specification{ + + @Shared + @SpringBean + OpenTelemetryConfig openTelemetryConfig = new OpenTelemetryConfig() + + def setupSpec() { + openTelemetryConfig.tracingExporterEndpointUrl="http://tracingExporterEndpointUrl" + openTelemetryConfig.jaegerRemoteSamplerUrl="http://jaegerremotesamplerurl" + openTelemetryConfig.serviceId ="cps-application" + } + + def 'OpenTelemetryConfig Construction.'() { + expect: 'the system can create an instance' + new OpenTelemetryConfig() != null + } + + def 'OTLP Exporter creation with Grpc protocol'(){ + when: 'an OTLP exporter is created' + def result = openTelemetryConfig.createOtlpExporterGrpc() + then: 'an OTLP Exporter is created' + assert result instanceof OtlpGrpcSpanExporter + } + + def 'OTLP Exporter creation with HTTP protocol'(){ + when: 'an OTLP exporter is created' + def result = openTelemetryConfig.createOtlpExporterHttp() + then: 'an OTLP Exporter is created' + assert result instanceof OtlpHttpSpanExporter + and: + assert result.builder.endpoint=="http://tracingExporterEndpointUrl" + } + + def 'Jaeger Remote Sampler Creation'(){ + when: 'an OTLP exporter is created' + def result = openTelemetryConfig.createJaegerRemoteSampler() + then: 'an OTLP Exporter is created' + assert result instanceof JaegerRemoteSampler + and: + assert result.delegate.type=="remoteSampling" + and: + assert result.delegate.url.toString().startsWith("http://jaegerremotesamplerurl") + } + + def 'Skipping Acutator endpoints'(){ + when: 'an OTLP exporter is created' + def result = openTelemetryConfig.skipActuatorEndpointsFromObservation() + then: 'an OTLP Exporter is created' + assert result instanceof ObservationRegistryCustomizer + } +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/embeddedcache/CmNotificationSubscriptionCacheConfigSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/embeddedcache/CmNotificationSubscriptionCacheConfigSpec.groovy index e65011f71..adb1dfda2 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/embeddedcache/CmNotificationSubscriptionCacheConfigSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/embeddedcache/CmNotificationSubscriptionCacheConfigSpec.groovy @@ -22,10 +22,10 @@ package org.onap.cps.ncmp.api.impl.config.embeddedcache import com.hazelcast.core.Hazelcast import com.hazelcast.map.IMap +import org.onap.cps.ncmp.api.data.models.DatastoreType import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.CmNotificationSubscriptionStatus import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.DmiCmNotificationSubscriptionDetails import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.DmiCmNotificationSubscriptionPredicate -import org.onap.cps.ncmp.api.impl.operations.DatastoreType import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.context.SpringBootTest import spock.lang.Specification diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/kafka/KafkaConfigSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/kafka/KafkaConfigSpec.groovy index 16f27d081..4d3fd6616 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/kafka/KafkaConfigSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/kafka/KafkaConfigSpec.groovy @@ -18,7 +18,7 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.config.kafka; +package org.onap.cps.ncmp.api.impl.config.kafka import io.cloudevents.CloudEvent import io.cloudevents.kafka.CloudEventDeserializer @@ -31,12 +31,14 @@ import org.springframework.boot.test.context.SpringBootTest import org.springframework.kafka.core.KafkaTemplate import org.springframework.kafka.support.serializer.JsonDeserializer import org.springframework.kafka.support.serializer.JsonSerializer +import org.springframework.test.context.TestPropertySource import spock.lang.Shared import spock.lang.Specification @SpringBootTest(classes = [KafkaProperties, KafkaConfig]) @EnableSharedInjection @EnableConfigurationProperties +@TestPropertySource(properties = ["cps.tracing.enabled=true"]) class KafkaConfigSpec extends Specification { @Shared diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/avc/AvcEventConsumerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/avc/AvcEventConsumerSpec.groovy index a90fd9405..0f5d4fe5a 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/avc/AvcEventConsumerSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/avc/AvcEventConsumerSpec.groovy @@ -37,9 +37,10 @@ import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.context.SpringBootTest import org.springframework.test.annotation.DirtiesContext import org.testcontainers.spock.Testcontainers + import java.time.Duration -import static org.onap.cps.ncmp.api.impl.events.mapper.CloudEventMapper.toTargetEvent +import static org.onap.cps.ncmp.utils.events.CloudEventMapper.toTargetEvent @SpringBootTest(classes = [EventsPublisher, AvcEventConsumer, ObjectMapper, JsonObjectMapper]) @Testcontainers @@ -91,4 +92,4 @@ class AvcEventConsumerSpec extends MessagingBaseSpec { assert testEventSent == convertedAvcEvent } -}
\ No newline at end of file +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionDeltaSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionDeltaSpec.groovy index e50652689..89ccc7e7d 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionDeltaSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionDeltaSpec.groovy @@ -19,9 +19,9 @@ */ package org.onap.cps.ncmp.api.impl.events.cmsubscription +import org.onap.cps.ncmp.api.data.models.DatastoreType import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.DmiCmNotificationSubscriptionPredicate import org.onap.cps.ncmp.api.impl.events.cmsubscription.service.CmNotificationSubscriptionPersistenceService -import org.onap.cps.ncmp.api.impl.operations.DatastoreType import spock.lang.Specification class CmNotificationSubscriptionDeltaSpec extends Specification { @@ -46,4 +46,15 @@ class CmNotificationSubscriptionDeltaSpec extends Specification { } + def 'Find Delta of given list of predicates when it is an ongoing Cm Subscription'() { + given: 'A list of predicates' + def predicateList = [new DmiCmNotificationSubscriptionPredicate(['ch-1'].toSet(), DatastoreType.PASSTHROUGH_OPERATIONAL, ['a/1/'].toSet())] + and: 'its already present' + mockCmNotificationSubscriptionPersistenceService.isOngoingCmNotificationSubscription(DatastoreType.PASSTHROUGH_OPERATIONAL, 'ch-1', 'a/1/') >>> true + when: 'getDelta is called' + def result = objectUnderTest.getDelta(predicateList) + then: 'verify correct delta is returned' + assert result.size() == 0 + } + } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionDmiInEventProducerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionDmiInEventProducerSpec.groovy index cfb28a0ad..253763b13 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionDmiInEventProducerSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionDmiInEventProducerSpec.groovy @@ -24,10 +24,10 @@ import com.fasterxml.jackson.databind.ObjectMapper import io.cloudevents.CloudEvent import org.onap.cps.events.EventsPublisher import org.onap.cps.ncmp.api.impl.events.cmsubscription.producer.CmNotificationSubscriptionDmiInEventProducer -import org.onap.cps.ncmp.api.impl.events.mapper.CloudEventMapper +import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.ncmp_to_dmi.CmHandle import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.ncmp_to_dmi.CmNotificationSubscriptionDmiInEvent -import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.ncmp_to_dmi.Cmhandle import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.ncmp_to_dmi.Data +import org.onap.cps.ncmp.utils.events.CloudEventMapper import org.onap.cps.utils.JsonObjectMapper import spock.lang.Specification @@ -43,7 +43,7 @@ class CmNotificationSubscriptionDmiInEventProducerSpec extends Specification { def subscriptionId = 'test-subscription-id' def dmiPluginName = 'test-dmiplugin' def eventType = 'subscriptionCreateRequest' - def cmNotificationSubscriptionDmiInEvent = new CmNotificationSubscriptionDmiInEvent(data: new Data(cmhandles: [new Cmhandle(cmhandleId: 'test-1', privateProperties: [:])])) + def cmNotificationSubscriptionDmiInEvent = new CmNotificationSubscriptionDmiInEvent(data: new Data(cmHandles: [new CmHandle(cmhandleId: 'test-1', privateProperties: [:])])) and: 'also we have target topic for dmiPlugin' objectUnderTest.cmNotificationSubscriptionDmiInEventTopic = 'dmiplugin-test-topic' when: 'the event is published' diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionDmiOutEventConsumerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionDmiOutEventConsumerSpec.groovy index 488879db7..9b0a48d93 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionDmiOutEventConsumerSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionDmiOutEventConsumerSpec.groovy @@ -36,7 +36,6 @@ import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.dmi_to_ncm import org.onap.cps.ncmp.utils.TestUtils import org.onap.cps.utils.JsonObjectMapper import org.slf4j.LoggerFactory -import org.spockframework.spring.SpringBean import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.context.SpringBootTest @@ -108,9 +107,9 @@ class CmNotificationSubscriptionDmiOutEventConsumerSpec extends MessagingBaseSpe and: 'correct number of calls to publish the ncmp out event to client' 1 * mockCmNotificationSubscriptionEventsHandler.publishCmNotificationSubscriptionNcmpOutEvent('sub-1', 'subscriptionCreateResponse', _, false) where: 'the following parameters are used' - scenario | subscriptionStatus | statusCode || expectedCacheCalls | expectedPersistenceCalls - 'Accepted Status' | CmNotificationSubscriptionStatus.ACCEPTED | '1' || 1 | 1 - 'Rejected Status' | CmNotificationSubscriptionStatus.REJECTED | '2' || 1 | 0 + scenario | subscriptionStatus | statusCode || expectedCacheCalls | expectedPersistenceCalls + 'Accepted Status' | CmNotificationSubscriptionStatus.ACCEPTED | '1' || 1 | 1 + 'Rejected Status' | CmNotificationSubscriptionStatus.REJECTED | '104' || 1 | 0 } def getLoggingEvent() { diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionNcmpInEventConsumerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionNcmpInEventConsumerSpec.groovy index 9c84c51b2..01a92c02f 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionNcmpInEventConsumerSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionNcmpInEventConsumerSpec.groovy @@ -72,19 +72,40 @@ class CmNotificationSubscriptionNcmpInEventConsumerSpec extends MessagingBaseSpe .withSource(URI.create('some-resource')) .withExtension('correlationid', 'test-cmhandle1').build() def consumerRecord = new ConsumerRecord<String, CloudEvent>('topic-name', 0, 0, 'event-key', testCloudEventSent) - and: 'notifications are enabled' - objectUnderTest.notificationFeatureEnabled = true when: 'the valid event is consumed' objectUnderTest.consumeSubscriptionEvent(consumerRecord) then: 'an event is logged with level INFO' def loggingEvent = getLoggingEvent() assert loggingEvent.level == Level.INFO and: 'the log indicates the task completed successfully' - assert loggingEvent.formattedMessage == 'Subscription for source some-resource with subscription id test-id ...' + assert loggingEvent.formattedMessage == 'Subscription create request for source some-resource with subscription id test-id ...' and: 'the subscription handler service is called once' - 1 * mockCmNotificationSubscriptionHandlerService.processSubscriptionCreateRequest(_) + 1 * mockCmNotificationSubscriptionHandlerService.processSubscriptionCreateRequest('test-id',_) } + def 'Consume valid CmNotificationSubscriptionNcmpInEvent delete message'() { + given: 'a cmNotificationSubscription event' + def jsonData = TestUtils.getResourceFileContent('cmSubscription/cmNotificationSubscriptionNcmpInEvent.json') + def testEventSent = jsonObjectMapper.convertJsonString(jsonData, CmNotificationSubscriptionNcmpInEvent.class) + def testCloudEventSent = CloudEventBuilder.v1() + .withData(objectMapper.writeValueAsBytes(testEventSent)) + .withId('sub-id') + .withType('subscriptionDeleteRequest') + .withSource(URI.create('some-resource')) + .withExtension('correlationid', 'test-cmhandle1').build() + def consumerRecord = new ConsumerRecord<String, CloudEvent>('topic-name', 0, 0, 'event-key', testCloudEventSent) + when: 'the valid event is consumed' + objectUnderTest.consumeSubscriptionEvent(consumerRecord) + then: 'an event is logged with level INFO' + def loggingEvent = getLoggingEvent() + assert loggingEvent.level == Level.INFO + and: 'the log indicates the task completed successfully' + assert loggingEvent.formattedMessage == 'Subscription delete request for source some-resource with subscription id test-id ...' + and: 'the subscription handler service is called once' + 1 * mockCmNotificationSubscriptionHandlerService.processSubscriptionDeleteRequest('test-id',_) + } + + def getLoggingEvent() { return logger.list[1] } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionNcmpOutEventProducerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionNcmpOutEventProducerSpec.groovy index 77bbe7ebc..1fb5837eb 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionNcmpOutEventProducerSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionNcmpOutEventProducerSpec.groovy @@ -4,9 +4,9 @@ import com.fasterxml.jackson.databind.ObjectMapper import io.cloudevents.CloudEvent import org.onap.cps.events.EventsPublisher import org.onap.cps.ncmp.api.impl.events.cmsubscription.producer.CmNotificationSubscriptionNcmpOutEventProducer -import org.onap.cps.ncmp.api.impl.events.mapper.CloudEventMapper import org.onap.cps.ncmp.events.cmsubscription_merge1_0_0.ncmp_to_client.CmNotificationSubscriptionNcmpOutEvent import org.onap.cps.ncmp.events.cmsubscription_merge1_0_0.ncmp_to_client.Data +import org.onap.cps.ncmp.utils.events.CloudEventMapper import org.onap.cps.utils.JsonObjectMapper import spock.lang.Specification diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/DmiCmNotificationSubscriptionCacheHandlerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/DmiCmNotificationSubscriptionCacheHandlerSpec.groovy index 43568be50..7d4bd73aa 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/DmiCmNotificationSubscriptionCacheHandlerSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/DmiCmNotificationSubscriptionCacheHandlerSpec.groovy @@ -27,17 +27,17 @@ import org.apache.kafka.clients.consumer.ConsumerRecord import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.CmNotificationSubscriptionStatus import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.DmiCmNotificationSubscriptionDetails import org.onap.cps.ncmp.api.impl.events.cmsubscription.service.CmNotificationSubscriptionPersistenceService -import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence -import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle import org.onap.cps.ncmp.api.kafka.MessagingBaseSpec import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.client_to_ncmp.CmNotificationSubscriptionNcmpInEvent +import org.onap.cps.ncmp.impl.inventory.InventoryPersistence +import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle import org.onap.cps.ncmp.utils.TestUtils import org.onap.cps.utils.JsonObjectMapper import org.spockframework.spring.SpringBean import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.context.SpringBootTest -import static org.onap.cps.ncmp.api.impl.events.mapper.CloudEventMapper.toTargetEvent +import static org.onap.cps.ncmp.utils.events.CloudEventMapper.toTargetEvent @SpringBootTest(classes = [ObjectMapper, JsonObjectMapper]) class DmiCmNotificationSubscriptionCacheHandlerSpec extends MessagingBaseSpec { @@ -133,10 +133,10 @@ class DmiCmNotificationSubscriptionCacheHandlerSpec extends MessagingBaseSpec { assert resultMapForDmi2.dmiCmNotificationSubscriptionPredicates[1].targetCmHandleIds == ['ch4'].toSet() and: 'the list of xpath for each is correct' assert resultMapForDmi1.dmiCmNotificationSubscriptionPredicates[0].xpaths - && resultMapForDmi2.dmiCmNotificationSubscriptionPredicates[0].xpaths == ['/x1/y1','x2/y2'].toSet() + && resultMapForDmi2.dmiCmNotificationSubscriptionPredicates[0].xpaths == ['/x1/y1','x2/y2'].toSet() assert resultMapForDmi1.dmiCmNotificationSubscriptionPredicates[1].xpaths - && resultMapForDmi2.dmiCmNotificationSubscriptionPredicates[1].xpaths == ['/x3/y3','x4/y4'].toSet() + && resultMapForDmi2.dmiCmNotificationSubscriptionPredicates[1].xpaths == ['/x3/y3','x4/y4'].toSet() } def 'Get map for cm handle IDs by DMI service name'() { @@ -164,7 +164,7 @@ class DmiCmNotificationSubscriptionCacheHandlerSpec extends MessagingBaseSpec { } def 'Persist Cache into database per dmi'() { - given: 'populate cache' + given: 'populated cache' def predicates = cmNotificationSubscriptionNcmpInEvent.getData().getPredicates() def subscriptionId = cmNotificationSubscriptionNcmpInEvent.getData().getSubscriptionId() objectUnderTest.add(subscriptionId, predicates) @@ -174,15 +174,26 @@ class DmiCmNotificationSubscriptionCacheHandlerSpec extends MessagingBaseSpec { 4 * mockCmNotificationSubscriptionPersistenceService.addCmNotificationSubscription(_,_,_,subscriptionId) } + def 'Remove subscription from database per dmi'() { + given: 'populated cache' + def predicates = cmNotificationSubscriptionNcmpInEvent.getData().getPredicates() + def subscriptionId = cmNotificationSubscriptionNcmpInEvent.getData().getSubscriptionId() + objectUnderTest.add(subscriptionId, predicates) + when: 'subscription is persisted in database' + objectUnderTest.removeFromDatabasePerDmi(subscriptionId,'dmi-1') + then: 'persistence service is called the correct number of times per dmi' + 4 * mockCmNotificationSubscriptionPersistenceService.removeCmNotificationSubscription(_,_,_,subscriptionId) + } + def setUpTestEvent(){ def jsonData = TestUtils.getResourceFileContent('cmSubscription/cmNotificationSubscriptionNcmpInEvent.json') def testEventSent = jsonObjectMapper.convertJsonString(jsonData, CmNotificationSubscriptionNcmpInEvent.class) def testCloudEventSent = CloudEventBuilder.v1() - .withData(objectMapper.writeValueAsBytes(testEventSent)) - .withId('subscriptionCreated') - .withType('subscriptionCreated') - .withSource(URI.create('some-resource')) - .withExtension('correlationid', 'test-cmhandle1').build() + .withData(objectMapper.writeValueAsBytes(testEventSent)) + .withId('subscriptionCreated') + .withType('subscriptionCreated') + .withSource(URI.create('some-resource')) + .withExtension('correlationid', 'test-cmhandle1').build() def consumerRecord = new ConsumerRecord<String, CloudEvent>('topic-name', 0, 0, 'event-key', testCloudEventSent) def cloudEvent = consumerRecord.value() @@ -191,10 +202,10 @@ class DmiCmNotificationSubscriptionCacheHandlerSpec extends MessagingBaseSpec { def initialiseMockInventoryPersistenceResponses(){ mockInventoryPersistence.getYangModelCmHandles(['ch1','ch2']) - >> [yangModelCmHandle1, yangModelCmHandle2] + >> [yangModelCmHandle1, yangModelCmHandle2] mockInventoryPersistence.getYangModelCmHandles(['ch3','ch4']) - >> [yangModelCmHandle3, yangModelCmHandle4] + >> [yangModelCmHandle3, yangModelCmHandle4] } -}
\ No newline at end of file +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/mapper/CmNotificationSubscriptionDmiInEventMapperSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/mapper/CmNotificationSubscriptionDmiInEventMapperSpec.groovy index 763aedaa0..ebbaf9557 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/mapper/CmNotificationSubscriptionDmiInEventMapperSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/mapper/CmNotificationSubscriptionDmiInEventMapperSpec.groovy @@ -22,12 +22,12 @@ package org.onap.cps.ncmp.api.impl.events.cmsubscription.mapper import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.DmiCmNotificationSubscriptionPredicate -import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence -import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle +import org.onap.cps.ncmp.impl.inventory.InventoryPersistence +import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle import spock.lang.Specification -import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_OPERATIONAL -import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_RUNNING +import static org.onap.cps.ncmp.api.data.models.DatastoreType.PASSTHROUGH_OPERATIONAL +import static org.onap.cps.ncmp.api.data.models.DatastoreType.PASSTHROUGH_RUNNING class CmNotificationSubscriptionDmiInEventMapperSpec extends Specification { @@ -48,10 +48,10 @@ class CmNotificationSubscriptionDmiInEventMapperSpec extends Specification { when: 'we try to map the values' def result = objectUnderTest.toCmNotificationSubscriptionDmiInEvent(dmiCmNotificationSubscriptionPredicates) then: 'it contains correct cm notification subscription cmhandle object' - assert result.data.cmhandles.cmhandleId.containsAll(['ch-1', 'ch-2']) - assert result.data.cmhandles.privateProperties.containsAll([['k1': 'v1'], ['k2': 'v2']]) + assert result.data.cmHandles.cmhandleId.containsAll(['ch-1', 'ch-2']) + assert result.data.cmHandles.privateProperties.containsAll([['k1': 'v1'], ['k2': 'v2']]) and: 'also has the correct dmi cm notification subscription predicates' assert result.data.predicates.targetFilter.containsAll([['ch-1'], ['ch-2']]) } -}
\ No newline at end of file +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/mapper/CmNotificationSubscriptionNcmpOutEventMapperSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/mapper/CmNotificationSubscriptionNcmpOutEventMapperSpec.groovy index f6bb24c2f..179cf361d 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/mapper/CmNotificationSubscriptionNcmpOutEventMapperSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/mapper/CmNotificationSubscriptionNcmpOutEventMapperSpec.groovy @@ -20,10 +20,10 @@ package org.onap.cps.ncmp.api.impl.events.cmsubscription.mapper +import org.onap.cps.ncmp.api.data.models.DatastoreType import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.CmNotificationSubscriptionStatus import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.DmiCmNotificationSubscriptionDetails import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.DmiCmNotificationSubscriptionPredicate -import org.onap.cps.ncmp.api.impl.operations.DatastoreType import spock.lang.Specification class CmNotificationSubscriptionNcmpOutEventMapperSpec extends Specification { diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmNotificationSubscriptionHandlerServiceImplSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmNotificationSubscriptionHandlerServiceImplSpec.groovy index 98b4ee267..55a817ed6 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmNotificationSubscriptionHandlerServiceImplSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmNotificationSubscriptionHandlerServiceImplSpec.groovy @@ -21,14 +21,14 @@ package org.onap.cps.ncmp.api.impl.events.cmsubscription.service import com.fasterxml.jackson.databind.ObjectMapper +import org.onap.cps.ncmp.api.data.models.DatastoreType import org.onap.cps.ncmp.api.impl.events.cmsubscription.CmNotificationSubscriptionDelta import org.onap.cps.ncmp.api.impl.events.cmsubscription.CmNotificationSubscriptionEventsHandler import org.onap.cps.ncmp.api.impl.events.cmsubscription.CmNotificationSubscriptionMappersHandler import org.onap.cps.ncmp.api.impl.events.cmsubscription.DmiCmNotificationSubscriptionCacheHandler -import org.onap.cps.ncmp.api.impl.events.cmsubscription.mapper.CmNotificationSubscriptionDmiInEventMapper -import org.onap.cps.ncmp.api.impl.events.cmsubscription.mapper.CmNotificationSubscriptionNcmpOutEventMapper import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.CmNotificationSubscriptionStatus import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.DmiCmNotificationSubscriptionDetails +import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.DmiCmNotificationSubscriptionPredicate import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.client_to_ncmp.CmNotificationSubscriptionNcmpInEvent import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.ncmp_to_dmi.CmNotificationSubscriptionDmiInEvent import org.onap.cps.ncmp.events.cmsubscription_merge1_0_0.ncmp_to_client.CmNotificationSubscriptionNcmpOutEvent @@ -50,13 +50,16 @@ class CmNotificationSubscriptionHandlerServiceImplSpec extends Specification{ mockCmNotificationSubscriptionEventsHandler, mockDmiCmNotificationSubscriptionCacheHandler) def testSubscriptionDetailsMap = ["dmi-1":new DmiCmNotificationSubscriptionDetails([], CmNotificationSubscriptionStatus.PENDING)] - def testListOfDeltaPredicates = [] def 'Consume valid and unique CmNotificationSubscriptionNcmpInEvent create message'() { given: 'a cmNotificationSubscriptionNcmp in event with unique subscription id' def jsonData = TestUtils.getResourceFileContent('cmSubscription/cmNotificationSubscriptionNcmpInEvent.json') def testEventConsumed = jsonObjectMapper.convertJsonString(jsonData, CmNotificationSubscriptionNcmpInEvent.class) + def testListOfDeltaPredicates = [new DmiCmNotificationSubscriptionPredicate(['ch1'].toSet(), DatastoreType.PASSTHROUGH_OPERATIONAL, ['/a/b'].toSet())] mockCmNotificationSubscriptionPersistenceService.isUniqueSubscriptionId("test-id") >> true + and: 'relevant details is extracted from the event' + def subscriptionId = testEventConsumed.getData().getSubscriptionId() + def predicates = testEventConsumed.getData().getPredicates() and: 'the cache handler returns for relevant subscription id' 1 * mockDmiCmNotificationSubscriptionCacheHandler.get("test-id") >> testSubscriptionDetailsMap and: 'the delta predicates is returned' @@ -66,7 +69,7 @@ class CmNotificationSubscriptionHandlerServiceImplSpec extends Specification{ 1 * mockCmNotificationSubscriptionMappersHandler .toCmNotificationSubscriptionDmiInEvent(testListOfDeltaPredicates) >> testDmiInEvent when: 'the valid and unique event is consumed' - objectUnderTest.processSubscriptionCreateRequest(testEventConsumed) + objectUnderTest.processSubscriptionCreateRequest(subscriptionId, predicates) then: 'the subscription cache handler is called once' 1 * mockDmiCmNotificationSubscriptionCacheHandler.add('test-id',_) and: 'the events handler method to publish DMI event is called correct number of times with the correct parameters' @@ -77,21 +80,68 @@ class CmNotificationSubscriptionHandlerServiceImplSpec extends Specification{ "test-id", "subscriptionCreateResponse", null, true) } + def 'Consume valid and Overlapping Cm Notification Subscription NcmpIn Event'() { + given: 'a cmNotificationSubscriptionNcmp in event with unique subscription id' + def jsonData = TestUtils.getResourceFileContent('cmSubscription/cmNotificationSubscriptionNcmpInEvent.json') + def testEventConsumed = jsonObjectMapper.convertJsonString(jsonData, CmNotificationSubscriptionNcmpInEvent.class) + def noDeltaPredicates = [] + mockCmNotificationSubscriptionPersistenceService.isUniqueSubscriptionId("test-id") >> true + and: 'the cache handler returns for relevant subscription id' + 1 * mockDmiCmNotificationSubscriptionCacheHandler.get("test-id") >> testSubscriptionDetailsMap + and: 'the delta predicates is returned' + 1 * mockCmNotificationSubscriptionDelta.getDelta(_) >> noDeltaPredicates + when: 'the valid and unique event is consumed' + objectUnderTest.processSubscriptionCreateRequest('test-id', noDeltaPredicates) + then: 'the subscription cache handler is called once' + 1 * mockDmiCmNotificationSubscriptionCacheHandler.add('test-id', _) + and: 'the subscription details are updated in the cache' + 1 * mockDmiCmNotificationSubscriptionCacheHandler.updateDmiCmNotificationSubscriptionStatusPerDmi('test-id', _, CmNotificationSubscriptionStatus.ACCEPTED) + and: 'we schedule to send the response after configured time from the cache' + 1 * mockCmNotificationSubscriptionEventsHandler.publishCmNotificationSubscriptionNcmpOutEvent( + "test-id", "subscriptionCreateResponse", null, true) + } + def 'Consume valid and but non-unique CmNotificationSubscription create message'() { given: 'a cmNotificationSubscriptionNcmp in event' def jsonData = TestUtils.getResourceFileContent('cmSubscription/cmNotificationSubscriptionNcmpInEvent.json') def testEventConsumed = jsonObjectMapper.convertJsonString(jsonData, CmNotificationSubscriptionNcmpInEvent.class) mockCmNotificationSubscriptionPersistenceService.isUniqueSubscriptionId('test-id') >> false + and: 'relevant details is extracted from the event' + def subscriptionId = testEventConsumed.getData().getSubscriptionId() + def predicates = testEventConsumed.getData().getPredicates() and: 'the NCMP out in event mapper returns an event for rejected request' def testNcmpOutEvent = new CmNotificationSubscriptionNcmpOutEvent() 1 * mockCmNotificationSubscriptionMappersHandler.toCmNotificationSubscriptionNcmpOutEventForRejectedRequest( "test-id",_) >> testNcmpOutEvent when: 'the valid but non-unique event is consumed' - objectUnderTest.processSubscriptionCreateRequest(testEventConsumed) + objectUnderTest.processSubscriptionCreateRequest(subscriptionId, predicates) then: 'the events handler method to publish DMI event is never called' 0 * mockCmNotificationSubscriptionEventsHandler.publishCmNotificationSubscriptionDmiInEvent(_,_,_,_) and: 'the events handler method to publish NCMP out event is called once' 1 * mockCmNotificationSubscriptionEventsHandler.publishCmNotificationSubscriptionNcmpOutEvent( 'test-id', 'subscriptionCreateResponse', testNcmpOutEvent, false) } + + def 'Consume valid CmNotificationSubscriptionNcmpInEvent delete message'() { + given: 'a cmNotificationSubscriptionNcmp in event for delete' + def jsonData = TestUtils.getResourceFileContent('cmSubscription/cmNotificationSubscriptionNcmpInEvent.json') + def testEventConsumed = jsonObjectMapper.convertJsonString(jsonData, CmNotificationSubscriptionNcmpInEvent.class) + and: 'relevant details is extracted from the event' + def subscriptionId = testEventConsumed.getData().getSubscriptionId() + def predicates = testEventConsumed.getData().getPredicates() + and: 'the cache handler returns for relevant subscription id' + 1 * mockDmiCmNotificationSubscriptionCacheHandler.get('test-id') >> testSubscriptionDetailsMap + when: 'the valid and unique event is consumed' + objectUnderTest.processSubscriptionDeleteRequest(subscriptionId, predicates) + then: 'the subscription cache handler is called once' + 1 * mockDmiCmNotificationSubscriptionCacheHandler.add('test-id', predicates) + and: 'the mapper handler to get DMI in event is called once' + 1 * mockCmNotificationSubscriptionMappersHandler.toCmNotificationSubscriptionDmiInEvent(_) + and: 'the events handler method to publish DMI event is called correct number of times with the correct parameters' + testSubscriptionDetailsMap.size() * mockCmNotificationSubscriptionEventsHandler.publishCmNotificationSubscriptionDmiInEvent( + 'test-id', 'dmi-1', 'subscriptionDeleteRequest', _) + and: 'we schedule to send the response after configured time from the cache' + 1 * mockCmNotificationSubscriptionEventsHandler.publishCmNotificationSubscriptionNcmpOutEvent( + 'test-id', 'subscriptionDeleteResponse', null, true) + } } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmNotificationSubscriptionPersistenceServiceImplSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmNotificationSubscriptionPersistenceServiceImplSpec.groovy index b51ecb0cf..ef735fd82 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmNotificationSubscriptionPersistenceServiceImplSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmNotificationSubscriptionPersistenceServiceImplSpec.groovy @@ -1,6 +1,7 @@ /* * ============LICENSE_START======================================================= * Copyright (c) 2024 Nordix Foundation. + * Modifications Copyright (C) 2024 TechMahindra Ltd. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,21 +21,20 @@ package org.onap.cps.ncmp.api.impl.events.cmsubscription.service -import org.onap.cps.utils.ContentType - -import static org.onap.cps.ncmp.api.impl.events.cmsubscription.service.CmNotificationSubscriptionPersistenceServiceImpl.CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_ID; -import static org.onap.cps.ncmp.api.impl.events.cmsubscription.service.CmNotificationSubscriptionPersistenceServiceImpl.CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_AND_CMHANDLE; -import static org.onap.cps.ncmp.api.impl.events.cmsubscription.service.CmNotificationSubscriptionPersistenceServiceImpl.CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_CMHANDLE_AND_XPATH; - +import com.fasterxml.jackson.databind.ObjectMapper import org.onap.cps.api.CpsDataService import org.onap.cps.api.CpsQueryService -import org.onap.cps.ncmp.api.impl.operations.DatastoreType +import org.onap.cps.ncmp.api.data.models.DatastoreType import org.onap.cps.spi.FetchDescendantsOption import org.onap.cps.spi.model.DataNode +import org.onap.cps.utils.ContentType import org.onap.cps.utils.JsonObjectMapper -import com.fasterxml.jackson.databind.ObjectMapper import spock.lang.Specification +import static org.onap.cps.ncmp.api.impl.events.cmsubscription.service.CmNotificationSubscriptionPersistenceServiceImpl.CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_FILTERS_WITH_DATASTORE_AND_CMHANDLE +import static org.onap.cps.ncmp.api.impl.events.cmsubscription.service.CmNotificationSubscriptionPersistenceServiceImpl.CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_CMHANDLE_AND_XPATH +import static org.onap.cps.ncmp.api.impl.events.cmsubscription.service.CmNotificationSubscriptionPersistenceServiceImpl.CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_ID + class CmNotificationSubscriptionPersistenceServiceImplSpec extends Specification { def jsonObjectMapper = new JsonObjectMapper(new ObjectMapper()) @@ -48,7 +48,7 @@ class CmNotificationSubscriptionPersistenceServiceImplSpec extends Specification def cpsPathQuery = "/datastores/datastore[@name='ncmp-datastore:passthrough-running']/cm-handles/cm-handle[@id='ch-1']/filters/filter[@xpath='/cps/path']"; and: 'datanodes optionally returned' 1 * mockCpsQueryService.queryDataNodes('NCMP-Admin', 'cm-data-subscriptions', - cpsPathQuery, FetchDescendantsOption.OMIT_DESCENDANTS) >> dataNode + cpsPathQuery, FetchDescendantsOption.OMIT_DESCENDANTS) >> dataNode when: 'we check for an ongoing cm subscription' def response = objectUnderTest.isOngoingCmNotificationSubscription(DatastoreType.PASSTHROUGH_RUNNING, 'ch-1', '/cps/path') then: 'we get expected response' @@ -64,7 +64,7 @@ class CmNotificationSubscriptionPersistenceServiceImplSpec extends Specification def cpsPathQuery = CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_ID.formatted('some-sub') and: 'relevant datanodes are returned' 1 * mockCpsQueryService.queryDataNodes('NCMP-Admin', 'cm-data-subscriptions', cpsPathQuery, FetchDescendantsOption.OMIT_DESCENDANTS) >> - dataNodes + dataNodes when: 'a subscription ID is tested for uniqueness' def result = objectUnderTest.isUniqueSubscriptionId('some-sub') then: 'result is as expected' @@ -79,7 +79,7 @@ class CmNotificationSubscriptionPersistenceServiceImplSpec extends Specification given: 'a valid cm subscription path query' def cpsPathQuery =CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_CMHANDLE_AND_XPATH.formatted('ncmp-datastore:passthrough-running', 'ch-1', '/x/y') and: 'a dataNode exists for the given cps path query' - mockCpsQueryService.queryDataNodes('NCMP-Admin', 'cm-data-subscriptions', + mockCpsQueryService.queryDataNodes('NCMP-Admin', 'cm-data-subscriptions', cpsPathQuery, FetchDescendantsOption.OMIT_DESCENDANTS) >> [new DataNode(xpath: cpsPathQuery, leaves: ['xpath': '/x/y','subscriptionIds': ['sub-1']])] when: 'the method to add/update cm notification subscription is called' objectUnderTest.addCmNotificationSubscription(DatastoreType.PASSTHROUGH_RUNNING, 'ch-1','/x/y', 'newSubId') @@ -88,18 +88,18 @@ class CmNotificationSubscriptionPersistenceServiceImplSpec extends Specification 'NCMP-Admin', 'cm-data-subscriptions', '/datastores/datastore[@name=\'ncmp-datastore:passthrough-running\']/cm-handles/cm-handle[@id=\'ch-1\']/filters', - objectUnderTest.getSubscriptionDetailsAsJson('/x/y', ['sub-1','newSubId']), _) + objectUnderTest.getSubscriptionDetailsAsJson('/x/y', ['sub-1','newSubId']), _,ContentType.JSON) } def 'Add new cm notification subscription for #datastoreType'() { given: 'a valid cm subscription path query' def cmSubscriptionCpsPathQuery = CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_CMHANDLE_AND_XPATH.formatted(datastoreName, 'ch-1', '/x/y') - def cmHandleForSubscriptionPathQuery = CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_AND_CMHANDLE.formatted(datastoreName, 'ch-1') + def cmHandleForSubscriptionPathQuery = CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_FILTERS_WITH_DATASTORE_AND_CMHANDLE.formatted(datastoreName, 'ch-1') and: 'a parent node xpath for the cm subscription path above' def parentNodeXpath = '/datastores/datastore[@name=\'%s\']/cm-handles' and: 'a datanode does not exist for cm subscription path query' mockCpsQueryService.queryDataNodes('NCMP-Admin', 'cm-data-subscriptions', - cmSubscriptionCpsPathQuery, + cmSubscriptionCpsPathQuery, FetchDescendantsOption.OMIT_DESCENDANTS) >> [] and: 'a datanode does not exist for the given cm handle subscription path query' mockCpsQueryService.queryDataNodes('NCMP-Admin', 'cm-data-subscriptions', @@ -124,7 +124,7 @@ class CmNotificationSubscriptionPersistenceServiceImplSpec extends Specification def 'Add new cm notification subscription when xpath does not exist for existing subscription cm handle'() { given: 'a valid cm subscription path query' def cmSubscriptionCpsPathQuery = CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_CMHANDLE_AND_XPATH.formatted(datastoreName, 'ch-1', '/x/y') - def cmHandleForSubscriptionPathQuery = CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_AND_CMHANDLE.formatted(datastoreName, 'ch-1') + def cmHandleForSubscriptionPathQuery = CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_FILTERS_WITH_DATASTORE_AND_CMHANDLE.formatted(datastoreName, 'ch-1') and: 'a parent node xpath for given cm handle for subscription path above' def parentNodeXpath = '/datastores/datastore[@name=\'%s\']/cm-handles/cm-handle[@id=\'%s\']/filters' and: 'a datanode does not exist for cm subscription path query' @@ -157,19 +157,36 @@ class CmNotificationSubscriptionPersistenceServiceImplSpec extends Specification then: 'the list of subscribers is updated' 1 * mockCpsDataService.updateNodeLeaves('NCMP-Admin', 'cm-data-subscriptions', '/datastores/datastore[@name=\'ncmp-datastore:passthrough-running\']/cm-handles/cm-handle[@id=\'ch-1\']/filters', - objectUnderTest.getSubscriptionDetailsAsJson('/x/y', ['sub-2']), _) + objectUnderTest.getSubscriptionDetailsAsJson('/x/y', ['sub-2']), _, ContentType.JSON) } - def 'Removing last ongoing subscription for datastore, cmhandle and xpath'(){ + def 'Removing last ongoing subscription for datastore and cmhandle and xpath'(){ given: 'a subscription exists when queried but has only 1 subscriber' - def cpsPathQuery = CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_CMHANDLE_AND_XPATH.formatted('ncmp-datastore:passthrough-running', 'ch-1', '/x/y') - mockCpsQueryService.queryDataNodes('NCMP-Admin', 'cm-data-subscriptions', - cpsPathQuery, FetchDescendantsOption.OMIT_DESCENDANTS) >> [new DataNode(xpath: cpsPathQuery, leaves: ['xpath': '/x/y','subscriptionIds': ['sub-1']])] + mockCpsQueryService.queryDataNodes( + 'NCMP-Admin', + 'cm-data-subscriptions', + CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_CMHANDLE_AND_XPATH.formatted('ncmp-datastore:passthrough-running', 'ch-1', '/x/y'), + FetchDescendantsOption.OMIT_DESCENDANTS) >> [new DataNode(leaves: ['xpath': '/x/y','subscriptionIds': ['sub-1']])] + and: 'the #scenario' + mockCpsQueryService.queryDataNodes( + 'NCMP-Admin', + 'cm-data-subscriptions', + CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_FILTERS_WITH_DATASTORE_AND_CMHANDLE.formatted('ncmp-datastore:passthrough-running', 'ch-1'), + FetchDescendantsOption.DIRECT_CHILDREN_ONLY) >> [new DataNode(childDataNodes: listOfChildNodes)] when: 'that last ongoing subscription is removed' objectUnderTest.removeCmNotificationSubscription(DatastoreType.PASSTHROUGH_RUNNING, 'ch-1', '/x/y', 'sub-1') then: 'the subscription with empty subscriber list is removed' 1 * mockCpsDataService.deleteDataNode('NCMP-Admin', 'cm-data-subscriptions', '/datastores/datastore[@name=\'ncmp-datastore:passthrough-running\']/cm-handles/cm-handle[@id=\'ch-1\']/filters/filter[@xpath=\'/x/y\']', _) + and: 'method call to delete the cm handle is called the correct number of times' + numberOfCallsToDeleteCmHandle * mockCpsDataService.deleteDataNode('NCMP-Admin', 'cm-data-subscriptions', + '/datastores/datastore[@name=\'ncmp-datastore:passthrough-running\']/cm-handles/cm-handle[@id=\'ch-1\']', + _) + where: + scenario | listOfChildNodes || numberOfCallsToDeleteCmHandle + 'cm handle in same datastore is used for other subscriptions' | [new DataNode()] || 0 + 'cm handle in same datastore is NOT used for other subscriptions' | [] || 1 } -}
\ No newline at end of file + +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiDataOperationsSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiDataOperationsSpec.groovy deleted file mode 100644 index eb6c7a0f4..000000000 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiDataOperationsSpec.groovy +++ /dev/null @@ -1,175 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2021-2024 Nordix Foundation - * Modifications Copyright (C) 2022 Bell Canada - * ================================================================================ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * ============LICENSE_END========================================================= - */ - -package org.onap.cps.ncmp.api.impl.operations - -import com.fasterxml.jackson.databind.ObjectMapper -import org.onap.cps.events.EventsPublisher -import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration -import org.onap.cps.ncmp.api.impl.exception.HttpClientRequestException -import org.onap.cps.ncmp.api.impl.utils.DmiServiceUrlBuilder -import org.onap.cps.ncmp.api.impl.utils.context.CpsApplicationContext -import org.onap.cps.ncmp.api.models.DataOperationRequest -import org.onap.cps.ncmp.api.models.CmResourceAddress -import org.onap.cps.ncmp.events.async1_0_0.DataOperationEvent -import org.onap.cps.ncmp.utils.TestUtils -import org.onap.cps.utils.JsonObjectMapper -import org.spockframework.spring.SpringBean -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.context.SpringBootTest -import org.springframework.http.HttpStatus -import org.springframework.http.ResponseEntity -import org.springframework.test.context.ContextConfiguration -import spock.lang.Shared - -import java.util.concurrent.TimeoutException - -import static org.onap.cps.ncmp.api.NcmpResponseStatus.DMI_SERVICE_NOT_RESPONDING -import static org.onap.cps.ncmp.api.NcmpResponseStatus.UNABLE_TO_READ_RESOURCE_DATA -import static org.onap.cps.ncmp.api.impl.events.mapper.CloudEventMapper.toTargetEvent -import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_OPERATIONAL -import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_RUNNING -import static org.onap.cps.ncmp.api.impl.operations.OperationType.CREATE -import static org.onap.cps.ncmp.api.impl.operations.OperationType.READ -import static org.onap.cps.ncmp.api.impl.operations.OperationType.UPDATE - -@SpringBootTest -@ContextConfiguration(classes = [EventsPublisher, CpsApplicationContext, NcmpConfiguration.DmiProperties, DmiDataOperations]) -class DmiDataOperationsSpec extends DmiOperationsBaseSpec { - - @SpringBean - DmiServiceUrlBuilder dmiServiceUrlBuilder = Mock() - def dmiServiceBaseUrl = "${dmiServiceName}/dmi/v1/ch/${cmHandleId}/data/ds/ncmp-datastore:" - def NO_TOPIC = null - def NO_REQUEST_ID = null - def NO_AUTH_HEADER = null - @Shared - def OPTIONS_PARAM = '(a=1,b=2)' - - @SpringBean - JsonObjectMapper spiedJsonObjectMapper = Spy(new JsonObjectMapper(new ObjectMapper())) - - @Autowired - DmiDataOperations objectUnderTest - - @SpringBean - EventsPublisher eventsPublisher = Stub() - - def 'call get resource data for #expectedDatastoreInUrl from DMI without topic #scenario.'() { - given: 'a cm handle for #cmHandleId' - mockYangModelCmHandleRetrieval(dmiProperties) - and: 'a positive response from DMI service when it is called with the expected parameters' - def responseFromDmi = new ResponseEntity<Object>(HttpStatus.OK) - def expectedUrl = dmiServiceBaseUrl + "${expectedDatastoreInUrl}?resourceIdentifier=${resourceIdentifier}${expectedOptionsInUrl}" - mockDmiRestClient.postOperationWithJsonData(expectedUrl, expectedJson, READ, NO_AUTH_HEADER) >> responseFromDmi - dmiServiceUrlBuilder.getDmiDatastoreUrl(_, _) >> expectedUrl - when: 'get resource data is invoked' - def cmResourceAddress = new CmResourceAddress(dataStore.datastoreName, cmHandleId, resourceIdentifier) - def result = objectUnderTest.getResourceDataFromDmi(cmResourceAddress, options, NO_TOPIC, NO_REQUEST_ID, NO_AUTH_HEADER) - then: 'the result is the response from the DMI service' - assert result == responseFromDmi - where: 'the following parameters are used' - scenario | dmiProperties | dataStore | options || expectedJson | expectedDatastoreInUrl | expectedOptionsInUrl - 'without properties' | [] | PASSTHROUGH_OPERATIONAL | OPTIONS_PARAM || '{"operation":"read","cmHandleProperties":{}}' | 'passthrough-operational' | '&options=(a=1,b=2)' - 'with properties' | [yangModelCmHandleProperty] | PASSTHROUGH_OPERATIONAL | OPTIONS_PARAM || '{"operation":"read","cmHandleProperties":{"prop1":"val1"}}' | 'passthrough-operational' | '&options=(a=1,b=2)' - 'null options' | [yangModelCmHandleProperty] | PASSTHROUGH_OPERATIONAL | null || '{"operation":"read","cmHandleProperties":{"prop1":"val1"}}' | 'passthrough-operational' | '' - 'empty options' | [yangModelCmHandleProperty] | PASSTHROUGH_OPERATIONAL | '' || '{"operation":"read","cmHandleProperties":{"prop1":"val1"}}' | 'passthrough-operational' | '' - 'datastore running without properties' | [] | PASSTHROUGH_RUNNING | OPTIONS_PARAM || '{"operation":"read","cmHandleProperties":{}}' | 'passthrough-running' | '&options=(a=1,b=2)' - 'datastore running with properties' | [yangModelCmHandleProperty] | PASSTHROUGH_RUNNING | OPTIONS_PARAM || '{"operation":"read","cmHandleProperties":{"prop1":"val1"}}' | 'passthrough-running' | '&options=(a=1,b=2)' - } - - def 'Execute (async) data operation from DMI service.'() { - given: 'collection of yang model cm Handles and data operation request' - mockYangModelCmHandleCollectionRetrieval([yangModelCmHandleProperty]) - def dataOperationBatchRequestJsonData = TestUtils.getResourceFileContent('dataOperationRequest.json') - def dataOperationRequest = spiedJsonObjectMapper.convertJsonString(dataOperationBatchRequestJsonData, DataOperationRequest.class) - dataOperationRequest.dataOperationDefinitions[0].cmHandleIds = [cmHandleId] - and: 'a positive response from DMI service when it is called with valid request parameters' - def responseFromDmi = new ResponseEntity<Object>(HttpStatus.ACCEPTED) - def expectedDmiBatchResourceDataUrl = "ncmp/v1/data/topic=my-topic-name" - def expectedBatchRequestAsJson = '{"operations":[{"operation":"read","operationId":"operational-14","datastore":"ncmp-datastore:passthrough-operational","options":"some option","resourceIdentifier":"some resource identifier","cmHandles":[{"id":"some-cm-handle","cmHandleProperties":{"prop1":"val1"}}]}]}' - mockDmiRestClient.postOperationWithJsonData(expectedDmiBatchResourceDataUrl, _, READ.operationName, NO_AUTH_HEADER) >> responseFromDmi - dmiServiceUrlBuilder.getDataOperationRequestUrl(_, _) >> expectedDmiBatchResourceDataUrl - when: 'get resource data for group of cm handles are invoked' - objectUnderTest.requestResourceDataFromDmi('my-topic-name', dataOperationRequest, 'requestId', NO_AUTH_HEADER) - then: 'the post operation was called and ncmp generated dmi request body json args' - 1 * mockDmiRestClient.postOperationWithJsonData(expectedDmiBatchResourceDataUrl, expectedBatchRequestAsJson, READ, NO_AUTH_HEADER) - } - - def 'Execute (async) data operation from DMI service for #scenario.'() { - given: 'data operation request body and dmi resource url' - def dmiDataOperation = DmiDataOperation.builder().operationId('some-operation-id').build() - dmiDataOperation.getCmHandles().add(CmHandle.builder().id('some-cm-handle-id').build()) - def dmiDataOperationResourceDataUrl = "http://dmi-service-name:dmi-port/dmi/v1/data?topic=my-topic-name&requestId=some-request-id" - def actualDataOperationCloudEvent = null - when: 'exception occurs after sending request to dmi service' - objectUnderTest.handleTaskCompletionException(new Throwable(exception), dmiDataOperationResourceDataUrl, List.of(dmiDataOperation)) - then: 'a cloud event is published' - eventsPublisher.publishCloudEvent('my-topic-name', 'some-request-id', _) >> { args -> actualDataOperationCloudEvent = args[2] } - and: 'the event contains the expected error details' - def eventDataValue = extractDataValue(actualDataOperationCloudEvent) - assert eventDataValue.operationId == dmiDataOperation.operationId - assert eventDataValue.ids == dmiDataOperation.cmHandles.id - assert eventDataValue.statusCode == responseCode.code - assert eventDataValue.statusMessage == responseCode.message - where: 'the following exceptions are occurred' - scenario | exception || responseCode - 'http client request exception' | new HttpClientRequestException('error-message', 'error-details', HttpStatus.SERVICE_UNAVAILABLE.value()) || UNABLE_TO_READ_RESOURCE_DATA - 'timeout exception' | new TimeoutException() || DMI_SERVICE_NOT_RESPONDING - } - - def 'call get all resource data.'() { - given: 'the system returns a cm handle with a sample property' - mockYangModelCmHandleRetrieval([yangModelCmHandleProperty]) - and: 'a positive response from DMI service when it is called with the expected parameters' - def responseFromDmi = new ResponseEntity<Object>(HttpStatus.OK) - def expectedUrl = dmiServiceBaseUrl + "passthrough-operational?resourceIdentifier=/" - mockDmiRestClient.postOperationWithJsonData(expectedUrl, '{"operation":"read","cmHandleProperties":{"prop1":"val1"}}', READ, null) >> responseFromDmi - dmiServiceUrlBuilder.getDmiDatastoreUrl(_, _) >> expectedUrl - when: 'get resource data is invoked' - def result = objectUnderTest.getResourceDataFromDmi( PASSTHROUGH_OPERATIONAL.datastoreName, cmHandleId, NO_REQUEST_ID) - then: 'the result is the response from the DMI service' - assert result == responseFromDmi - } - - def 'Write data for pass-through:running datastore in DMI.'() { - given: 'a cm handle for #cmHandleId' - mockYangModelCmHandleRetrieval([yangModelCmHandleProperty]) - and: 'a positive response from DMI service when it is called with the expected parameters' - def expectedUrl = dmiServiceBaseUrl + "passthrough-running?resourceIdentifier=${resourceIdentifier}" - def expectedJson = '{"operation":"' + expectedOperationInUrl + '","dataType":"some data type","data":"requestData","cmHandleProperties":{"prop1":"val1"}}' - def responseFromDmi = new ResponseEntity<Object>(HttpStatus.OK) - dmiServiceUrlBuilder.getDmiDatastoreUrl(_, _) >> expectedUrl - mockDmiRestClient.postOperationWithJsonData(expectedUrl, expectedJson, operation, NO_AUTH_HEADER) >> responseFromDmi - when: 'write resource method is invoked' - def result = objectUnderTest.writeResourceDataPassThroughRunningFromDmi(cmHandleId, 'parent/child', operation, 'requestData', 'some data type', NO_AUTH_HEADER) - then: 'the result is the response from the DMI service' - assert result == responseFromDmi - where: 'the following operation is performed' - operation || expectedOperationInUrl - CREATE || 'create' - UPDATE || 'update' - } - - def extractDataValue(actualDataOperationCloudEvent) { - return toTargetEvent(actualDataOperationCloudEvent, DataOperationEvent.class).data.responses[0] - } -} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/DataNodeBaseSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/DataNodeBaseSpec.groovy index 9f84fb073..2e0469430 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/DataNodeBaseSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/DataNodeBaseSpec.groovy @@ -20,11 +20,11 @@ package org.onap.cps.ncmp.api.impl.utils -import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DATASPACE_NAME - import org.onap.cps.spi.model.DataNodeBuilder import spock.lang.Specification +import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DATASPACE_NAME + class DataNodeBaseSpec extends Specification { def leaves1 = [status:'PENDING', cmHandleId:'CMHandle3', details:'Subscription forwarded to dmi plugin'] as Map diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/DataNodeHelperSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/DataNodeHelperSpec.groovy index 05e5f5898..9481613d4 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/DataNodeHelperSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/DataNodeHelperSpec.groovy @@ -20,10 +20,10 @@ package org.onap.cps.ncmp.api.impl.utils -import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DATASPACE_NAME - import org.onap.cps.spi.model.DataNodeBuilder +import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DATASPACE_NAME + class DataNodeHelperSpec extends DataNodeBaseSpec { def 'Get data node leaves as expected from a nested data node.'() { diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/DmiServiceUrlBuilderSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/DmiServiceUrlBuilderSpec.groovy index fbf2c3d78..69d08e3de 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/DmiServiceUrlBuilderSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/DmiServiceUrlBuilderSpec.groovy @@ -20,76 +20,67 @@ package org.onap.cps.ncmp.api.impl.utils -import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_RUNNING - -import org.onap.cps.ncmp.api.impl.operations.RequiredDmiService -import org.onap.cps.spi.utils.CpsValidator -import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle -import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration -import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle import spock.lang.Specification class DmiServiceUrlBuilderSpec extends Specification { - static YangModelCmHandle yangModelCmHandle = YangModelCmHandle.toYangModelCmHandle('dmiServiceName', - 'dmiDataServiceName', 'dmiModuleServiceName', new NcmpServiceCmHandle(cmHandleId: 'some-cm-handle-id'),'my-module-set-tag', 'my-alternate-id', 'my-data-producer-identifier') - - NcmpConfiguration.DmiProperties dmiProperties = new NcmpConfiguration.DmiProperties() - - def mockCpsValidator = Mock(CpsValidator) + def objectUnderTest = new DmiServiceUrlBuilder() - def objectUnderTest = new DmiServiceUrlBuilder(dmiProperties, mockCpsValidator) - - def setup() { - dmiProperties.dmiBasePath = 'dmi' + def 'Build URI with (variable) path segments and parameters.'() { + given: 'the URI details are given to the builder' + objectUnderTest.pathSegment(segment1) + objectUnderTest.variablePathSegment('myVariableSegment','someValue') + objectUnderTest.pathSegment(segment2) + objectUnderTest.queryParameter('param1', paramValue1) + objectUnderTest.queryParameter('param2', paramValue2) + objectUnderTest.queryParameter('param3', null) + objectUnderTest.queryParameter('param4', '') + when: 'the URI (string) is build' + def result = objectUnderTest.build('myDmiServer', 'myBasePath') + then: 'the URI is correct (segments are in correct order) ' + assert result == expectedUri + where: 'following URI details are used' + segment1 | segment2 | paramValue1 | paramValue2 || expectedUri + 'segment1' | 'segment2' | '123' | 'abc' || 'myDmiServer/myBasePath/v1/segment1/someValue/segment2?param1=123¶m2=abc' + 'segment2' | 'segment1' | 'abc' | '123' || 'myDmiServer/myBasePath/v1/segment2/someValue/segment1?param1=abc¶m2=123' } - def 'Create the dmi service url with #scenario.'() { - given: 'uri variables' - def uriVars = objectUnderTest.populateUriVariables(PASSTHROUGH_RUNNING.datastoreName, yangModelCmHandle.resolveDmiServiceName(RequiredDmiService.DATA), 'cmHandle') - and: 'query params' - def uriQueries = objectUnderTest.populateQueryParams(resourceId, 'optionsParamInQuery', topic) - when: 'a dmi datastore service url is generated' - def dmiServiceUrl = objectUnderTest.getDmiDatastoreUrl(uriQueries, uriVars) - then: 'service url is generated as expected' - assert dmiServiceUrl == expectedDmiServiceUrl - where: 'the following parameters are used' - scenario | topic | resourceId || expectedDmiServiceUrl - 'With valid resourceId' | 'topicParamInQuery' | 'resourceId' || 'dmiServiceName/dmi/v1/ch/cmHandle/data/ds/ncmp-datastore:passthrough-running?resourceIdentifier=resourceId&options=optionsParamInQuery&topic=topicParamInQuery' - 'With Empty resourceId' | 'topicParamInQuery' | '' || 'dmiServiceName/dmi/v1/ch/cmHandle/data/ds/ncmp-datastore:passthrough-running?options=optionsParamInQuery&topic=topicParamInQuery' - 'With Empty dmi base path' | 'topicParamInQuery' | 'resourceId' || 'dmiServiceName/dmi/v1/ch/cmHandle/data/ds/ncmp-datastore:passthrough-running?resourceIdentifier=resourceId&options=optionsParamInQuery&topic=topicParamInQuery' - 'With Empty topicParamInQuery' | '' | 'resourceId' || 'dmiServiceName/dmi/v1/ch/cmHandle/data/ds/ncmp-datastore:passthrough-running?resourceIdentifier=resourceId&options=optionsParamInQuery' + def 'Build URI with special characters in path segments.'() { + given: 'the path segments are given to the builder' + objectUnderTest.pathSegment(segment) + objectUnderTest.variablePathSegment('myVariableSegment', variableSegmentValue) + when: 'the URI (string) is build' + def result = objectUnderTest.build('myDmiServer', 'myBasePath') + then: 'Only teh characters that cause issues in path segments issues are encoded' + assert result == expectedUri + where: 'following variable path segments are used' + segment | variableSegmentValue || expectedUri + 'some/special?characters=are\\encoded' | 'my/variable/segment' || 'myDmiServer/myBasePath/v1/some%2Fspecial%3Fcharacters=are%5Cencoded/my%2Fvariable%2Fsegment' + 'but=some&are:not-!' | 'my&variable:segment' || 'myDmiServer/myBasePath/v1/but=some&are:not-!/my&variable:segment' } - def 'Populate dmi data store url #scenario.'() { - given: 'uri variables are created' - dmiProperties.dmiBasePath = dmiBasePath - def uriVars = objectUnderTest.populateUriVariables(PASSTHROUGH_RUNNING.datastoreName, yangModelCmHandle.resolveDmiServiceName(RequiredDmiService.DATA), 'cmHandle') - and: 'null query params' - def uriQueries = objectUnderTest.populateQueryParams(null, null, null) - when: 'a dmi datastore service url is generated' - def dmiServiceUrl = objectUnderTest.getDmiDatastoreUrl(uriQueries, uriVars) - then: 'the created dmi service url matches the expected' - assert dmiServiceUrl == expectedDmiServiceUrl - where: 'the following parameters are used' - scenario | decription | dmiBasePath || expectedDmiServiceUrl - 'with base path / ' | 'Invalid base path as it starts with /' | '/dmi' || 'dmiServiceName//dmi/v1/ch/cmHandle/data/ds/ncmp-datastore:passthrough-running' - 'without base path / ' | 'Valid path as it does not starts with /' | 'dmi' || 'dmiServiceName/dmi/v1/ch/cmHandle/data/ds/ncmp-datastore:passthrough-running' + def 'Build URI with special characters in query parameters.'() { + given: 'the query parameter is given to the builder' + objectUnderTest.queryParameter(paramName, value) + when: 'the URI (string) is build' + def result = objectUnderTest.build('myDmiServer', 'myBasePath') + then: 'Only the characters (in the name and value) that cause issues in query parameters are encoded' + assert result == expectedUri + where: 'the following query parameters are used' + paramName | value || expectedUri + 'my¶m' | 'some?special&characters=are\\encoded' || 'myDmiServer/myBasePath/v1?my%26param=some?special%26characters%3Dare%5Cencoded' + 'my-param' | 'but/some:are-not-!' || 'myDmiServer/myBasePath/v1?my-param=but/some:are-not-!' } - def 'Bath request Url creation.'() { - given: 'the required path parameters' - def batchRequestUriVariables = [dmiServiceName: 'some-service', dmiBasePath: 'testBase', cmHandleId: '123'] - and: 'the relevant query parameters' - def batchRequestQueryParams = objectUnderTest.getDataOperationRequestQueryParams('some topic', 'some id') - when: 'a URL is created' - def result = objectUnderTest.getDataOperationRequestUrl(batchRequestQueryParams, batchRequestUriVariables) - then: 'it is formed correctly' - assert result.toString() == 'some-service/testBase/v1/data?topic=some topic&requestId=some id' + def 'Build URI with empty query parameters.'() { + when: 'the query parameter is given to the builder' + objectUnderTest.queryParameter('param', value) + and: 'the URI (string) is build' + def result = objectUnderTest.build('myDmiServer', 'myBasePath') + then: 'no parameter gets added' + assert result == 'myDmiServer/myBasePath/v1' + where: 'the following parameter values are used' + value << [ null, '', ' ' ] } - def 'Populate batch uri variables.'() { - expect: 'Populate batch uri variables returns a map with given service name and base path from setup' - assert objectUnderTest.populateDataOperationRequestUriVariables('some service') == [dmiServiceName: 'some service', dmiBasePath: 'dmi' ] - } } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/EventDateTimeFormatterSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/EventDateTimeFormatterSpec.groovy new file mode 100644 index 000000000..c72eb9e4c --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/EventDateTimeFormatterSpec.groovy @@ -0,0 +1,45 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2024 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.api.impl.utils + +import spock.lang.Specification +import java.time.Year + +class EventDateTimeFormatterSpec extends Specification { + + def 'Get ISO formatted date and time.' () { + expect: 'iso formatted date and time starts with current year' + assert EventDateTimeFormatter.getCurrentIsoFormattedDateTime().startsWith(String.valueOf(Year.now())) + } + + def 'Convert date time from string to OffsetDateTime type.'() { + when: 'date time as a string is converted to OffsetDateTime type' + def result = EventDateTimeFormatter.toIsoOffsetDateTime('2024-05-28T18:28:02.869+0100') + then: 'the result convert back back to a string is the same as the original timestamp (except the format of timezone offset)' + assert result.toString() == '2024-05-28T18:28:02.869+01:00' + } + + def 'Convert blank string.' () { + expect: 'converting a blank string result in null' + assert EventDateTimeFormatter.toIsoOffsetDateTime(' ') == null + } + +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/context/CpsApplicationContextSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/context/CpsApplicationContextSpec.groovy index b7fa44925..ee117160c 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/context/CpsApplicationContextSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/context/CpsApplicationContextSpec.groovy @@ -1,3 +1,23 @@ +/* + * ============LICENSE_START======================================================== + * Copyright (c) 2023-2024 Nordix Foundation. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an 'AS IS' BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + package org.onap.cps.ncmp.api.impl.utils.context import com.fasterxml.jackson.databind.ObjectMapper diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/models/CmHandleRegistrationResponseSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/models/CmHandleRegistrationResponseSpec.groovy index 7803ae319..055a6e744 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/models/CmHandleRegistrationResponseSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/models/CmHandleRegistrationResponseSpec.groovy @@ -19,16 +19,17 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.models +package org.onap.cps.ncmp.api.inventory.models + +import org.onap.cps.ncmp.api.inventory.models.CmHandleRegistrationResponse.Status +import spock.lang.Specification + +import java.util.stream.Collectors import static org.onap.cps.ncmp.api.NcmpResponseStatus.ALTERNATE_ID_ALREADY_ASSOCIATED import static org.onap.cps.ncmp.api.NcmpResponseStatus.CM_HANDLE_ALREADY_EXIST import static org.onap.cps.ncmp.api.NcmpResponseStatus.UNKNOWN_ERROR -import org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.Status -import spock.lang.Specification -import java.util.stream.Collectors - class CmHandleRegistrationResponseSpec extends Specification { def 'Successful cm-handle Registration Response'() { diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/CompositeStateBuilderSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/models/CompositeStateBuilderSpec.groovy index 777b505b4..dfed3c787 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/CompositeStateBuilderSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/models/CompositeStateBuilderSpec.groovy @@ -19,13 +19,12 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.inventory +package org.onap.cps.ncmp.api.inventory.models -import org.onap.cps.ncmp.api.impl.inventory.CmHandleState -import org.onap.cps.ncmp.api.impl.inventory.CompositeState -import org.onap.cps.ncmp.api.impl.inventory.CompositeStateBuilder -import org.onap.cps.ncmp.api.impl.inventory.DataStoreSyncState -import org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory + +import org.onap.cps.ncmp.impl.inventory.DataStoreSyncState +import org.onap.cps.ncmp.impl.inventory.models.CmHandleState +import org.onap.cps.ncmp.impl.inventory.models.LockReasonCategory import org.onap.cps.spi.model.DataNode import org.onap.cps.spi.model.DataNodeBuilder import spock.lang.Specification diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/CompositeStateSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/models/CompositeStateSpec.groovy index d8beba8de..f6e4c8870 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/CompositeStateSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/models/CompositeStateSpec.groovy @@ -18,20 +18,20 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.inventory +package org.onap.cps.ncmp.api.inventory.models import com.fasterxml.jackson.databind.ObjectMapper -import org.onap.cps.ncmp.api.impl.inventory.CmHandleState -import org.onap.cps.ncmp.api.impl.inventory.CompositeState -import org.onap.cps.ncmp.api.impl.inventory.DataStoreSyncState -import org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory +import org.onap.cps.ncmp.impl.inventory.DataStoreSyncState +import org.onap.cps.ncmp.impl.inventory.models.CmHandleState +import org.onap.cps.ncmp.impl.inventory.models.LockReasonCategory import spock.lang.Specification + import java.time.OffsetDateTime import java.time.ZoneOffset import java.time.format.DateTimeFormatter -import static org.onap.cps.ncmp.api.impl.inventory.CompositeState.DataStores -import static org.onap.cps.ncmp.api.impl.inventory.CompositeState.Operational +import static org.onap.cps.ncmp.api.inventory.models.CompositeState.DataStores +import static org.onap.cps.ncmp.api.inventory.models.CompositeState.Operational import static org.onap.cps.ncmp.utils.TestUtils.getResourceFileContent import static org.springframework.util.StringUtils.trimAllWhitespace diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/trustlevel/TrustLevelSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/models/TrustLevelSpec.groovy index f2521b560..1e1dfaaa9 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/trustlevel/TrustLevelSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/models/TrustLevelSpec.groovy @@ -18,7 +18,7 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.trustlevel +package org.onap.cps.ncmp.api.inventory.models import spock.lang.Specification diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/models/YangResourceTest.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/models/YangResourceTest.groovy index 0a0c84e25..068997591 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/models/YangResourceTest.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/models/YangResourceTest.groovy @@ -1,4 +1,5 @@ -package org.onap.cps.ncmp.api.models +package org.onap.cps.ncmp.api.inventory.models + import spock.lang.Specification diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiOperationsBaseSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/DmiOperationsBaseSpec.groovy index 72a0f2f11..f2d2ab0a1 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiOperationsBaseSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/DmiOperationsBaseSpec.groovy @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2021-2023 Nordix Foundation + * Copyright (C) 2021-2024 Nordix Foundation * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,17 +18,14 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.operations +package org.onap.cps.ncmp.impl import com.fasterxml.jackson.databind.ObjectMapper import org.onap.cps.ncmp.api.impl.client.DmiRestClient -import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration -import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle -import org.onap.cps.ncmp.api.impl.utils.DmiServiceUrlBuilder -import org.onap.cps.ncmp.api.impl.inventory.CmHandleState -import org.onap.cps.ncmp.api.impl.inventory.CompositeState -import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence -import org.onap.cps.spi.utils.CpsValidator +import org.onap.cps.ncmp.api.inventory.models.CompositeState +import org.onap.cps.ncmp.impl.inventory.InventoryPersistence +import org.onap.cps.ncmp.impl.inventory.models.CmHandleState +import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle import org.spockframework.spring.SpringBean import spock.lang.Shared import spock.lang.Specification @@ -44,16 +41,11 @@ abstract class DmiOperationsBaseSpec extends Specification { @SpringBean InventoryPersistence mockInventoryPersistence = Mock() - def mockCpsValidator = Mock(CpsValidator) - @SpringBean ObjectMapper spyObjectMapper = Spy() - @SpringBean - DmiServiceUrlBuilder dmiServiceUrlBuilder = new DmiServiceUrlBuilder(new NcmpConfiguration.DmiProperties(), mockCpsValidator) - def yangModelCmHandle = new YangModelCmHandle() - def static dmiServiceName = 'some service name' + def static dmiServiceName = 'someServiceName' def static cmHandleId = 'some-cm-handle' def static resourceIdentifier = 'parent/child' @@ -68,7 +60,7 @@ abstract class DmiOperationsBaseSpec extends Specification { } def mockYangModelCmHandleCollectionRetrieval(dmiProperties) { - populateYangModelCmHandle(dmiProperties, "") + populateYangModelCmHandle(dmiProperties, '') mockInventoryPersistence.getYangModelCmHandles(_) >> [yangModelCmHandle] } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/DmiDataOperationsSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/DmiDataOperationsSpec.groovy new file mode 100644 index 000000000..65d3100d1 --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/DmiDataOperationsSpec.groovy @@ -0,0 +1,195 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2021-2024 Nordix Foundation + * Modifications Copyright (C) 2022 Bell Canada + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.impl.data + +import com.fasterxml.jackson.databind.ObjectMapper +import org.onap.cps.events.EventsPublisher +import org.onap.cps.ncmp.api.data.models.CmResourceAddress +import org.onap.cps.ncmp.api.data.models.DataOperationRequest +import org.onap.cps.ncmp.api.impl.config.DmiProperties +import org.onap.cps.ncmp.api.impl.exception.DmiClientRequestException +import org.onap.cps.ncmp.api.impl.utils.context.CpsApplicationContext +import org.onap.cps.ncmp.events.async1_0_0.DataOperationEvent +import org.onap.cps.ncmp.impl.DmiOperationsBaseSpec +import org.onap.cps.ncmp.impl.inventory.models.CmHandleState +import org.onap.cps.ncmp.utils.TestUtils +import org.onap.cps.utils.JsonObjectMapper +import org.spockframework.spring.SpringBean +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.http.HttpStatus +import org.springframework.http.ResponseEntity +import org.springframework.test.context.ContextConfiguration +import reactor.core.publisher.Mono +import spock.lang.Shared + +import static org.onap.cps.ncmp.api.NcmpResponseStatus.UNKNOWN_ERROR +import static org.onap.cps.ncmp.api.data.models.DatastoreType.PASSTHROUGH_OPERATIONAL +import static org.onap.cps.ncmp.api.data.models.DatastoreType.PASSTHROUGH_RUNNING +import static org.onap.cps.ncmp.api.data.models.OperationType.CREATE +import static org.onap.cps.ncmp.api.data.models.OperationType.READ +import static org.onap.cps.ncmp.api.data.models.OperationType.UPDATE +import static org.onap.cps.ncmp.impl.models.RequiredDmiService.DATA +import static org.onap.cps.ncmp.utils.events.CloudEventMapper.toTargetEvent + +@SpringBootTest +@ContextConfiguration(classes = [EventsPublisher, CpsApplicationContext, DmiProperties, DmiDataOperations]) +class DmiDataOperationsSpec extends DmiOperationsBaseSpec { + + def dmiServiceBaseUrl = "${DmiOperationsBaseSpec.dmiServiceName}/dmi/v1/ch/${DmiOperationsBaseSpec.cmHandleId}/data/ds/ncmp-datastore:" + def NO_TOPIC = null + def NO_REQUEST_ID = null + def NO_AUTH_HEADER = null + + @Shared + def OPTIONS_PARAM = '(a=1,b=2)' + + @SpringBean + JsonObjectMapper spiedJsonObjectMapper = Spy(new JsonObjectMapper(new ObjectMapper())) + + @Autowired + DmiDataOperations objectUnderTest + + @SpringBean + EventsPublisher eventsPublisher = Stub() + + def 'call get resource data for #expectedDatastoreInUrl from DMI without topic #scenario.'() { + given: 'a cm handle for #cmHandleId' + mockYangModelCmHandleRetrieval(dmiProperties) + and: 'a positive response from DMI service when it is called with the expected parameters' + def responseFromDmi = Mono.just(new ResponseEntity<Object>('{some-key:some-value}', HttpStatus.OK)) + def expectedUrl = "${dmiServiceBaseUrl}${expectedDatastoreInUrl}?resourceIdentifier=${DmiOperationsBaseSpec.resourceIdentifier}${expectedOptionsInUrl}" + def expectedJson = '{"operation":"read","cmHandleProperties":' + expectedProperties + ',"moduleSetTag":""}' + mockDmiRestClient.asynchronousPostOperationWithJsonData(DATA, expectedUrl, expectedJson, READ, NO_AUTH_HEADER) >> responseFromDmi + when: 'get resource data is invoked' + def cmResourceAddress = new CmResourceAddress(dataStore.datastoreName, DmiOperationsBaseSpec.cmHandleId, DmiOperationsBaseSpec.resourceIdentifier) + def result = objectUnderTest.getResourceDataFromDmi(cmResourceAddress, options, NO_TOPIC, NO_REQUEST_ID, NO_AUTH_HEADER).block() + then: 'the result is the response from the DMI service' + assert result.body == '{some-key:some-value}' + assert result.statusCode.'2xxSuccessful' + where: 'the following parameters are used' + scenario | dmiProperties | dataStore | options || expectedProperties | expectedDatastoreInUrl | expectedOptionsInUrl + 'without properties' | [] | PASSTHROUGH_OPERATIONAL | OPTIONS_PARAM || '{}' | 'passthrough-operational' | '&options=(a%3D1,b%3D2)' + 'with properties' | [yangModelCmHandleProperty] | PASSTHROUGH_OPERATIONAL | OPTIONS_PARAM || '{"prop1":"val1"}' | 'passthrough-operational' | '&options=(a%3D1,b%3D2)' + 'null options' | [yangModelCmHandleProperty] | PASSTHROUGH_OPERATIONAL | null || '{"prop1":"val1"}' | 'passthrough-operational' | '' + 'empty options' | [yangModelCmHandleProperty] | PASSTHROUGH_OPERATIONAL | '' || '{"prop1":"val1"}' | 'passthrough-operational' | '' + 'datastore running without properties' | [] | PASSTHROUGH_RUNNING | OPTIONS_PARAM || '{}' | 'passthrough-running' | '&options=(a%3D1,b%3D2)' + 'datastore running with properties' | [yangModelCmHandleProperty] | PASSTHROUGH_RUNNING | OPTIONS_PARAM || '{"prop1":"val1"}' | 'passthrough-running' | '&options=(a%3D1,b%3D2)' + } + + def 'Execute (async) data operation from DMI service.'() { + given: 'collection of yang model cm Handles and data operation request' + mockYangModelCmHandleCollectionRetrieval([yangModelCmHandleProperty]) + def dataOperationBatchRequestJsonData = TestUtils.getResourceFileContent('dataOperationRequest.json') + def dataOperationRequest = spiedJsonObjectMapper.convertJsonString(dataOperationBatchRequestJsonData, DataOperationRequest.class) + dataOperationRequest.dataOperationDefinitions[0].cmHandleIds = [DmiOperationsBaseSpec.cmHandleId] + and: 'a positive response from DMI service when it is called with valid request parameters' + def responseFromDmi = Mono.just(new ResponseEntity<Object>(HttpStatus.ACCEPTED)) + def expectedDmiBatchResourceDataUrl = "someServiceName/dmi/v1/data?requestId=requestId&topic=my-topic-name" + def expectedBatchRequestAsJson = '{"operations":[{"operation":"read","operationId":"operational-14","datastore":"ncmp-datastore:passthrough-operational","options":"some option","resourceIdentifier":"some resource identifier","cmHandles":[{"id":"some-cm-handle","moduleSetTag":"","cmHandleProperties":{"prop1":"val1"}}]}]}' + mockDmiRestClient.asynchronousPostOperationWithJsonData(DATA, expectedDmiBatchResourceDataUrl, _, READ, NO_AUTH_HEADER) >> responseFromDmi + when: 'get resource data for group of cm handles is invoked' + objectUnderTest.requestResourceDataFromDmi('my-topic-name', dataOperationRequest, 'requestId', NO_AUTH_HEADER) + then: 'the post operation was called with the expected URL and JSON request body' + 1 * mockDmiRestClient.asynchronousPostOperationWithJsonData(DATA, expectedDmiBatchResourceDataUrl, expectedBatchRequestAsJson, READ, NO_AUTH_HEADER) + } + + def 'Execute (async) data operation from DMI service with Exception.'() { + given: 'collection of yang model cm Handles and data operation request' + mockYangModelCmHandleCollectionRetrieval([yangModelCmHandleProperty]) + def dataOperationBatchRequestJsonData = TestUtils.getResourceFileContent('dataOperationRequest.json') + def dataOperationRequest = spiedJsonObjectMapper.convertJsonString(dataOperationBatchRequestJsonData, DataOperationRequest.class) + dataOperationRequest.dataOperationDefinitions[0].cmHandleIds = [DmiOperationsBaseSpec.cmHandleId] + and: 'the published cloud event will be captured' + def actualDataOperationCloudEvent = null + eventsPublisher.publishCloudEvent('my-topic-name', 'my-request-id', _) >> { args -> actualDataOperationCloudEvent = args[2] } + and: 'a DMI client request exception is thrown when DMI service is called' + mockDmiRestClient.asynchronousPostOperationWithJsonData(*_) >> { Mono.error(new DmiClientRequestException(123, '', '', UNKNOWN_ERROR)) } + when: 'attempt to get resource data for group of cm handles is invoked' + objectUnderTest.requestResourceDataFromDmi('my-topic-name', dataOperationRequest, 'my-request-id', NO_AUTH_HEADER) + then: 'the event contains the expected error details' + def eventDataValue = extractDataValue(actualDataOperationCloudEvent) + assert eventDataValue.statusCode == '108' + assert eventDataValue.statusMessage == UNKNOWN_ERROR.message + and: 'the event contains the correct operation details' + assert eventDataValue.operationId == dataOperationRequest.dataOperationDefinitions[0].operationId + assert eventDataValue.ids == dataOperationRequest.dataOperationDefinitions[0].cmHandleIds + } + + def 'call get all resource data.'() { + given: 'the system returns a cm handle with a sample property and sample module set tag' + mockYangModelCmHandleRetrieval([yangModelCmHandleProperty], 'my-module-set-tag') + and: 'a positive response from DMI service when it is called with the expected parameters' + def responseFromDmi = new ResponseEntity<Object>(HttpStatus.OK) + def expectedUrl = dmiServiceBaseUrl + "passthrough-operational?resourceIdentifier=/" + def expectedJson = '{"operation":"read","cmHandleProperties":{"prop1":"val1"},"moduleSetTag":"my-module-set-tag"}' + mockDmiRestClient.synchronousPostOperationWithJsonData(DATA, expectedUrl, expectedJson, READ, null) >> responseFromDmi + when: 'get resource data is invoked' + def result = objectUnderTest.getAllResourceDataFromDmi(DmiOperationsBaseSpec.cmHandleId, NO_REQUEST_ID) + then: 'the result is the response from the DMI service' + assert result == responseFromDmi + } + + def 'Write data for pass-through:running datastore in DMI.'() { + given: 'a cm handle for #cmHandleId' + mockYangModelCmHandleRetrieval([yangModelCmHandleProperty]) + and: 'a positive response from DMI service when it is called with the expected parameters' + def expectedUrl = "${dmiServiceBaseUrl}passthrough-running?resourceIdentifier=${DmiOperationsBaseSpec.resourceIdentifier}" + def expectedJson = '{"operation":"' + expectedOperationInUrl + '","dataType":"some data type","data":"requestData","cmHandleProperties":{"prop1":"val1"},"moduleSetTag":""}' + def responseFromDmi = new ResponseEntity<Object>(HttpStatus.OK) + mockDmiRestClient.synchronousPostOperationWithJsonData(DATA, expectedUrl, expectedJson, operation, NO_AUTH_HEADER) >> responseFromDmi + when: 'write resource method is invoked' + def result = objectUnderTest.writeResourceDataPassThroughRunningFromDmi(DmiOperationsBaseSpec.cmHandleId, 'parent/child', operation, 'requestData', 'some data type', NO_AUTH_HEADER) + then: 'the result is the response from the DMI service' + assert result == responseFromDmi + where: 'the following operation is performed' + operation || expectedOperationInUrl + CREATE || 'create' + UPDATE || 'update' + } + + def 'State Ready validation'() { + given: ' a yang model cm handle' + populateYangModelCmHandle([] ,'') + when: 'Validating State of #cmHandleState' + def caughtException = null + try { + objectUnderTest.validateIfCmHandleStateReady(yangModelCmHandle, cmHandleState) + } catch (Exception e) { + caughtException = e + } + then: 'only when not ready a exception is thrown' + if (expecteException) { + assert caughtException.details.contains('not in READY state') + } else { + assert caughtException == null + } + where: ' the following states are used' + cmHandleState || expecteException + CmHandleState.READY || false + CmHandleState.ADVISED || true + } + + def extractDataValue(actualDataOperationCloudEvent) { + return toTargetEvent(actualDataOperationCloudEvent, DataOperationEvent).data.responses[0] + } +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/NcmpCachedResourceRequestHandlerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/NcmpCachedResourceRequestHandlerSpec.groovy new file mode 100644 index 000000000..9c696dcc7 --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/NcmpCachedResourceRequestHandlerSpec.groovy @@ -0,0 +1,63 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2024 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.impl.data + +import org.onap.cps.api.CpsDataService +import org.onap.cps.ncmp.api.data.models.CmResourceAddress +import org.onap.cps.spi.model.DataNode +import reactor.core.publisher.Mono +import spock.lang.Specification + +import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS +import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS + +class NcmpCachedResourceRequestHandlerSpec extends Specification { + + def cpsDataService = Mock(CpsDataService) + def networkCmProxyQueryService= Mock(NetworkCmProxyQueryService) + + def objectUnderTest = new NcmpCachedResourceRequestHandler(cpsDataService, networkCmProxyQueryService) + + def 'Execute a request with include descendants = #includeDescendants.'() { + when: 'executing a request' + objectUnderTest.executeRequest('ch-1', 'resource', includeDescendants) + then: 'it is delegated to the ncmp query service with the correct option' + 1 * networkCmProxyQueryService.queryResourceDataOperational('ch-1','resource', expectedFetchDescendantsOption) + where: 'the following options are used' + includeDescendants || expectedFetchDescendantsOption + true || INCLUDE_ALL_DESCENDANTS + false || OMIT_DESCENDANTS + } + + def 'Get resource data.'() { + given: 'the data service returns 2 nodes for the given resource address' + def cmResourceAddress = new CmResourceAddress('datastore','ch-1','resource') + def dataNode1 = new DataNode(xpath:'p1') + def dataNode2 = new DataNode(xpath:'p2') + cpsDataService.getDataNodes('datastore','ch-1','resource',OMIT_DESCENDANTS) >> [dataNode1, dataNode2] + when: 'getting the resource data' + def result = objectUnderTest.getResourceDataForCmHandle(cmResourceAddress, 'options', 'topic', 'request id', false, 'authorization') + then: 'the result is a "Mono" holding just the first data node' + assert result instanceof Mono + assert result.block() == dataNode1 + } + +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/NcmpDatastoreRequestHandlerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/NcmpDatastoreRequestHandlerSpec.groovy new file mode 100644 index 000000000..70c6428c8 --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/NcmpDatastoreRequestHandlerSpec.groovy @@ -0,0 +1,132 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2023-2024 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.impl.data + +import org.onap.cps.ncmp.api.data.exceptions.InvalidDatastoreException +import org.onap.cps.ncmp.api.data.exceptions.OperationNotSupportedException +import org.onap.cps.ncmp.api.data.models.CmResourceAddress +import org.onap.cps.ncmp.api.data.models.DataOperationDefinition +import org.onap.cps.ncmp.api.data.models.DataOperationRequest +import org.onap.cps.ncmp.exceptions.PayloadTooLargeException +import org.onap.cps.ncmp.impl.data.exceptions.InvalidOperationException +import org.springframework.http.ResponseEntity +import reactor.core.publisher.Mono +import spock.lang.Specification + +import static org.springframework.http.HttpStatus.I_AM_A_TEAPOT + +class NcmpDatastoreRequestHandlerSpec extends Specification { + + def dmiDataOperations = Mock(DmiDataOperations) + + def objectUnderTest = new NcmpPassthroughResourceRequestHandler(dmiDataOperations) + + def NO_TOPIC = null + def NO_AUTH_HEADER = null + + def 'Attempt to execute async get request with #scenario.'() { + given: 'notification feature is turned on/off' + objectUnderTest.notificationFeatureEnabled = notificationFeatureEnabled + and: 'a CM resource address' + def cmResourceAddress = new CmResourceAddress('ds', 'ch1', 'resource1') + and: 'the (mocked) service when called with the correct parameters (with or without topic) returns a response from dmi' + def dmiResponse = Mono.justOrEmpty(new ResponseEntity('dmi response',I_AM_A_TEAPOT)) + dmiDataOperations.getResourceDataFromDmi(cmResourceAddress, 'options', NO_TOPIC, _, NO_AUTH_HEADER) >> dmiResponse + dmiDataOperations.getResourceDataFromDmi(cmResourceAddress, 'options', topic, _, NO_AUTH_HEADER) >> dmiResponse + when: 'get request is executed with topic = #topic' + def response = objectUnderTest.executeRequest(cmResourceAddress, 'options', topic, false, NO_AUTH_HEADER) + then: 'a successful result with/without request id is returned' + if (expectSynchronousResponse) { + assert response == 'dmi response' + } else { // expect request id in a map + assert response.keySet()[0] == 'requestId' + } + where: 'the following parameters are used' + scenario | notificationFeatureEnabled | topic || expectSynchronousResponse + 'feature on, valid topic' | true | 'valid' || false + 'feature on, no topic' | true | null || true + 'feature off, valid topic' | false | 'valid' || true + 'feature off, no topic' | false | null || true + } + + def 'Attempt to execute async data operation request with feature #scenario.'() { + given: 'a extended request handler that supports bulk requests' + def objectUnderTest = new NcmpPassthroughResourceRequestHandler(dmiDataOperations) + and: 'notification feature is turned on/off' + objectUnderTest.notificationFeatureEnabled = notificationFeatureEnabled + when: 'data operation request is executed' + def dataOperationDefinition = new DataOperationDefinition(operation: 'read', datastore: 'ncmp-datastore:passthrough-running', cmHandleIds: ['ch']) + def result = objectUnderTest.executeAsynchronousRequest('someTopic', new DataOperationRequest(dataOperationDefinitions:[dataOperationDefinition]), NO_AUTH_HEADER) + then: 'the task is executed in an async fashion or not' + expectedCalls * dmiDataOperations.requestResourceDataFromDmi('someTopic', _, _, NO_AUTH_HEADER) + and: + result.keySet()[0] == expectedKeyInMap + where: 'the following parameters are used' + scenario | notificationFeatureEnabled || expectedCalls || expectedKeyInMap + 'on' | true || 1 || 'requestId' + 'off' | false || 0 || 'status' + } + + def 'Attempt to execute async data operation request with error #scenario'() { + given: 'a data operation definition with datastore: #datastore' + def dataOperationDefinition = new DataOperationDefinition(operation: 'read', datastore: datastore) + when: 'data operation request is executed' + def dataOperationRequest = new DataOperationRequest(dataOperationDefinitions: [dataOperationDefinition]) + objectUnderTest.executeAsynchronousRequest('myTopic', dataOperationRequest, NO_AUTH_HEADER) + then: 'the correct error is thrown' + def thrown = thrown(InvalidDatastoreException) + assert thrown.message.contains(expectedErrorMessage) + where: 'the following datastore names are used' + scenario | datastore || expectedErrorMessage + 'unsupported datastore' | 'ncmp-datastore:operational' || 'not supported' + 'invalid datastore' | 'invalid' || 'invalid datastore name' + } + + def 'Attempt to execute async data operation request with #scenario operation: #operation.'() { + given: 'a data operation definition with operation: #operation' + def dataOperationDefinition = new DataOperationDefinition(operation: operation, datastore: 'ncmp-datastore:passthrough-running') + when: 'data operation request is executed' + objectUnderTest.executeAsynchronousRequest('someTopic', new DataOperationRequest(dataOperationDefinitions:[dataOperationDefinition]), NO_AUTH_HEADER) + then: 'the expected type of exception is thrown' + thrown(expectedException) + where: 'the following operations are used' + scenario | operation || expectedException + 'invalid' | 'invalid' || InvalidOperationException + 'unsupported' | 'create' || OperationNotSupportedException + 'unsupported' | 'update' || OperationNotSupportedException + 'unsupported' | 'patch' || OperationNotSupportedException + 'unsupported' | 'delete' || OperationNotSupportedException + } + + def 'Attempt to execute async data operation request with too many cm handles.'() { + given: 'a data operation definition with too many cm handles' + def tooMany = objectUnderTest.MAXIMUM_CM_HANDLES_PER_OPERATION + 1 + def cmHandleIds = new String[tooMany] + def dataOperationDefinition = new DataOperationDefinition(operationId: 'abc', operation: 'read', datastore: 'ncmp-datastore:passthrough-running', cmHandleIds: cmHandleIds) + when: 'data operation request is executed' + objectUnderTest.executeAsynchronousRequest('someTopic', new DataOperationRequest(dataOperationDefinitions:[dataOperationDefinition]), NO_AUTH_HEADER) + then: 'a payload too large exception is thrown' + def exceptionThrown = thrown(PayloadTooLargeException) + and: 'the error message contains the offending number of cm handles' + assert exceptionThrown.message == "Operation 'abc' affects too many (${tooMany}) cm handles" + } + +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/NetworkCmProxyFacadeSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/NetworkCmProxyFacadeSpec.groovy new file mode 100644 index 000000000..f4e449904 --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/NetworkCmProxyFacadeSpec.groovy @@ -0,0 +1,108 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2021-2024 Nordix Foundation + * Modifications Copyright (C) 2021 Pantheon.tech + * Modifications Copyright (C) 2021-2022 Bell Canada + * Modifications Copyright (C) 2023 TechMahindra Ltd. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.impl.data + + +import org.onap.cps.ncmp.api.data.models.CmResourceAddress +import org.onap.cps.ncmp.api.data.models.DataOperationRequest +import org.onap.cps.spi.model.DataNode +import reactor.core.publisher.Mono +import spock.lang.Specification + +import static org.onap.cps.ncmp.api.data.models.DatastoreType.OPERATIONAL +import static org.onap.cps.ncmp.api.data.models.DatastoreType.PASSTHROUGH_OPERATIONAL +import static org.onap.cps.ncmp.api.data.models.DatastoreType.PASSTHROUGH_RUNNING +import static org.onap.cps.ncmp.api.data.models.OperationType.CREATE +import static org.onap.cps.ncmp.api.data.models.OperationType.UPDATE + +class NetworkCmProxyFacadeSpec extends Specification { + + def mockDmiDataOperations = Mock(DmiDataOperations) + def mockNcmpCachedResourceRequestHandler = Mock(NcmpCachedResourceRequestHandler) + def mockNcmpPassthroughResourceRequestHandler = Mock(NcmpPassthroughResourceRequestHandler) + + def objectUnderTest = new NetworkCmProxyFacade(mockNcmpCachedResourceRequestHandler, mockNcmpPassthroughResourceRequestHandler, mockDmiDataOperations) + + def NO_TOPIC = null + + def 'Execute Data Operation for CM Handles (delegation).'() { + given: 'a data operation request' + def dataOperationRequest = Mock(DataOperationRequest) + and: ' a response from the (mocked) pass-through request handler for the given parameters' + def responseFromHandler = [attr:'value'] + mockNcmpPassthroughResourceRequestHandler.executeAsynchronousRequest('topic', dataOperationRequest, 'authorization') >> responseFromHandler + expect: 'the response form the handler' + assert objectUnderTest.executeDataOperationForCmHandles('topic', dataOperationRequest, 'authorization') == responseFromHandler + } + + def 'Query Resource Data for cm handle (delegation).'() { + given: 'a response from the (mocked) cached data handler for the given parameters' + def responseFromHandler = [Mock(DataNode)] + mockNcmpCachedResourceRequestHandler.executeRequest('ch-1', 'some cps path', true) >> responseFromHandler + expect: 'the response form the handler' + assert objectUnderTest.queryResourceDataForCmHandle('ch-1','some cps path',true) == responseFromHandler + } + + def 'Choosing Data Request Handler.'() { + expect: '(a mock of) #expectedHandler' + assert objectUnderTest.getNcmpDatastoreRequestHandler(datastore.datastoreName).class.name.startsWith(expectedHandler.name) + where: + datastore || expectedHandler + OPERATIONAL || NcmpCachedResourceRequestHandler.class + PASSTHROUGH_RUNNING || NcmpPassthroughResourceRequestHandler.class + PASSTHROUGH_OPERATIONAL || NcmpPassthroughResourceRequestHandler.class + } + + def 'Write resource data for pass-through running from DMI using POST (delegation).'() { + when: 'write resource data is called' + objectUnderTest.writeResourceDataPassThroughRunningForCmHandle('testCmHandle', + 'testResourceId', CREATE, + '{some-json}', 'application/json', null) + then: 'DMI called with correct data' + 1 * mockDmiDataOperations.writeResourceDataPassThroughRunningFromDmi('testCmHandle', 'testResourceId', CREATE, '{some-json}', 'application/json', null) + } + + def 'Get resource data from DMI (delegation).'() { + given: 'a cm resource address for datastore operational' + def cmResourceAddress = new CmResourceAddress('ncmp-datastore:operational', 'some CM Handle', 'some resource Id') + and: 'get resource data from DMI is called' + mockNcmpCachedResourceRequestHandler.executeRequest(cmResourceAddress, 'options', NO_TOPIC, false, 'authorization') >> + Mono.just('dmi response') + when: 'get resource data operational for the given cm resource address is called' + def response = objectUnderTest.getResourceDataForCmHandle(cmResourceAddress, 'options', NO_TOPIC, false, 'authorization').block() + then: 'DMI returns a json response' + assert response == 'dmi response' + } + + def 'Update resource data for pass-through running from dmi (delegation).'() { + when: 'get resource data is called' + objectUnderTest.writeResourceDataPassThroughRunningForCmHandle('testCmHandle', + 'testResourceId', UPDATE, + '{some-json}', 'application/json', 'authorization') + then: 'DMI called with correct data' + 1 * mockDmiDataOperations.writeResourceDataPassThroughRunningFromDmi('testCmHandle', 'testResourceId', UPDATE, '{some-json}', 'application/json', 'authorization') + } + + +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyQueryServiceImplSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/NetworkCmProxyQueryServiceImplSpec.groovy index 5f4f248c6..6d0cf84e2 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyQueryServiceImplSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/NetworkCmProxyQueryServiceImplSpec.groovy @@ -18,16 +18,16 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl - -import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME -import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR +package org.onap.cps.ncmp.impl.data import org.onap.cps.api.CpsQueryService import org.onap.cps.spi.FetchDescendantsOption import org.onap.cps.spi.model.DataNode import spock.lang.Specification +import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR +import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME + class NetworkCmProxyQueryServiceImplSpec extends Specification { def mockCpsQueryService = Mock(CpsQueryService) diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/async/CpsAsyncRequestResponseEventIntegrationSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/async/CpsAsyncRequestResponseEventIntegrationSpec.groovy index f646ee5bf..ad7d741ae 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/async/CpsAsyncRequestResponseEventIntegrationSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/async/CpsAsyncRequestResponseEventIntegrationSpec.groovy @@ -18,7 +18,7 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.async +package org.onap.cps.ncmp.impl.data.async import com.fasterxml.jackson.databind.ObjectMapper import org.apache.kafka.clients.consumer.KafkaConsumer @@ -35,6 +35,7 @@ import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.context.SpringBootTest import org.springframework.test.annotation.DirtiesContext import org.testcontainers.spock.Testcontainers + import java.time.Duration @SpringBootTest(classes = [EventsPublisher, AsyncRestRequestResponseEventConsumer, ObjectMapper, JsonObjectMapper]) diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/async/DataOperationEventConsumerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/async/DataOperationEventConsumerSpec.groovy index 369b496ca..7e9f5089b 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/async/DataOperationEventConsumerSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/async/DataOperationEventConsumerSpec.groovy @@ -18,14 +18,14 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.async +package org.onap.cps.ncmp.impl.data.async import com.fasterxml.jackson.databind.ObjectMapper import io.cloudevents.CloudEvent +import io.cloudevents.core.builder.CloudEventBuilder import io.cloudevents.kafka.CloudEventDeserializer import io.cloudevents.kafka.CloudEventSerializer import io.cloudevents.kafka.impl.KafkaHeaders -import io.cloudevents.core.builder.CloudEventBuilder import org.apache.kafka.clients.consumer.ConsumerRecord import org.apache.kafka.clients.consumer.KafkaConsumer import org.apache.kafka.common.header.internals.RecordHeaders @@ -40,9 +40,10 @@ import org.springframework.boot.test.context.SpringBootTest import org.springframework.kafka.listener.adapter.RecordFilterStrategy import org.springframework.test.annotation.DirtiesContext import org.testcontainers.spock.Testcontainers + import java.time.Duration -import static org.onap.cps.ncmp.api.impl.events.mapper.CloudEventMapper.toTargetEvent +import static org.onap.cps.ncmp.utils.events.CloudEventMapper.toTargetEvent @SpringBootTest(classes = [EventsPublisher, DataOperationEventConsumer, RecordFilterStrategies, JsonObjectMapper, ObjectMapper]) @Testcontainers diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/async/FilterStrategiesIntegrationSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/async/FilterStrategiesIntegrationSpec.groovy index fba1f953f..0643dbcf1 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/async/FilterStrategiesIntegrationSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/async/FilterStrategiesIntegrationSpec.groovy @@ -18,7 +18,7 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.async +package org.onap.cps.ncmp.impl.data.async import io.cloudevents.core.builder.CloudEventBuilder import org.onap.cps.events.EventsPublisher @@ -32,6 +32,7 @@ import org.springframework.boot.test.context.SpringBootTest import org.springframework.test.annotation.DirtiesContext import org.testcontainers.spock.Testcontainers import spock.util.concurrent.PollingConditions + import java.util.concurrent.TimeUnit @SpringBootTest(classes =[DataOperationEventConsumer, AsyncRestRequestResponseEventConsumer, RecordFilterStrategies, KafkaConfig]) diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/async/NcmpAsyncRequestResponseEventMapperSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/async/NcmpAsyncRequestResponseEventMapperSpec.groovy index 07e9b49ff..c7f094b1e 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/async/NcmpAsyncRequestResponseEventMapperSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/async/NcmpAsyncRequestResponseEventMapperSpec.groovy @@ -18,7 +18,7 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.async +package org.onap.cps.ncmp.impl.data.async import org.mapstruct.factory.Mappers import org.onap.cps.ncmp.event.model.DmiAsyncRequestResponseEvent diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/async/RecordFilterStrategiesSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/async/RecordFilterStrategiesSpec.groovy index 4189a8b38..5ff2a3b55 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/async/RecordFilterStrategiesSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/async/RecordFilterStrategiesSpec.groovy @@ -1,8 +1,8 @@ -package org.onap.cps.ncmp.api.impl.async +package org.onap.cps.ncmp.impl.data.async -import spock.lang.Specification import org.apache.kafka.common.header.Header import org.apache.kafka.common.header.Headers +import spock.lang.Specification import java.nio.charset.Charset diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/async/SerializationIntegrationSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/async/SerializationIntegrationSpec.groovy index ee8933300..94ebc38a4 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/async/SerializationIntegrationSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/async/SerializationIntegrationSpec.groovy @@ -18,7 +18,7 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.async +package org.onap.cps.ncmp.impl.data.async import com.fasterxml.jackson.databind.ObjectMapper import io.cloudevents.core.builder.CloudEventBuilder diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/data/operation/ResourceDataOperationRequestUtilsSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/utils/DmiDataOperationsHelperSpec.groovy index 8df27bb62..cdf9eae40 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/data/operation/ResourceDataOperationRequestUtilsSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/utils/DmiDataOperationsHelperSpec.groovy @@ -18,7 +18,7 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.utils.data.operation +package org.onap.cps.ncmp.impl.data.utils import com.fasterxml.jackson.databind.ObjectMapper import io.cloudevents.CloudEvent @@ -26,27 +26,28 @@ import io.cloudevents.kafka.CloudEventDeserializer import io.cloudevents.kafka.impl.KafkaHeaders import org.apache.kafka.clients.consumer.KafkaConsumer import org.onap.cps.events.EventsPublisher -import org.onap.cps.ncmp.api.NcmpResponseStatus +import org.onap.cps.ncmp.api.data.models.DataOperationRequest +import org.onap.cps.ncmp.api.data.models.OperationType import org.onap.cps.ncmp.api.impl.utils.context.CpsApplicationContext -import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle -import org.onap.cps.ncmp.api.impl.inventory.CmHandleState -import org.onap.cps.ncmp.api.impl.inventory.CompositeStateBuilder +import org.onap.cps.ncmp.api.inventory.models.CompositeStateBuilder import org.onap.cps.ncmp.api.kafka.MessagingBaseSpec -import org.onap.cps.ncmp.api.models.DataOperationRequest import org.onap.cps.ncmp.events.async1_0_0.DataOperationEvent +import org.onap.cps.ncmp.impl.data.models.DmiDataOperation +import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle import org.onap.cps.ncmp.utils.TestUtils import org.onap.cps.utils.JsonObjectMapper import org.spockframework.spring.SpringBean -import org.springframework.beans.factory.annotation.Autowired import org.springframework.test.context.ContextConfiguration +import org.springframework.util.LinkedMultiValueMap import java.time.Duration -import java.util.concurrent.TimeoutException -import static org.onap.cps.ncmp.api.impl.events.mapper.CloudEventMapper.toTargetEvent +import static org.onap.cps.ncmp.impl.inventory.models.CmHandleState.ADVISED +import static org.onap.cps.ncmp.impl.inventory.models.CmHandleState.READY +import static org.onap.cps.ncmp.utils.events.CloudEventMapper.toTargetEvent -@ContextConfiguration(classes = [EventsPublisher, CpsApplicationContext, ObjectMapper]) -class ResourceDataOperationRequestUtilsSpec extends MessagingBaseSpec { +@ContextConfiguration(classes = [EventsPublisher, CpsApplicationContext]) +class DmiDataOperationsHelperSpec extends MessagingBaseSpec { def static clientTopic = 'my-topic-name' def static dataOperationType = 'org.onap.cps.ncmp.events.async1_0_0.DataOperationEvent' @@ -57,9 +58,6 @@ class ResourceDataOperationRequestUtilsSpec extends MessagingBaseSpec { @SpringBean EventsPublisher eventPublisher = new EventsPublisher<CloudEvent>(legacyEventKafkaTemplate, cloudEventKafkaTemplate) - @Autowired - ObjectMapper objectMapper - def 'Process per data operation request with #serviceName.'() { given: 'data operation request with 3 operations' def dataOperationRequestJsonData = TestUtils.getResourceFileContent('dataOperationRequest.json') @@ -67,7 +65,7 @@ class ResourceDataOperationRequestUtilsSpec extends MessagingBaseSpec { and: '4 known cm handles: ch1-dmi1, ch2-dmi1, ch3-dmi2, ch4-dmi2' def yangModelCmHandles = getYangModelCmHandles() when: 'data operation request is processed' - def operationsOutPerDmiServiceName = ResourceDataOperationRequestUtils.processPerDefinitionInDataOperationsRequest(clientTopic,'request-id', dataOperationRequest, yangModelCmHandles) + def operationsOutPerDmiServiceName = DmiDataOperationsHelper.processPerDefinitionInDataOperationsRequest(clientTopic,'request-id', dataOperationRequest, yangModelCmHandles) and: 'converted to a json node' def dmiDataOperationRequestBody = jsonObjectMapper.asJsonString(operationsOutPerDmiServiceName.get(serviceName)) def dmiDataOperationRequestBodyAsJsonNode = jsonObjectMapper.convertToJsonNode(dmiDataOperationRequestBody).get(operationIndex) @@ -90,6 +88,23 @@ class ResourceDataOperationRequestUtilsSpec extends MessagingBaseSpec { 'dmi2' | 2 || 'operational-15' | 'ncmp-datastore:passthrough-operational' | ['ch4-dmi2'] } + def 'Process one data operation request with #serviceName and Module Set Tag set.'() { + given: 'data operation request' + def dataOperationRequestJsonData = TestUtils.getResourceFileContent('dataOperationRequest.json') + def dataOperationRequest = jsonObjectMapper.convertJsonString(dataOperationRequestJsonData, DataOperationRequest.class) + and: '1 known cm handles: ch1-dmi1' + def yangModelCmHandles = getYangModelCmHandlesForOneCmHandle() + when: 'data operation request is processed' + def operationsOutPerDmiServiceName = DmiDataOperationsHelper.processPerDefinitionInDataOperationsRequest(clientTopic,'request-id', dataOperationRequest, yangModelCmHandles) + and: 'converted to a json node' + def dmiDataOperationRequestBody = operationsOutPerDmiServiceName['dmi1'] + def cmHandlesInRequestBody = dmiDataOperationRequestBody[0].cmHandles + then: 'it contains the correct operation details' + assert cmHandlesInRequestBody.size() == 1 + assert cmHandlesInRequestBody[0].id == 'ch1-dmi1' + assert cmHandlesInRequestBody[0].moduleSetTag == 'module-set-tag1' + } + def 'Process per data operation request with non-ready, non-existing cm handle and publish event to client specified topic'() { given: 'consumer subscribing to client topic' def cloudEventKafkaConsumer = new KafkaConsumer<>(eventConsumerConfigProperties('test-1', CloudEventDeserializer)) @@ -98,7 +113,7 @@ class ResourceDataOperationRequestUtilsSpec extends MessagingBaseSpec { def dataOperationRequestJsonData = TestUtils.getResourceFileContent('dataOperationRequest.json') def dataOperationRequest = jsonObjectMapper.convertJsonString(dataOperationRequestJsonData, DataOperationRequest.class) when: 'data operation request is processed' - ResourceDataOperationRequestUtils.processPerDefinitionInDataOperationsRequest(clientTopic, 'request-id', dataOperationRequest, yangModelCmHandles) + DmiDataOperationsHelper.processPerDefinitionInDataOperationsRequest(clientTopic, 'request-id', dataOperationRequest, yangModelCmHandles) and: 'subscribed client specified topic is polled and first record is selected' def consumerRecordOut = cloudEventKafkaConsumer.poll(Duration.ofMillis(1500)).last() then: 'verify cloud compliant headers' @@ -118,34 +133,10 @@ class ResourceDataOperationRequestUtilsSpec extends MessagingBaseSpec { jsonObjectMapper.asJsonString(dataOperationResponseEvent.data.responses) == dataOperationResponseEventJson } - def 'Publish error response for entire data operations request when async task fails'() { - given: 'consumer subscribing to client topic' - def cloudEventKafkaConsumer = new KafkaConsumer<>(eventConsumerConfigProperties(consumerGroupId, CloudEventDeserializer)) - cloudEventKafkaConsumer.subscribe([clientTopic]) - and: 'data operation request having non-ready and non-existing cm handle ids' - def dataOperationRequestJsonData = TestUtils.getResourceFileContent('dataOperationRequest.json') - def dataOperationRequest = jsonObjectMapper.convertJsonString(dataOperationRequestJsonData, DataOperationRequest.class) - when: 'an error occurs for the entire data operations request' - ResourceDataOperationRequestUtils.handleAsyncTaskCompletionForDataOperationsRequest(clientTopic, 'request-id', dataOperationRequest, exceptionThrown) - and: 'subscribed client specified topic is polled and first record is selected' - def consumerRecordOut = cloudEventKafkaConsumer.poll(Duration.ofMillis(1500)).last() - def dataOperationResponseEvent = toTargetEvent(consumerRecordOut.value(), DataOperationEvent.class) - then: 'data operation response event response size is 3' - dataOperationResponseEvent.data.responses.size() == 3 - and: 'all 3 have the expected error code' - dataOperationResponseEvent.data.responses.each { - assert it.statusCode == errorReportedToClientTopic.code - } - where: - scenario | exceptionThrown | consumerGroupId || errorReportedToClientTopic - 'task timed out' | new TimeoutException() | 'test-2' || NcmpResponseStatus.DMI_SERVICE_NOT_RESPONDING - 'unspecified error' | new RuntimeException() | 'test-3' || NcmpResponseStatus.UNKNOWN_ERROR - } - static def getYangModelCmHandles() { def dmiProperties = [new YangModelCmHandle.Property('prop', 'some DMI property')] - def readyState = new CompositeStateBuilder().withCmHandleState(CmHandleState.READY).withLastUpdatedTimeNow().build() - def advisedState = new CompositeStateBuilder().withCmHandleState(CmHandleState.ADVISED).withLastUpdatedTimeNow().build() + def readyState = new CompositeStateBuilder().withCmHandleState(READY).withLastUpdatedTimeNow().build() + def advisedState = new CompositeStateBuilder().withCmHandleState(ADVISED).withLastUpdatedTimeNow().build() return [new YangModelCmHandle(id: 'ch1-dmi1', dmiServiceName: 'dmi1', dmiProperties: dmiProperties, compositeState: readyState), new YangModelCmHandle(id: 'ch2-dmi1', dmiServiceName: 'dmi1', dmiProperties: dmiProperties, compositeState: readyState), new YangModelCmHandle(id: 'ch6-dmi1', dmiServiceName: 'dmi1', dmiProperties: dmiProperties, compositeState: readyState), @@ -156,4 +147,19 @@ class ResourceDataOperationRequestUtilsSpec extends MessagingBaseSpec { new YangModelCmHandle(id: 'non-ready-cm-handle', dmiServiceName: 'dmi2', dmiProperties: dmiProperties, compositeState: advisedState) ] } + + static def getYangModelCmHandlesForOneCmHandle() { + def dmiProperties = [new YangModelCmHandle.Property('prop', 'some DMI property')] + def readyState = new CompositeStateBuilder().withCmHandleState(READY).withLastUpdatedTimeNow().build() + return [new YangModelCmHandle(id: 'ch1-dmi1', dmiServiceName: 'dmi1', moduleSetTag: 'module-set-tag1', dmiProperties: dmiProperties, compositeState: readyState)] + } + + def mockAndPopulateErrorMap(errorReportedToClientTopic) { + def dmiDataOperation = DmiDataOperation.builder().operation(OperationType.fromOperationName('read')) + .operationId('some-op-id').datastore('ncmp-datastore:passthrough-operational') + .options('some-option').resourceIdentifier('some-resource-identifier').build() + def cmHandleIdsPerResponseCodesPerOperation = new LinkedMultiValueMap<>() + cmHandleIdsPerResponseCodesPerOperation.add(dmiDataOperation, Map.of(errorReportedToClientTopic, ['some-cm-handle-id'])) + return cmHandleIdsPerResponseCodesPerOperation + } } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/AlternateIdCheckerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/AlternateIdCheckerSpec.groovy index 1e843676b..b086e58de 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/AlternateIdCheckerSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/AlternateIdCheckerSpec.groovy @@ -18,11 +18,10 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.utils +package org.onap.cps.ncmp.impl.inventory -import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence -import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle -import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle +import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle +import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle import org.onap.cps.spi.exceptions.DataNodeNotFoundException import org.onap.cps.spi.model.DataNode import org.onap.cps.spi.model.DataNodeBuilder diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/RestQueryParametersValidatorSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleQueryParametersValidatorSpec.groovy index dc471e64f..15526a0c8 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/RestQueryParametersValidatorSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleQueryParametersValidatorSpec.groovy @@ -18,21 +18,20 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.utils +package org.onap.cps.ncmp.impl.inventory -import org.onap.cps.ncmp.api.models.CmHandleQueryServiceParameters +import org.onap.cps.ncmp.api.inventory.models.CmHandleQueryServiceParameters import org.onap.cps.spi.exceptions.DataValidationException import org.onap.cps.spi.model.ConditionProperties import spock.lang.Specification -class RestQueryParametersValidatorSpec extends Specification { - +class CmHandleQueryParametersValidatorSpec extends Specification { def 'CM Handle Query validation: empty query.'() { given: 'a cm handle query' def cmHandleQueryParameters = new CmHandleQueryServiceParameters() when: 'validator is invoked' - RestQueryParametersValidator.validateCmHandleQueryParameters(cmHandleQueryParameters, []) + CmHandleQueryParametersValidator.validateCmHandleQueryParameters(cmHandleQueryParameters, []) then: 'data validation exception is not thrown' noExceptionThrown() } @@ -45,7 +44,7 @@ class RestQueryParametersValidatorSpec extends Specification { condition.conditionParameters = [['key':'value']] cmHandleQueryParameters.cmHandleQueryParameters = [condition] when: 'validator is invoked' - RestQueryParametersValidator.validateCmHandleQueryParameters(cmHandleQueryParameters, ['validConditionName']) + CmHandleQueryParametersValidator.validateCmHandleQueryParameters(cmHandleQueryParameters, ['validConditionName']) then: 'data validation exception is not thrown' noExceptionThrown() } @@ -58,17 +57,17 @@ class RestQueryParametersValidatorSpec extends Specification { condition.conditionParameters = conditionParameters cmHandleQueryParameters.cmHandleQueryParameters = [condition] when: 'validator is invoked' - RestQueryParametersValidator.validateCmHandleQueryParameters(cmHandleQueryParameters, ['validConditionName']) + CmHandleQueryParametersValidator.validateCmHandleQueryParameters(cmHandleQueryParameters, ['validConditionName']) then: 'a data validation exception is thrown' def thrown = thrown(DataValidationException) and: 'the exception details contain the correct significant term ' - thrown.details.contains(expectedWordInDetails) + assert thrown.details.contains(expectedWordInDetails) where: scenario | conditionName | conditionParameters || expectedWordInDetails 'unknown condition name' | 'unknownCondition' | [['key': 'value']] || 'conditionName' 'no condition name' | '' | [['key': 'value']] || 'conditionName' + 'empty conditions' | 'validConditionName' | [] || 'conditionsParameters' 'empty properties' | 'validConditionName' | [[:]] || 'conditionsParameter' - 'empty conditions' | 'validConditionName' | [[:]] || 'conditionsParameter' 'too many properties' | 'validConditionName' | [[key1: 'value1', key2: 'value2']] || 'conditionsParameter' 'empty key' | 'validConditionName' | [['': 'wrong']] || 'conditionsParameter' } @@ -77,14 +76,14 @@ class RestQueryParametersValidatorSpec extends Specification { given: 'a condition property' def conditionProperty = [moduleName: 'value'] when: 'validator is invoked' - RestQueryParametersValidator.validateModuleNameConditionProperties(conditionProperty) + CmHandleQueryParametersValidator.validateModuleNameConditionProperties(conditionProperty) then: 'data validation exception is not thrown' noExceptionThrown() } def 'CM Handle Query validation: validate module name condition properties - #scenario.'() { when: 'validator is invoked' - RestQueryParametersValidator.validateModuleNameConditionProperties(conditionProperty) + CmHandleQueryParametersValidator.validateModuleNameConditionProperties(conditionProperty) then: 'a data validation exception is thrown' thrown(DataValidationException) where: @@ -95,7 +94,7 @@ class RestQueryParametersValidatorSpec extends Specification { def 'Validate CmHandle where an exception is thrown due to #scenario.'() { when: 'the validator is called on a cps path condition property' - RestQueryParametersValidator.validateCpsPathConditionProperties(conditionProperty) + CmHandleQueryParametersValidator.validateCpsPathConditionProperties(conditionProperty) then: 'a data validation exception is thrown' def e = thrown(DataValidationException) and: 'exception message matches the expected message' @@ -109,12 +108,12 @@ class RestQueryParametersValidatorSpec extends Specification { def 'No conditions.'() { expect: 'no conditions always returns true' - RestQueryParametersValidator.validateCpsPathConditionProperties([:]) == true + CmHandleQueryParametersValidator.validateCpsPathConditionProperties([:]) == true } def 'Validate CmHandle where #scenario.'() { when: 'the validator is called on a cps path condition property' - def result = RestQueryParametersValidator.validateCpsPathConditionProperties(['cpsPath':cpsPath]) + def result = CmHandleQueryParametersValidator.validateCpsPathConditionProperties(['cpsPath':cpsPath]) then: 'the expected boolean value is returned' result == expectedBoolean where: diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/CmHandleQueriesImplSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleQueryServiceImplSpec.groovy index 2f9d26494..36fd75577 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/CmHandleQueriesImplSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleQueryServiceImplSpec.groovy @@ -19,21 +19,23 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.inventory +package org.onap.cps.ncmp.impl.inventory -import org.onap.cps.ncmp.api.impl.trustlevel.TrustLevel -import org.onap.cps.spi.utils.CpsValidator -import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DATASPACE_NAME -import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR -import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DMI_REGISTRY_PARENT -import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS -import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS +import org.onap.cps.ncmp.api.inventory.models.TrustLevel +import org.onap.cps.ncmp.impl.inventory.models.CmHandleState import org.onap.cps.spi.CpsDataPersistenceService import org.onap.cps.spi.model.DataNode +import org.onap.cps.spi.utils.CpsValidator import spock.lang.Shared import spock.lang.Specification -class CmHandleQueriesImplSpec extends Specification { +import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DATASPACE_NAME +import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR +import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DMI_REGISTRY_PARENT +import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS +import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS + +class CmHandleQueryServiceImplSpec extends Specification { def mockCpsDataPersistenceService = Mock(CpsDataPersistenceService) @@ -43,7 +45,7 @@ class CmHandleQueriesImplSpec extends Specification { def mockCpsValidator = Mock(CpsValidator) - def objectUnderTest = new CmHandleQueriesImpl(mockCpsDataPersistenceService, trustLevelPerDmiPlugin, trustLevelPerCmHandle, mockCpsValidator) + def objectUnderTest = new CmHandleQueryServiceImpl(mockCpsDataPersistenceService, trustLevelPerDmiPlugin, trustLevelPerCmHandle, mockCpsValidator) @Shared def static sampleDataNodes = [new DataNode()] diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServicePropertyHandlerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationServicePropertyHandlerSpec.groovy index 260772714..1beab20de 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServicePropertyHandlerSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationServicePropertyHandlerSpec.groovy @@ -2,7 +2,7 @@ * ============LICENSE_START======================================================= * Copyright (C) 2022-2024 Nordix Foundation * Modifications Copyright (C) 2022 Bell Canada - * Modifications Copyright (C) 2023 TechMahindra Ltd. + * Modifications Copyright (C) 2024 TechMahindra Ltd. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,7 +20,7 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl +package org.onap.cps.ncmp.impl.inventory import ch.qos.logback.classic.Level import ch.qos.logback.classic.Logger @@ -28,13 +28,12 @@ import ch.qos.logback.classic.spi.ILoggingEvent import ch.qos.logback.core.read.ListAppender import com.fasterxml.jackson.databind.ObjectMapper import org.onap.cps.api.CpsDataService -import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence -import org.onap.cps.ncmp.api.impl.utils.AlternateIdChecker -import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle +import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle import org.onap.cps.spi.exceptions.DataNodeNotFoundException import org.onap.cps.spi.exceptions.DataValidationException import org.onap.cps.spi.model.DataNode import org.onap.cps.spi.model.DataNodeBuilder +import org.onap.cps.utils.ContentType import org.onap.cps.utils.JsonObjectMapper import org.slf4j.LoggerFactory import spock.lang.Specification @@ -42,22 +41,22 @@ import spock.lang.Specification import static org.onap.cps.ncmp.api.NcmpResponseStatus.CM_HANDLES_NOT_FOUND import static org.onap.cps.ncmp.api.NcmpResponseStatus.CM_HANDLE_INVALID_ID import static org.onap.cps.ncmp.api.NcmpResponseStatus.UNKNOWN_ERROR -import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DATASPACE_NAME -import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR -import static org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.Status +import static org.onap.cps.ncmp.api.inventory.models.CmHandleRegistrationResponse.Status +import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DATASPACE_NAME +import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR -class NetworkCmProxyDataServicePropertyHandlerSpec extends Specification { +class CmHandleRegistrationServicePropertyHandlerSpec extends Specification { def mockInventoryPersistence = Mock(InventoryPersistence) def mockCpsDataService = Mock(CpsDataService) def jsonObjectMapper = new JsonObjectMapper(new ObjectMapper()) def mockAlternateIdChecker = Mock(AlternateIdChecker) - def objectUnderTest = new NetworkCmProxyDataServicePropertyHandler(mockInventoryPersistence, mockCpsDataService, jsonObjectMapper, mockAlternateIdChecker) + def objectUnderTest = new CmHandleRegistrationServicePropertyHandler(mockInventoryPersistence, mockCpsDataService, jsonObjectMapper, mockAlternateIdChecker) def logger = Spy(ListAppender<ILoggingEvent>) void setup() { - def setupLogger = ((Logger) LoggerFactory.getLogger(NetworkCmProxyDataServicePropertyHandler.class)) + def setupLogger = ((Logger) LoggerFactory.getLogger(CmHandleRegistrationServicePropertyHandler.class)) setupLogger.addAppender(logger) setupLogger.setLevel(Level.DEBUG) logger.start() @@ -66,7 +65,7 @@ class NetworkCmProxyDataServicePropertyHandlerSpec extends Specification { } void cleanup() { - ((Logger) LoggerFactory.getLogger(NetworkCmProxyDataServicePropertyHandler.class)).detachAndStopAllAppenders() + ((Logger) LoggerFactory.getLogger(CmHandleRegistrationServicePropertyHandler.class)).detachAndStopAllAppenders() } def static cmHandleId = 'myHandle1' @@ -209,7 +208,7 @@ class NetworkCmProxyDataServicePropertyHandlerSpec extends Specification { when: 'cm handle properties is updated' def response = objectUnderTest.updateCmHandleProperties(cmHandleUpdateRequest) then: 'the update is delegated to cps data service with correct parameters' - 1 * mockCpsDataService.updateNodeLeaves('NCMP-Admin', 'ncmp-dmi-registry', '/dmi-registry', _, _) >> + 1 * mockCpsDataService.updateNodeLeaves('NCMP-Admin', 'ncmp-dmi-registry', '/dmi-registry', _, _, ContentType.JSON) >> { args -> assert args[3].contains('alt-1') } @@ -241,12 +240,12 @@ class NetworkCmProxyDataServicePropertyHandlerSpec extends Specification { given: 'an existing cm handle with no data producer identifier' DataNode existingCmHandleDataNode = new DataNode(xpath: cmHandleXpath, leaves: ['id': 'cmHandleId','data-producer-identifier': oldDataProducerIdentifier]) and: 'an update request with a new data producer identifier' - def ncmpServiceCmHandle = new NcmpServiceCmHandle(cmHandleId: cmHandleId, dataProducerIdentifier: 'someDataProducerIdentifier') + def ncmpServiceCmHandle = new NcmpServiceCmHandle(cmHandleId: cmHandleId, dataProducerIdentifier: 'New Data Producer Identifier') when: 'data producer identifier updated' objectUnderTest.updateDataProducerIdentifier(existingCmHandleDataNode, ncmpServiceCmHandle) then: 'the update node leaves method is invoked once' - 1 * mockCpsDataService.updateNodeLeaves('NCMP-Admin', 'ncmp-dmi-registry', '/dmi-registry', _, _) >> { args -> - assert args[3].contains('someDataProducerIdentifier') + 1 * mockCpsDataService.updateNodeLeaves('NCMP-Admin', 'ncmp-dmi-registry', '/dmi-registry', _, _, ContentType.JSON) >> { args -> + assert args[3].contains('New Data Producer Identifier') } and: 'correct information is logged' def lastLoggingEvent = logger.list[0] @@ -258,6 +257,17 @@ class NetworkCmProxyDataServicePropertyHandlerSpec extends Specification { 'blank to something' | '' } + def 'Update CM Handle data producer identifier with same value'() { + given: 'an existing cm handle with no data producer identifier' + DataNode existingCmHandleDataNode = new DataNode(xpath: cmHandleXpath, leaves: ['id': 'cmHandleId','data-producer-identifier': 'same id']) + and: 'an update request with a new data producer identifier' + def ncmpServiceCmHandle = new NcmpServiceCmHandle(cmHandleId: cmHandleId, dataProducerIdentifier: 'same id') + when: 'data producer identifier updated' + objectUnderTest.updateDataProducerIdentifier(existingCmHandleDataNode, ncmpServiceCmHandle) + then: 'the update node leaves method is not invoked' + 0 * mockCpsDataService.updateNodeLeaves(*_) + } + def 'Update CM Handle data producer identifier from some data producer identifier to another data producer identifier'() { given: 'an existing cm handle with a data producer identifier' DataNode existingCmHandleDataNode = new DataNode(xpath: cmHandleXpath, leaves: ['id': 'cmHandleId', 'data-producer-identifier': 'someDataProducerIdentifier']) diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplRegistrationSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationServiceSpec.groovy index fbfbac59e..4ff56312a 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplRegistrationSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationServiceSpec.groovy @@ -19,51 +19,43 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl +package org.onap.cps.ncmp.impl.inventory -import static org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.Status -import static org.onap.cps.ncmp.api.NcmpResponseStatus.CM_HANDLES_NOT_FOUND -import static org.onap.cps.ncmp.api.NcmpResponseStatus.CM_HANDLE_ALREADY_EXIST -import static org.onap.cps.ncmp.api.NcmpResponseStatus.CM_HANDLE_INVALID_ID -import static org.onap.cps.ncmp.api.NcmpResponseStatus.UNKNOWN_ERROR - -import org.onap.cps.ncmp.api.impl.inventory.CompositeState -import org.onap.cps.ncmp.api.impl.trustlevel.TrustLevelManager -import org.onap.cps.ncmp.api.impl.utils.AlternateIdChecker -import org.onap.cps.ncmp.api.models.UpgradedCmHandles -import com.fasterxml.jackson.databind.ObjectMapper import com.hazelcast.map.IMap import org.onap.cps.api.CpsDataService import org.onap.cps.api.CpsModuleService -import org.onap.cps.ncmp.api.NetworkCmProxyCmHandleQueryService -import org.onap.cps.ncmp.api.impl.events.lcm.LcmEventsCmHandleStateHandler import org.onap.cps.ncmp.api.impl.exception.DmiRequestException -import org.onap.cps.ncmp.api.impl.operations.DmiDataOperations -import org.onap.cps.ncmp.api.impl.trustlevel.TrustLevel -import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle -import org.onap.cps.ncmp.api.impl.inventory.CmHandleQueries -import org.onap.cps.ncmp.api.impl.inventory.CmHandleState -import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence -import org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse -import org.onap.cps.ncmp.api.models.DmiPluginRegistration -import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle +import org.onap.cps.ncmp.api.inventory.models.CmHandleRegistrationResponse +import org.onap.cps.ncmp.api.inventory.models.CompositeState +import org.onap.cps.ncmp.api.inventory.models.DmiPluginRegistration +import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle +import org.onap.cps.ncmp.api.inventory.models.TrustLevel +import org.onap.cps.ncmp.api.inventory.models.UpgradedCmHandles +import org.onap.cps.ncmp.impl.inventory.models.CmHandleState +import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle +import org.onap.cps.ncmp.impl.inventory.sync.lcm.LcmEventsCmHandleStateHandler +import org.onap.cps.ncmp.impl.inventory.trustlevel.TrustLevelManager import org.onap.cps.spi.exceptions.AlreadyDefinedException +import org.onap.cps.spi.exceptions.CpsException import org.onap.cps.spi.exceptions.DataNodeNotFoundException import org.onap.cps.spi.exceptions.DataValidationException import org.onap.cps.spi.exceptions.SchemaSetNotFoundException -import org.onap.cps.utils.JsonObjectMapper import spock.lang.Specification -class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification { +import static org.onap.cps.ncmp.api.NcmpResponseStatus.CM_HANDLES_NOT_FOUND +import static org.onap.cps.ncmp.api.NcmpResponseStatus.CM_HANDLE_ALREADY_EXIST +import static org.onap.cps.ncmp.api.NcmpResponseStatus.CM_HANDLE_INVALID_ID +import static org.onap.cps.ncmp.api.NcmpResponseStatus.UNKNOWN_ERROR +import static org.onap.cps.ncmp.api.inventory.models.CmHandleRegistrationResponse.Status +import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME + +class CmHandleRegistrationServiceSpec extends Specification { def ncmpServiceCmHandle = new NcmpServiceCmHandle(cmHandleId: 'some-cm-handle-id') def mockCpsModuleService = Mock(CpsModuleService) - def spiedJsonObjectMapper = Spy(new JsonObjectMapper(new ObjectMapper())) - def mockDmiDataOperations = Mock(DmiDataOperations) - def mockNetworkCmProxyDataServicePropertyHandler = Mock(NetworkCmProxyDataServicePropertyHandler) + def mockNetworkCmProxyDataServicePropertyHandler = Mock(CmHandleRegistrationServicePropertyHandler) def mockInventoryPersistence = Mock(InventoryPersistence) - def mockCmHandleQueries = Mock(CmHandleQueries) - def stubbedNetworkCmProxyCmHandlerQueryService = Stub(NetworkCmProxyCmHandleQueryService) + def mockCmHandleQueries = Mock(CmHandleQueryService) def mockLcmEventsCmHandleStateHandler = Mock(LcmEventsCmHandleStateHandler) def mockCpsDataService = Mock(CpsDataService) def mockModuleSyncStartedOnCmHandles = Mock(IMap<String, Object>) @@ -71,10 +63,9 @@ class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification { def mockTrustLevelManager = Mock(TrustLevelManager) def mockAlternateIdChecker = Mock(AlternateIdChecker) - def objectUnderTest = Spy(new NetworkCmProxyDataServiceImpl(spiedJsonObjectMapper, mockDmiDataOperations, - mockNetworkCmProxyDataServicePropertyHandler, mockInventoryPersistence, mockCmHandleQueries, - stubbedNetworkCmProxyCmHandlerQueryService, mockLcmEventsCmHandleStateHandler, mockCpsDataService, - mockModuleSyncStartedOnCmHandles, trustLevelPerDmiPlugin, mockTrustLevelManager, mockAlternateIdChecker)) + def objectUnderTest = Spy(new CmHandleRegistrationService( + mockNetworkCmProxyDataServicePropertyHandler, mockInventoryPersistence, mockCpsDataService, mockLcmEventsCmHandleStateHandler, + mockModuleSyncStartedOnCmHandles, trustLevelPerDmiPlugin , mockTrustLevelManager, mockAlternateIdChecker)) def setup() { // always accept all cm handles @@ -418,4 +409,46 @@ class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification { 'an unexpected exception' | 'cmhandle' | new RuntimeException('Failed') || UNKNOWN_ERROR | 'Failed' } + def 'Set Cm Handle Data Sync Enabled Flag where data sync flag is #scenario'() { + given: 'an existing cm handle composite state' + def compositeState = new CompositeState(cmHandleState: CmHandleState.READY, dataSyncEnabled: initialDataSyncEnabledFlag, + dataStores: CompositeState.DataStores.builder() + .operationalDataStore(CompositeState.Operational.builder() + .dataStoreSyncState(initialDataSyncState) + .build()).build()) + and: 'get cm handle state returns the composite state for the given cm handle id' + mockInventoryPersistence.getCmHandleState('some-cm-handle-id') >> compositeState + when: 'set data sync enabled is called with the data sync enabled flag set to #dataSyncEnabledFlag' + objectUnderTest.setDataSyncEnabled('some-cm-handle-id', dataSyncEnabledFlag) + then: 'the data sync enabled flag is set to #dataSyncEnabled' + compositeState.dataSyncEnabled == dataSyncEnabledFlag + and: 'the data store sync state is set to #expectedDataStoreSyncState' + compositeState.dataStores.operationalDataStore.dataStoreSyncState == expectedDataStoreSyncState + and: 'the cps data service to delete data nodes is invoked the expected number of times' + deleteDataNodeExpectedNumberOfInvocation * mockCpsDataService.deleteDataNode(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, 'some-cm-handle-id', '/netconf-state', _) + and: 'the inventory persistence service to update node leaves is called with the correct values' + saveCmHandleStateExpectedNumberOfInvocations * mockInventoryPersistence.saveCmHandleState('some-cm-handle-id', compositeState) + where: 'the following data sync enabled flag is used' + scenario | dataSyncEnabledFlag | initialDataSyncEnabledFlag | initialDataSyncState || expectedDataStoreSyncState | deleteDataNodeExpectedNumberOfInvocation | saveCmHandleStateExpectedNumberOfInvocations + 'enabled' | true | false | DataStoreSyncState.NONE_REQUESTED || DataStoreSyncState.UNSYNCHRONIZED | 0 | 1 + 'disabled' | false | true | DataStoreSyncState.UNSYNCHRONIZED || DataStoreSyncState.NONE_REQUESTED | 0 | 1 + 'disabled where sync-state is currently SYNCHRONIZED' | false | true | DataStoreSyncState.SYNCHRONIZED || DataStoreSyncState.NONE_REQUESTED | 1 | 1 + 'is set to existing flag state' | true | true | DataStoreSyncState.UNSYNCHRONIZED || DataStoreSyncState.UNSYNCHRONIZED | 0 | 0 + } + + def 'Set cm Handle Data Sync Enabled flag with following cm handle not in ready state exception' () { + given: 'a cm handle composite state' + def compositeState = new CompositeState(cmHandleState: CmHandleState.ADVISED, dataSyncEnabled: false) + and: 'get cm handle state returns the composite state for the given cm handle id' + mockInventoryPersistence.getCmHandleState('some-cm-handle-id') >> compositeState + when: 'set data sync enabled is called with the data sync enabled flag set to true' + objectUnderTest.setDataSyncEnabled('some-cm-handle-id', true) + then: 'the expected exception is thrown' + thrown(CpsException) + and: 'the inventory persistence service to update node leaves is not invoked' + 0 * mockInventoryPersistence.saveCmHandleState(_, _) + } + + + } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/InventoryPersistenceImplSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/InventoryPersistenceImplSpec.groovy index 9907e9ab2..d58a1dfa0 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/InventoryPersistenceImplSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/InventoryPersistenceImplSpec.groovy @@ -20,24 +20,17 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.inventory - -import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DATASPACE_NAME -import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR -import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DMI_REGISTRY_PARENT -import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME -import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NO_TIMESTAMP -import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS -import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS +package org.onap.cps.ncmp.impl.inventory import com.fasterxml.jackson.databind.ObjectMapper import org.onap.cps.api.CpsAnchorService import org.onap.cps.api.CpsDataService import org.onap.cps.api.CpsModuleService -import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle +import org.onap.cps.ncmp.api.inventory.models.CompositeState +import org.onap.cps.ncmp.impl.inventory.models.CmHandleState +import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle import org.onap.cps.spi.CascadeDeleteAllowed import org.onap.cps.spi.FetchDescendantsOption -import org.onap.cps.ncmp.api.impl.exception.NoAlternateIdParentFoundException import org.onap.cps.spi.exceptions.DataNodeNotFoundException import org.onap.cps.spi.model.DataNode import org.onap.cps.spi.model.ModuleDefinition @@ -46,10 +39,19 @@ import org.onap.cps.spi.utils.CpsValidator import org.onap.cps.utils.JsonObjectMapper import spock.lang.Shared import spock.lang.Specification + import java.time.OffsetDateTime import java.time.ZoneOffset import java.time.format.DateTimeFormatter +import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DATASPACE_NAME +import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR +import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DMI_REGISTRY_PARENT +import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME +import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NO_TIMESTAMP +import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS +import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS + class InventoryPersistenceImplSpec extends Specification { def spiedJsonObjectMapper = Spy(new JsonObjectMapper(new ObjectMapper())) @@ -62,7 +64,7 @@ class InventoryPersistenceImplSpec extends Specification { def mockCpsValidator = Mock(CpsValidator) - def mockCmHandleQueries = Mock(CmHandleQueries) + def mockCmHandleQueries = Mock(CmHandleQueryService) def objectUnderTest = new InventoryPersistenceImpl(spiedJsonObjectMapper, mockCpsDataService, mockCpsModuleService, mockCpsValidator, mockCpsAnchorService, mockCmHandleQueries) @@ -303,41 +305,6 @@ class InventoryPersistenceImplSpec extends Specification { assert objectUnderTest.getCmHandleDataNodeByAlternateId('alternate id') == new DataNode() } - def 'Find cm handle parent data node using alternate ids'() { - given: 'cm handle in the registry with alternateId /a/b' - def matchingCpsPath = "/dmi-registry/cm-handles[@alternate-id='/a/b']" - mockCmHandleQueries.queryNcmpRegistryByCpsPath(matchingCpsPath, OMIT_DESCENDANTS) >> [new DataNode()] - and: 'no other cm handle' - mockCmHandleQueries.queryNcmpRegistryByCpsPath(*_) >> [] - expect: 'querying for alternate id a matching result found' - assert objectUnderTest.getCmHandleDataNodeByLongestMatchAlternateId(alternateId, '/') != null - where: 'the following parameters are used' - scenario | alternateId - 'exact match' | '/a/b' - 'exact match with trailing separator' | '/a/b/' - 'child match' | '/a/b/c' - } - - def 'Find cm handle parent data node using alternate ids mismatches'() { - given: 'cm handle in the registry with alternateId' - def matchingCpsPath = "/dmi-registry/cm-handles[@alternate-id='${cpsPath}]" - mockCmHandleQueries.queryNcmpRegistryByCpsPath(matchingCpsPath, OMIT_DESCENDANTS) >> [new DataNode()] - and: 'no other cm handle' - mockCmHandleQueries.queryNcmpRegistryByCpsPath(*_) >> [] - when: 'attempt to find alternateId' - objectUnderTest.getCmHandleDataNodeByLongestMatchAlternateId(alternateId, '/') - then: 'no alternate id found exception thrown' - def thrown = thrown(NoAlternateIdParentFoundException) - and: 'the exception has the relevant details from the error response' - assert thrown.message == 'No matching (parent) cm handle found using alternate ids' - assert thrown.details == 'cannot find a datanode with alternate id ' + alternateId - where: 'the following parameters are used' - scenario | alternateId | cpsPath - 'no match for parent only' | '/a' | '/a/b' - 'no match at all' | '/x/y/z' | '/a/b' - 'no match with trailing separator' | '/c/d/' | '/c/d' - } - def 'Attempt to get non existing cm handle data node by alternate id'() { given: 'query service is invoked and returns empty collection of data nodes' mockCmHandleQueries.queryNcmpRegistryByCpsPath(*_) >> [] diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/NetworkCmProxyInventoryFacadeSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/NetworkCmProxyInventoryFacadeSpec.groovy new file mode 100644 index 000000000..716efd8fd --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/NetworkCmProxyInventoryFacadeSpec.groovy @@ -0,0 +1,232 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2021-2024 Nordix Foundation + * Modifications Copyright (C) 2021 Pantheon.tech + * Modifications Copyright (C) 2021-2022 Bell Canada + * Modifications Copyright (C) 2023 TechMahindra Ltd. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.impl.inventory + +import com.fasterxml.jackson.databind.ObjectMapper +import org.onap.cps.ncmp.api.inventory.NetworkCmProxyInventoryFacade +import org.onap.cps.ncmp.api.inventory.models.CmHandleQueryApiParameters +import org.onap.cps.ncmp.api.inventory.models.CmHandleQueryServiceParameters +import org.onap.cps.ncmp.api.inventory.models.CompositeState +import org.onap.cps.ncmp.api.inventory.models.ConditionApiProperties +import org.onap.cps.ncmp.api.inventory.models.DmiPluginRegistration +import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle +import org.onap.cps.ncmp.api.inventory.models.TrustLevel +import org.onap.cps.ncmp.impl.inventory.models.CmHandleState +import org.onap.cps.ncmp.impl.inventory.models.LockReasonCategory +import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle +import org.onap.cps.spi.model.ConditionProperties +import org.onap.cps.utils.JsonObjectMapper +import spock.lang.Specification + +class NetworkCmProxyInventoryFacadeSpec extends Specification { + + def mockCmHandleRegistrationService = Mock(CmHandleRegistrationService) + def mockCmHandleQueryService = Mock(CmHandleQueryService) + def mockParameterizedCmHandleQueryService = Mock(ParameterizedCmHandleQueryService) + def spiedJsonObjectMapper = Spy(new JsonObjectMapper(new ObjectMapper())) + def mockInventoryPersistence = Mock(InventoryPersistence) + def trustLevelPerCmHandle = [:] + + def objectUnderTest = new NetworkCmProxyInventoryFacade(mockCmHandleRegistrationService, mockCmHandleQueryService, mockParameterizedCmHandleQueryService, mockInventoryPersistence, spiedJsonObjectMapper, trustLevelPerCmHandle) + + def 'Update DMI Registration'() { + given: 'an (updated) dmi plugin registration' + def dmiPluginRegistration = Mock(DmiPluginRegistration) + when: 'the registration is submitted ' + objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + then: 'the call is delegated to the cm handle registration service' + 1 * mockCmHandleRegistrationService.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + } + + def 'Execute cm handle id search for inventory'() { + given: 'a ConditionApiProperties object' + def conditionProperties = new ConditionProperties() + conditionProperties.conditionName = 'hasAllProperties' + conditionProperties.conditionParameters = [ [ 'some-key' : 'some-value' ] ] + def cmHandleQueryServiceParameters = new CmHandleQueryServiceParameters() + cmHandleQueryServiceParameters.cmHandleQueryParameters = [conditionProperties] as List<ConditionProperties> + and: 'the system returns an set of cmHandle ids' + mockParameterizedCmHandleQueryService.queryCmHandleIdsForInventory(*_) >> [ 'cmHandle1', 'cmHandle2' ] + when: 'executing the search' + def result = objectUnderTest.executeParameterizedCmHandleIdSearch(cmHandleQueryServiceParameters) + then: 'the result returns the correct 2 elements' + assert result.size() == 2 + assert result.contains('cmHandle1') + assert result.contains('cmHandle2') + } + + def 'Get all cm handle IDs by DMI plugin identifier.' () { + given: 'cm handle queries service returns cm handles' + 1 * mockCmHandleQueryService.getCmHandleIdsByDmiPluginIdentifier('some-dmi-plugin-identifier') >> ['cm-handle-1','cm-handle-2'] + when: 'cm handle Ids are requested with dmi plugin identifier' + def result = objectUnderTest.getAllCmHandleIdsByDmiPluginIdentifier('some-dmi-plugin-identifier') + then: 'the result size is correct' + assert result.size() == 2 + and: 'the result returns the correct details' + assert result.containsAll('cm-handle-1','cm-handle-2') + } + + def 'Getting Yang Resources.'() { + when: 'yang resources is called' + objectUnderTest.getYangResourcesModuleReferences('some-cm-handle') + then: 'CPS module services is invoked for the correct dataspace and cm handle' + 1 * mockInventoryPersistence.getYangResourcesModuleReferences('some-cm-handle') + } + + def 'Get a cm handle.'() { + given: 'the system returns a yang modelled cm handle' + def dmiServiceName = 'some service name' + def compositeState = new CompositeState(cmHandleState: CmHandleState.ADVISED, + lockReason: CompositeState.LockReason.builder().lockReasonCategory(LockReasonCategory.MODULE_SYNC_FAILED).details('lock details').build(), + lastUpdateTime: 'some-timestamp', + dataSyncEnabled: false, + dataStores: dataStores()) + def dmiProperties = [new YangModelCmHandle.Property('Book', 'Romance Novel')] + def publicProperties = [new YangModelCmHandle.Property('Public Book', 'Public Romance Novel')] + def moduleSetTag = 'some-module-set-tag' + def alternateId = 'some-alternate-id' + def yangModelCmHandle = new YangModelCmHandle(id: 'ch-1', dmiServiceName: dmiServiceName, dmiProperties: dmiProperties, + publicProperties: publicProperties, compositeState: compositeState, moduleSetTag: moduleSetTag, alternateId: alternateId) + 1 * mockInventoryPersistence.getYangModelCmHandle('ch-1') >> yangModelCmHandle + and: 'a trust level for the cm handle in the cache' + trustLevelPerCmHandle.put('ch-1', TrustLevel.COMPLETE) + when: 'getting cm handle details for a given cm handle id from ncmp service' + def result = objectUnderTest.getNcmpServiceCmHandle('ch-1') + then: 'the result is a ncmpServiceCmHandle' + assert result.class == NcmpServiceCmHandle.class + and: 'the cm handle contains the cm handle id' + assert result.cmHandleId == 'ch-1' + and: 'the cm handle contains the alternate id' + assert result.alternateId == 'some-alternate-id' + and: 'the cm handle contains the module-set-tag' + assert result.moduleSetTag == 'some-module-set-tag' + and: 'the cm handle contains the DMI Properties' + assert result.dmiProperties ==[ Book:'Romance Novel' ] + and: 'the cm handle contains the public Properties' + assert result.publicProperties == [ "Public Book":'Public Romance Novel' ] + and: 'the cm handle contains the cm handle composite state' + assert result.compositeState == compositeState + and: 'the cm handle contains the trust level from the cache' + assert result.currentTrustLevel == TrustLevel.COMPLETE + } + + def 'Get cm handle public properties'() { + given: 'a yang modelled cm handle' + def dmiProperties = [new YangModelCmHandle.Property('prop', 'some DMI property')] + def publicProperties = [new YangModelCmHandle.Property('public prop', 'some public prop')] + def yangModelCmHandle = new YangModelCmHandle(id:'some-cm-handle', dmiServiceName: 'some service name', dmiProperties: dmiProperties, publicProperties: publicProperties) + and: 'the system returns this yang modelled cm handle' + 1 * mockInventoryPersistence.getYangModelCmHandle('some-cm-handle') >> yangModelCmHandle + when: 'getting cm handle public properties for a given cm handle id from ncmp service' + def result = objectUnderTest.getCmHandlePublicProperties('some-cm-handle') + then: 'the result returns the correct data' + assert result == [ 'public prop' : 'some public prop' ] + } + + def 'Get cm handle composite state'() { + given: 'a yang modelled cm handle' + def compositeState = new CompositeState(cmHandleState: CmHandleState.ADVISED, + lockReason: CompositeState.LockReason.builder().lockReasonCategory(LockReasonCategory.MODULE_SYNC_FAILED).details("lock details").build(), + lastUpdateTime: 'some-timestamp', + dataSyncEnabled: false, + dataStores: dataStores()) + def dmiProperties = [new YangModelCmHandle.Property('prop', 'some DMI property')] + def publicProperties = [new YangModelCmHandle.Property('public prop', 'some public prop')] + def yangModelCmHandle = new YangModelCmHandle(id:'some-cm-handle', dmiServiceName: 'some service name', dmiProperties: dmiProperties, publicProperties: publicProperties, compositeState: compositeState) + and: 'the system returns this yang modelled cm handle' + 1 * mockInventoryPersistence.getYangModelCmHandle('some-cm-handle') >> yangModelCmHandle + when: 'getting cm handle composite state for a given cm handle id from ncmp service' + def result = objectUnderTest.getCmHandleCompositeState('some-cm-handle') + then: 'the result returns the correct data' + assert result == compositeState + } + + def 'Execute cm handle id search'() { + given: 'valid CmHandleQueryApiParameters input' + def cmHandleQueryApiParameters = new CmHandleQueryApiParameters() + def conditionApiProperties = new ConditionApiProperties() + conditionApiProperties.conditionName = 'hasAllModules' + conditionApiProperties.conditionParameters = [[moduleName: 'module-name-1']] + cmHandleQueryApiParameters.cmHandleQueryParameters = [conditionApiProperties] + and: 'query cm handle method return with a data node list' + mockParameterizedCmHandleQueryService.queryCmHandleIds( + spiedJsonObjectMapper.convertToValueType(cmHandleQueryApiParameters, CmHandleQueryServiceParameters.class)) + >> ['cm-handle-id-1'] + when: 'execute cm handle search is called' + def result = objectUnderTest.executeCmHandleIdSearch(cmHandleQueryApiParameters) + then: 'result is the same collection as returned by the CPS Data Service' + assert result == ['cm-handle-id-1'] + } + + def 'Getting module definitions by module'() { + when: 'get module definitions is performed with module name' + objectUnderTest.getModuleDefinitionsByCmHandleAndModule('some-cm-handle', 'some-module', '2021-08-04') + then: 'ncmp inventory persistence service is invoked once with correct parameters' + 1 * mockInventoryPersistence.getModuleDefinitionsByCmHandleAndModule('some-cm-handle', 'some-module', '2021-08-04') + } + + def 'Getting module definitions by cm handle id'() { + when: 'get module definitions is performed with cm handle id' + objectUnderTest.getModuleDefinitionsByCmHandleId('some-cm-handle') + then: 'ncmp inventory persistence service is invoked once with correct parameter' + 1 * mockInventoryPersistence.getModuleDefinitionsByCmHandleId('some-cm-handle') + } + + def 'Execute cm handle search'() { + given: 'valid CmHandleQueryApiParameters input' + def cmHandleQueryApiParameters = new CmHandleQueryApiParameters() + def conditionApiProperties = new ConditionApiProperties() + conditionApiProperties.conditionName = 'hasAllModules' + conditionApiProperties.conditionParameters = [[moduleName: 'module-name-1']] + cmHandleQueryApiParameters.cmHandleQueryParameters = [conditionApiProperties] + and: 'query cm handle method returns two cm handles' + mockParameterizedCmHandleQueryService.queryCmHandles( + spiedJsonObjectMapper.convertToValueType(cmHandleQueryApiParameters, CmHandleQueryServiceParameters.class)) + >> [new NcmpServiceCmHandle(cmHandleId: 'ch-0'), new NcmpServiceCmHandle(cmHandleId: 'ch-1')] + and: ' a trust level for ch-1' + trustLevelPerCmHandle.put('ch-1', TrustLevel.COMPLETE) + when: 'execute cm handle search is called' + def result = objectUnderTest.executeCmHandleSearch(cmHandleQueryApiParameters) + then: 'result consists of the two cm handles returned by the CPS Data Service' + assert result.size() == 2 + assert result[0].cmHandleId == 'ch-0' + assert result[1].cmHandleId == 'ch-1' + and: 'only ch-1 has a trust level' + assert result[0].currentTrustLevel == null + assert result[1].currentTrustLevel == TrustLevel.COMPLETE + } + + def 'Set Cm Handle Data Sync flag.'() { + when: 'setting data sync enabled flag' + objectUnderTest.setDataSyncEnabled('ch-1',true) + then: 'call is delegated to the cm handle registration service' + mockCmHandleRegistrationService.setDataSyncEnabled('ch-1', true) + } + + def dataStores() { + CompositeState.DataStores.builder().operationalDataStore(CompositeState.Operational.builder() + .dataStoreSyncState(DataStoreSyncState.NONE_REQUESTED) + .lastSyncTime('some-timestamp').build()).build() + } +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyCmHandleQueryServiceSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/ParameterizedCmHandleQueryServiceSpec.groovy index 7c410cc58..dfd549e63 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyCmHandleQueryServiceSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/ParameterizedCmHandleQueryServiceSpec.groovy @@ -18,17 +18,12 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl - -import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DMI_REGISTRY_PARENT +package org.onap.cps.ncmp.impl.inventory import org.onap.cps.cpspath.parser.PathParsingException -import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle -import org.onap.cps.ncmp.api.impl.inventory.CmHandleQueries -import org.onap.cps.ncmp.api.impl.inventory.CmHandleQueriesImpl -import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence -import org.onap.cps.ncmp.api.models.CmHandleQueryServiceParameters -import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle +import org.onap.cps.ncmp.api.inventory.models.CmHandleQueryServiceParameters +import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle +import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle import org.onap.cps.spi.FetchDescendantsOption import org.onap.cps.spi.exceptions.DataInUseException import org.onap.cps.spi.exceptions.DataValidationException @@ -36,16 +31,18 @@ import org.onap.cps.spi.model.ConditionProperties import org.onap.cps.spi.model.DataNode import spock.lang.Specification -class NetworkCmProxyCmHandleQueryServiceSpec extends Specification { +import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DMI_REGISTRY_PARENT + +class ParameterizedCmHandleQueryServiceSpec extends Specification { - def cmHandleQueries = Mock(CmHandleQueries) - def partiallyMockedCmHandleQueries = Spy(CmHandleQueries) + def cmHandleQueries = Mock(CmHandleQueryService) + def partiallyMockedCmHandleQueries = Spy(CmHandleQueryService) def mockInventoryPersistence = Mock(InventoryPersistence) def dmiRegistry = new DataNode(xpath: NCMP_DMI_REGISTRY_PARENT, childDataNodes: createDataNodeList(['PNFDemo1', 'PNFDemo2', 'PNFDemo3', 'PNFDemo4'])) - def objectUnderTest = new NetworkCmProxyCmHandleQueryServiceImpl(cmHandleQueries, mockInventoryPersistence) - def objectUnderTestWithPartiallyMockedQueries = new NetworkCmProxyCmHandleQueryServiceImpl(partiallyMockedCmHandleQueries, mockInventoryPersistence) + def objectUnderTest = new ParameterizedCmHandleQueryServiceImpl(cmHandleQueries, mockInventoryPersistence) + def objectUnderTestWithPartiallyMockedQueries = new ParameterizedCmHandleQueryServiceImpl(partiallyMockedCmHandleQueries, mockInventoryPersistence) def 'Query cm handle ids with cpsPath.'() { given: 'a cmHandleWithCpsPath condition property' diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/CmHandleQueryConditionsSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/models/CmHandleQueryConditionsSpec.groovy index 100705ff5..26541de64 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/CmHandleQueryConditionsSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/models/CmHandleQueryConditionsSpec.groovy @@ -18,7 +18,8 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.utils +package org.onap.cps.ncmp.impl.inventory.models + import spock.lang.Specification diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/InventoryQueryConditionsSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/models/InventoryQueryConditionsSpec.groovy index 00edb8d0d..51c1f4bb1 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/InventoryQueryConditionsSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/models/InventoryQueryConditionsSpec.groovy @@ -18,7 +18,8 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.utils +package org.onap.cps.ncmp.impl.inventory.models + import spock.lang.Specification diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/yangmodels/YangModelCmHandleSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/models/YangModelCmHandleSpec.groovy index cfc6ffda1..4908379a4 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/yangmodels/YangModelCmHandleSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/models/YangModelCmHandleSpec.groovy @@ -18,18 +18,16 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.yangmodels +package org.onap.cps.ncmp.impl.inventory.models -import org.onap.cps.ncmp.api.impl.inventory.CmHandleState -import org.onap.cps.ncmp.api.impl.inventory.CompositeState -import org.onap.cps.ncmp.api.impl.inventory.CompositeStateBuilder -import org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory -import org.onap.cps.ncmp.api.impl.inventory.DataStoreSyncState -import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle +import org.onap.cps.ncmp.api.inventory.models.CompositeState +import org.onap.cps.ncmp.api.inventory.models.CompositeStateBuilder +import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle +import org.onap.cps.ncmp.impl.inventory.DataStoreSyncState import spock.lang.Specification -import static org.onap.cps.ncmp.api.impl.operations.RequiredDmiService.DATA -import static org.onap.cps.ncmp.api.impl.operations.RequiredDmiService.MODEL +import static org.onap.cps.ncmp.impl.models.RequiredDmiService.DATA +import static org.onap.cps.ncmp.impl.models.RequiredDmiService.MODEL class YangModelCmHandleSpec extends Specification { diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/executor/AsyncTaskExecutorSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/AsyncTaskExecutorSpec.groovy index 64ecafefc..751c97a4d 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/executor/AsyncTaskExecutorSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/AsyncTaskExecutorSpec.groovy @@ -18,12 +18,13 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.inventory.sync.executor +package org.onap.cps.ncmp.impl.inventory.sync + -import org.onap.cps.ncmp.api.impl.inventory.sync.executor.AsyncTaskExecutor import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.context.SpringBootTest import spock.lang.Specification + import java.util.concurrent.TimeoutException import java.util.function.Supplier diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/DataSyncWatchdogSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/DataSyncWatchdogSpec.groovy index 0ffb56791..2ee5226dc 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/DataSyncWatchdogSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/DataSyncWatchdogSpec.groovy @@ -18,19 +18,19 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.inventory.sync - -import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME +package org.onap.cps.ncmp.impl.inventory.sync import com.hazelcast.map.IMap import org.onap.cps.api.CpsDataService -import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle -import org.onap.cps.ncmp.api.impl.inventory.CmHandleState -import org.onap.cps.ncmp.api.impl.inventory.CompositeState -import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence -import org.onap.cps.ncmp.api.impl.inventory.DataStoreSyncState +import org.onap.cps.ncmp.api.inventory.models.CompositeState +import org.onap.cps.ncmp.impl.inventory.DataStoreSyncState +import org.onap.cps.ncmp.impl.inventory.InventoryPersistence +import org.onap.cps.ncmp.impl.inventory.models.CmHandleState +import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle import spock.lang.Specification +import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME + class DataSyncWatchdogSpec extends Specification { def mockInventoryPersistence = Mock(InventoryPersistence) diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiModelOperationsSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/DmiModelOperationsSpec.groovy index 9aab46747..bae87d94c 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiModelOperationsSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/DmiModelOperationsSpec.groovy @@ -19,11 +19,12 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.operations +package org.onap.cps.ncmp.impl.inventory.sync import com.fasterxml.jackson.core.JsonProcessingException import com.fasterxml.jackson.databind.ObjectMapper -import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration +import org.onap.cps.ncmp.api.impl.config.DmiProperties +import org.onap.cps.ncmp.impl.DmiOperationsBaseSpec import org.onap.cps.spi.model.ModuleReference import org.onap.cps.utils.JsonObjectMapper import org.spockframework.spring.SpringBean @@ -34,10 +35,11 @@ import org.springframework.http.ResponseEntity import org.springframework.test.context.ContextConfiguration import spock.lang.Shared -import static org.onap.cps.ncmp.api.impl.operations.OperationType.READ +import static org.onap.cps.ncmp.api.data.models.OperationType.READ +import static org.onap.cps.ncmp.impl.models.RequiredDmiService.MODEL @SpringBootTest -@ContextConfiguration(classes = [NcmpConfiguration.DmiProperties, DmiModelOperations]) +@ContextConfiguration(classes = [DmiProperties, DmiModelOperations]) class DmiModelOperationsSpec extends DmiOperationsBaseSpec { @Shared @@ -56,10 +58,9 @@ class DmiModelOperationsSpec extends DmiOperationsBaseSpec { mockYangModelCmHandleRetrieval([]) and: 'a positive response from DMI service when it is called with the expected parameters' def moduleReferencesAsLisOfMaps = [[moduleName: 'mod1', revision: 'A'], [moduleName: 'mod2', revision: 'X']] - def expectedUrl = "${dmiServiceName}/dmi/v1/ch/${cmHandleId}/modules" + def expectedUrl = "${DmiOperationsBaseSpec.dmiServiceName}/dmi/v1/ch/${DmiOperationsBaseSpec.cmHandleId}/modules" def responseFromDmi = new ResponseEntity([schemas: moduleReferencesAsLisOfMaps], HttpStatus.OK) - mockDmiRestClient.postOperationWithJsonData(expectedUrl, '{"cmHandleProperties":{},"moduleSetTag":""}', READ, NO_AUTH_HEADER) - >> responseFromDmi + mockDmiRestClient.synchronousPostOperationWithJsonData(MODEL, expectedUrl, '{"cmHandleProperties":{},"moduleSetTag":""}', READ, NO_AUTH_HEADER) >> responseFromDmi when: 'get module references is called' def result = objectUnderTest.getModuleReferences(yangModelCmHandle) then: 'the result consists of expected module references' @@ -72,7 +73,7 @@ class DmiModelOperationsSpec extends DmiOperationsBaseSpec { and: 'any response from DMI service when it is called with the expected parameters' // TODO (toine): production code ignores any error code from DMI, this should be improved in future def responseFromDmi = new ResponseEntity(bodyAsMap, HttpStatus.NO_CONTENT) - mockDmiRestClient.postOperationWithJsonData(*_) >> responseFromDmi + mockDmiRestClient.synchronousPostOperationWithJsonData(*_) >> responseFromDmi when: 'get module references is called' def result = objectUnderTest.getModuleReferences(yangModelCmHandle) then: 'the result is empty' @@ -90,7 +91,7 @@ class DmiModelOperationsSpec extends DmiOperationsBaseSpec { mockYangModelCmHandleRetrieval(dmiProperties) and: 'a positive response from DMI service when it is called with tha expected parameters' def responseFromDmi = new ResponseEntity<String>(HttpStatus.OK) - mockDmiRestClient.postOperationWithJsonData("${dmiServiceName}/dmi/v1/ch/${cmHandleId}/modules", + mockDmiRestClient.synchronousPostOperationWithJsonData(MODEL, "${DmiOperationsBaseSpec.dmiServiceName}/dmi/v1/ch/${DmiOperationsBaseSpec.cmHandleId}/modules", '{"cmHandleProperties":' + expectedAdditionalPropertiesInRequest + ',"moduleSetTag":""}', READ, NO_AUTH_HEADER) >> responseFromDmi when: 'a get module references is called' def result = objectUnderTest.getModuleReferences(yangModelCmHandle) @@ -109,7 +110,7 @@ class DmiModelOperationsSpec extends DmiOperationsBaseSpec { def responseFromDmi = new ResponseEntity([[moduleName: 'mod1', revision: 'A', yangSource: 'some yang source'], [moduleName: 'mod2', revision: 'C', yangSource: 'other yang source']], HttpStatus.OK) def expectedModuleReferencesInRequest = '{"name":"mod1","revision":"A"},{"name":"mod2","revision":"X"}' - mockDmiRestClient.postOperationWithJsonData("${dmiServiceName}/dmi/v1/ch/${cmHandleId}/moduleResources", + mockDmiRestClient.synchronousPostOperationWithJsonData(MODEL, "${DmiOperationsBaseSpec.dmiServiceName}/dmi/v1/ch/${DmiOperationsBaseSpec.cmHandleId}/moduleResources", '{"data":{"modules":[' + expectedModuleReferencesInRequest + ']},"cmHandleProperties":{}}', READ, NO_AUTH_HEADER) >> responseFromDmi when: 'get new yang resources from DMI service' def result = objectUnderTest.getNewYangResourcesFromDmi(yangModelCmHandle, newModuleReferences) @@ -125,7 +126,7 @@ class DmiModelOperationsSpec extends DmiOperationsBaseSpec { and: 'a positive response from DMI service when it is called with tha expected parameters' // TODO (toine): production code ignores any error code from DMI, this should be improved in future def responseFromDmi = new ResponseEntity(responseFromDmiBody, HttpStatus.NO_CONTENT) - mockDmiRestClient.postOperationWithJsonData(*_) >> responseFromDmi + mockDmiRestClient.synchronousPostOperationWithJsonData(*_) >> responseFromDmi when: 'get new yang resources from DMI service' def result = objectUnderTest.getNewYangResourcesFromDmi(yangModelCmHandle, newModuleReferences) then: 'the result is empty' @@ -141,7 +142,7 @@ class DmiModelOperationsSpec extends DmiOperationsBaseSpec { mockYangModelCmHandleRetrieval(dmiProperties) and: 'a positive response from DMI service when it is called with the expected moduleSetTag, modules and properties' def responseFromDmi = new ResponseEntity<>([[moduleName: 'mod1', revision: 'A', yangSource: 'some yang source']], HttpStatus.OK) - mockDmiRestClient.postOperationWithJsonData("${dmiServiceName}/dmi/v1/ch/${cmHandleId}/moduleResources", + mockDmiRestClient.synchronousPostOperationWithJsonData(MODEL, "${DmiOperationsBaseSpec.dmiServiceName}/dmi/v1/ch/${DmiOperationsBaseSpec.cmHandleId}/moduleResources", '{"data":{"modules":[{"name":"mod1","revision":"A"},{"name":"mod2","revision":"X"}]},"cmHandleProperties":' + expectedAdditionalPropertiesInRequest + '}', READ, NO_AUTH_HEADER) >> responseFromDmi when: 'get new yang resources from DMI service' @@ -159,9 +160,9 @@ class DmiModelOperationsSpec extends DmiOperationsBaseSpec { mockYangModelCmHandleRetrieval([], moduleSetTag) and: 'a positive response from DMI service when it is called with the expected moduleSetTag' def responseFromDmi = new ResponseEntity<>([[moduleName: 'mod1', revision: 'A', yangSource: 'some yang source']], HttpStatus.OK) - mockDmiRestClient.postOperationWithJsonData("${dmiServiceName}/dmi/v1/ch/${cmHandleId}/moduleResources", - '{' + expectedModuleSetTagInRequest + '"data":{"modules":[{"name":"mod1","revision":"A"},{"name":"mod2","revision":"X"}]},"cmHandleProperties":{}}', - READ, NO_AUTH_HEADER) >> responseFromDmi + mockDmiRestClient.synchronousPostOperationWithJsonData(MODEL, "${DmiOperationsBaseSpec.dmiServiceName}/dmi/v1/ch/${DmiOperationsBaseSpec.cmHandleId}/moduleResources", + '{' + expectedModuleSetTagInRequest + '"data":{"modules":[{"name":"mod1","revision":"A"},{"name":"mod2","revision":"X"}]},"cmHandleProperties":{}}', + READ, NO_AUTH_HEADER) >> responseFromDmi when: 'get new yang resources from DMI service' def result = objectUnderTest.getNewYangResourcesFromDmi(yangModelCmHandle, newModuleReferences) then: 'the result is the response from DMI service' @@ -180,7 +181,7 @@ class DmiModelOperationsSpec extends DmiOperationsBaseSpec { then: 'no resources are returned' assert result == [:] and: 'no request is sent to DMI' - 0 * mockDmiRestClient.postOperationWithJsonData(*_) + 0 * mockDmiRestClient.synchronousPostOperationWithJsonData(*_) } def 'Retrieving yang resources from DMI with null DMI properties.'() { diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleOperationsUtilsSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleOperationsUtilsSpec.groovy index 44bc18200..babe8101d 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleOperationsUtilsSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleOperationsUtilsSpec.groovy @@ -19,40 +19,40 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.inventory.sync - -import static org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory.LOCKED_MISBEHAVING -import static org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory.MODULE_UPGRADE -import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_OPERATIONAL -import static org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory.MODULE_SYNC_FAILED -import static org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory.MODULE_UPGRADE_FAILED +package org.onap.cps.ncmp.impl.inventory.sync import ch.qos.logback.classic.Level import ch.qos.logback.classic.Logger import ch.qos.logback.core.read.ListAppender -import org.slf4j.LoggerFactory -import org.springframework.context.annotation.AnnotationConfigApplicationContext import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.databind.ObjectMapper -import org.onap.cps.ncmp.api.impl.operations.DmiDataOperations -import org.onap.cps.ncmp.api.impl.inventory.CmHandleQueries -import org.onap.cps.ncmp.api.impl.inventory.CmHandleState -import org.onap.cps.ncmp.api.impl.inventory.CompositeState -import org.onap.cps.ncmp.api.impl.inventory.CompositeStateBuilder -import org.onap.cps.ncmp.api.impl.inventory.DataStoreSyncState +import org.onap.cps.ncmp.api.inventory.models.CompositeState +import org.onap.cps.ncmp.api.inventory.models.CompositeStateBuilder +import org.onap.cps.ncmp.impl.data.DmiDataOperations +import org.onap.cps.ncmp.impl.inventory.CmHandleQueryService +import org.onap.cps.ncmp.impl.inventory.DataStoreSyncState +import org.onap.cps.ncmp.impl.inventory.models.CmHandleState import org.onap.cps.spi.FetchDescendantsOption import org.onap.cps.spi.model.DataNode import org.onap.cps.utils.JsonObjectMapper +import org.slf4j.LoggerFactory +import org.springframework.context.annotation.AnnotationConfigApplicationContext import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity import spock.lang.Specification + import java.time.OffsetDateTime import java.time.format.DateTimeFormatter import java.util.stream.Collectors +import static org.onap.cps.ncmp.impl.inventory.models.LockReasonCategory.LOCKED_MISBEHAVING +import static org.onap.cps.ncmp.impl.inventory.models.LockReasonCategory.MODULE_SYNC_FAILED +import static org.onap.cps.ncmp.impl.inventory.models.LockReasonCategory.MODULE_UPGRADE +import static org.onap.cps.ncmp.impl.inventory.models.LockReasonCategory.MODULE_UPGRADE_FAILED + class ModuleOperationsUtilsSpec extends Specification{ - def mockCmHandleQueries = Mock(CmHandleQueries) + def mockCmHandleQueries = Mock(CmHandleQueryService) def mockDmiDataOperations = Mock(DmiDataOperations) @@ -202,7 +202,7 @@ class ModuleOperationsUtilsSpec extends Specification{ def jsonString = '{"stores:bookstore":{"categories":[{"code":"01"}]}}' JsonNode jsonNode = jsonObjectMapper.convertToJsonNode(jsonString); def responseEntity = new ResponseEntity<>(jsonNode, HttpStatus.OK) - mockDmiDataOperations.getResourceDataFromDmi(PASSTHROUGH_OPERATIONAL.datastoreName, 'cm-handle-123', _) >> responseEntity + mockDmiDataOperations.getAllResourceDataFromDmi('cm-handle-123', _) >> responseEntity when: 'get resource data is called' def result = objectUnderTest.getResourceData('cm-handle-123') then: 'the returned data is correct' diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleSyncServiceSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncServiceSpec.groovy index cb933fafb..c3a01a739 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleSyncServiceSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncServiceSpec.groovy @@ -18,35 +18,33 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.inventory.sync +package org.onap.cps.ncmp.impl.inventory.sync import org.onap.cps.api.CpsAnchorService - -import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME -import static org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory.MODULE_UPGRADE - -import org.onap.cps.ncmp.api.impl.inventory.CmHandleState -import org.onap.cps.spi.model.DataNode import org.onap.cps.api.CpsDataService import org.onap.cps.api.CpsModuleService -import org.onap.cps.spi.model.DataNodeBuilder -import org.onap.cps.ncmp.api.impl.operations.DmiModelOperations -import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle -import org.onap.cps.ncmp.api.impl.inventory.CmHandleQueries -import org.onap.cps.ncmp.api.impl.inventory.CompositeStateBuilder -import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle +import org.onap.cps.ncmp.api.inventory.models.CompositeStateBuilder +import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle +import org.onap.cps.ncmp.impl.inventory.CmHandleQueryService +import org.onap.cps.ncmp.impl.inventory.models.CmHandleState +import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle import org.onap.cps.spi.CascadeDeleteAllowed import org.onap.cps.spi.exceptions.SchemaSetNotFoundException +import org.onap.cps.spi.model.DataNode +import org.onap.cps.spi.model.DataNodeBuilder import org.onap.cps.spi.model.ModuleReference import org.onap.cps.utils.JsonObjectMapper import spock.lang.Specification +import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME +import static org.onap.cps.ncmp.impl.inventory.models.LockReasonCategory.MODULE_UPGRADE + class ModuleSyncServiceSpec extends Specification { def mockCpsModuleService = Mock(CpsModuleService) def mockDmiModelOperations = Mock(DmiModelOperations) def mockCpsAnchorService = Mock(CpsAnchorService) - def mockCmHandleQueries = Mock(CmHandleQueries) + def mockCmHandleQueries = Mock(CmHandleQueryService) def mockCpsDataService = Mock(CpsDataService) def mockJsonObjectMapper = Mock(JsonObjectMapper) diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleSyncTasksSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncTasksSpec.groovy index 6c0d6dfb8..ee49f2f90 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleSyncTasksSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncTasksSpec.groovy @@ -19,10 +19,7 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.inventory.sync - -import static org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory.MODULE_SYNC_FAILED -import static org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory.MODULE_UPGRADE_FAILED +package org.onap.cps.ncmp.impl.inventory.sync import ch.qos.logback.classic.Level import ch.qos.logback.classic.Logger @@ -31,18 +28,22 @@ import ch.qos.logback.core.read.ListAppender import com.hazelcast.config.Config import com.hazelcast.instance.impl.HazelcastInstanceFactory import com.hazelcast.map.IMap -import org.onap.cps.ncmp.api.impl.events.lcm.LcmEventsCmHandleStateHandler -import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle -import org.onap.cps.ncmp.api.impl.inventory.CmHandleState -import org.onap.cps.ncmp.api.impl.inventory.CompositeState -import org.onap.cps.ncmp.api.impl.inventory.CompositeStateBuilder -import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence -import org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory +import org.onap.cps.ncmp.api.inventory.models.CompositeState +import org.onap.cps.ncmp.api.inventory.models.CompositeStateBuilder +import org.onap.cps.ncmp.impl.inventory.InventoryPersistence +import org.onap.cps.ncmp.impl.inventory.models.CmHandleState +import org.onap.cps.ncmp.impl.inventory.models.LockReasonCategory +import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle +import org.onap.cps.ncmp.impl.inventory.sync.lcm.LcmEventsCmHandleStateHandler import org.onap.cps.spi.model.DataNode import org.slf4j.LoggerFactory import spock.lang.Specification + import java.util.concurrent.atomic.AtomicInteger +import static org.onap.cps.ncmp.impl.inventory.models.LockReasonCategory.MODULE_SYNC_FAILED +import static org.onap.cps.ncmp.impl.inventory.models.LockReasonCategory.MODULE_UPGRADE_FAILED + class ModuleSyncTasksSpec extends Specification { def logger = Spy(ListAppender<ILoggingEvent>) diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleSyncWatchdogSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncWatchdogSpec.groovy index 1752a17a1..155edc8bc 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleSyncWatchdogSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncWatchdogSpec.groovy @@ -19,15 +19,15 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.inventory.sync +package org.onap.cps.ncmp.impl.inventory.sync import com.hazelcast.map.IMap -import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle -import org.onap.cps.ncmp.api.impl.inventory.sync.executor.AsyncTaskExecutor -import java.util.concurrent.ArrayBlockingQueue +import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle import org.onap.cps.spi.model.DataNode import spock.lang.Specification +import java.util.concurrent.ArrayBlockingQueue + class ModuleSyncWatchdogSpec extends Specification { def mockSyncUtils = Mock(ModuleOperationsUtils) diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/embeddedcache/SynchronizationCacheConfigSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/SynchronizationCacheConfigSpec.groovy index 6d09df0d4..8e59922c4 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/embeddedcache/SynchronizationCacheConfigSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/SynchronizationCacheConfigSpec.groovy @@ -18,7 +18,7 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.config.embeddedcache +package org.onap.cps.ncmp.impl.inventory.sync import com.hazelcast.config.Config import com.hazelcast.core.Hazelcast @@ -29,6 +29,7 @@ import org.springframework.boot.test.context.SpringBootTest import org.springframework.test.context.ContextConfiguration import spock.lang.Specification import spock.util.concurrent.PollingConditions + import java.util.concurrent.BlockingQueue import java.util.concurrent.TimeUnit diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/config/WatchdogSchedulingConfigurerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/WatchdogSchedulingConfigurerSpec.groovy index f37d5a41e..a943962e9 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/config/WatchdogSchedulingConfigurerSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/WatchdogSchedulingConfigurerSpec.groovy @@ -18,7 +18,8 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.inventory.sync.config +package org.onap.cps.ncmp.impl.inventory.sync + import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.context.SpringBootTest diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/lcm/LcmEventsCmHandleStateHandlerImplSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventsCmHandleStateHandlerImplSpec.groovy index 3ae95209f..bd7c321bc 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/lcm/LcmEventsCmHandleStateHandlerImplSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventsCmHandleStateHandlerImplSpec.groovy @@ -18,21 +18,21 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.events.lcm - -import static org.onap.cps.ncmp.api.impl.inventory.CmHandleState.ADVISED -import static org.onap.cps.ncmp.api.impl.inventory.CmHandleState.DELETED -import static org.onap.cps.ncmp.api.impl.inventory.CmHandleState.DELETING -import static org.onap.cps.ncmp.api.impl.inventory.CmHandleState.LOCKED -import static org.onap.cps.ncmp.api.impl.inventory.CmHandleState.READY -import static org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory.MODULE_SYNC_FAILED - -import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle -import org.onap.cps.ncmp.api.impl.inventory.CompositeState -import org.onap.cps.ncmp.api.impl.inventory.DataStoreSyncState -import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence +package org.onap.cps.ncmp.impl.inventory.sync.lcm + +import org.onap.cps.ncmp.api.inventory.models.CompositeState +import org.onap.cps.ncmp.impl.inventory.DataStoreSyncState +import org.onap.cps.ncmp.impl.inventory.InventoryPersistence +import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle import spock.lang.Specification +import static org.onap.cps.ncmp.impl.inventory.models.CmHandleState.ADVISED +import static org.onap.cps.ncmp.impl.inventory.models.CmHandleState.DELETED +import static org.onap.cps.ncmp.impl.inventory.models.CmHandleState.DELETING +import static org.onap.cps.ncmp.impl.inventory.models.CmHandleState.LOCKED +import static org.onap.cps.ncmp.impl.inventory.models.CmHandleState.READY +import static org.onap.cps.ncmp.impl.inventory.models.LockReasonCategory.MODULE_SYNC_FAILED + class LcmEventsCmHandleStateHandlerImplSpec extends Specification { def mockInventoryPersistence = Mock(InventoryPersistence) diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/lcm/LcmEventsCreatorSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventsCreatorSpec.groovy index bef2963cf..e0a552ea9 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/lcm/LcmEventsCreatorSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventsCreatorSpec.groovy @@ -18,19 +18,19 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.events.lcm - -import static org.onap.cps.ncmp.api.impl.inventory.CmHandleState.ADVISED -import static org.onap.cps.ncmp.api.impl.inventory.CmHandleState.DELETING -import static org.onap.cps.ncmp.api.impl.inventory.CmHandleState.READY +package org.onap.cps.ncmp.impl.inventory.sync.lcm import org.mapstruct.factory.Mappers -import org.onap.cps.ncmp.api.impl.inventory.CmHandleState -import org.onap.cps.ncmp.api.impl.inventory.CompositeState -import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle +import org.onap.cps.ncmp.api.inventory.models.CompositeState +import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle import org.onap.cps.ncmp.events.lcm.v1.Values +import org.onap.cps.ncmp.impl.inventory.models.CmHandleState import spock.lang.Specification +import static org.onap.cps.ncmp.impl.inventory.models.CmHandleState.ADVISED +import static org.onap.cps.ncmp.impl.inventory.models.CmHandleState.DELETING +import static org.onap.cps.ncmp.impl.inventory.models.CmHandleState.READY + class LcmEventsCreatorSpec extends Specification { LcmEventHeaderMapper lcmEventsHeaderMapper = Mappers.getMapper(LcmEventHeaderMapper) @@ -190,4 +190,4 @@ class LcmEventsCreatorSpec extends Specification { 'blank target and existing values' | '' | '' | '' | '' | '' | '' 'new target value and blank existing values' | '' | 'someAlternateId' | '' | 'someAlternateId' | '' | 'someDataProducerIdentifier' } -}
\ No newline at end of file +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/lcm/LcmEventsPublisherSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventsPublisherSpec.groovy index e2bdc5d1f..e7674761c 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/lcm/LcmEventsPublisherSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventsPublisherSpec.groovy @@ -18,7 +18,7 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.events.lcm +package org.onap.cps.ncmp.impl.inventory.sync.lcm import com.fasterxml.jackson.databind.ObjectMapper import org.apache.kafka.clients.consumer.KafkaConsumer diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/lcm/LcmEventsServiceSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventsServiceSpec.groovy index 0b6b5a7b9..b745734f0 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/lcm/LcmEventsServiceSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventsServiceSpec.groovy @@ -18,7 +18,7 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.events.lcm +package org.onap.cps.ncmp.impl.inventory.sync.lcm import org.onap.cps.events.EventsPublisher import org.onap.cps.ncmp.events.lcm.v1.LcmEvent diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/trustlevel/DeviceHeartbeatConsumerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/trustlevel/DeviceTrustLevelMessageConsumerSpec.groovy index 42a1c5d5a..6db304acd 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/trustlevel/DeviceHeartbeatConsumerSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/trustlevel/DeviceTrustLevelMessageConsumerSpec.groovy @@ -18,23 +18,24 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.trustlevel +package org.onap.cps.ncmp.impl.inventory.trustlevel import com.fasterxml.jackson.databind.ObjectMapper import io.cloudevents.CloudEvent import io.cloudevents.core.builder.CloudEventBuilder import org.apache.kafka.clients.consumer.ConsumerRecord +import org.onap.cps.ncmp.api.inventory.models.TrustLevel import org.onap.cps.ncmp.events.trustlevel.DeviceTrustLevel import org.onap.cps.utils.JsonObjectMapper import org.springframework.boot.test.context.SpringBootTest import spock.lang.Specification @SpringBootTest(classes = [ObjectMapper, JsonObjectMapper]) -class DeviceHeartbeatConsumerSpec extends Specification { +class DeviceTrustLevelMessageConsumerSpec extends Specification { def mockTrustLevelManager = Mock(TrustLevelManager) - def objectUnderTest = new DeviceHeartbeatConsumer(mockTrustLevelManager) + def objectUnderTest = new DeviceTrustLevelMessageConsumer(mockTrustLevelManager) def objectMapper = new ObjectMapper() def jsonObjectMapper = new JsonObjectMapper(objectMapper) @@ -46,7 +47,7 @@ class DeviceHeartbeatConsumerSpec extends Specification { def consumerRecord = new ConsumerRecord<String, CloudEvent>('test-device-heartbeat', 0, 0, 'sample-message-key', eventFromDmi) consumerRecord.headers().add('ce_id', objectMapper.writeValueAsBytes('ch-1')) when: 'the event is consumed' - objectUnderTest.heartbeatListener(consumerRecord) + objectUnderTest.deviceTrustLevelListener(consumerRecord) then: 'cm handles are stored with correct trust level' 1 * mockTrustLevelManager.handleUpdateOfDeviceTrustLevel('"ch-1"', TrustLevel.COMPLETE) } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/trustlevel/dmiavailability/DmiPluginWatchDogSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/trustlevel/DmiPluginTrustLevelWatchDogSpec.groovy index 446126be9..7fa8b2cce 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/trustlevel/dmiavailability/DmiPluginWatchDogSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/trustlevel/DmiPluginTrustLevelWatchDogSpec.groovy @@ -18,26 +18,22 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.trustlevel.dmiavailability +package org.onap.cps.ncmp.impl.inventory.trustlevel -import org.onap.cps.ncmp.api.NetworkCmProxyDataService import org.onap.cps.ncmp.api.impl.client.DmiRestClient -import org.onap.cps.ncmp.api.impl.trustlevel.TrustLevel -import org.onap.cps.ncmp.api.impl.trustlevel.TrustLevelManager +import org.onap.cps.ncmp.api.inventory.models.TrustLevel +import org.onap.cps.ncmp.impl.inventory.CmHandleQueryService import spock.lang.Specification -class DmiPluginWatchDogSpec extends Specification { +class DmiPluginTrustLevelWatchDogSpec extends Specification { def mockDmiRestClient = Mock(DmiRestClient) - def mockNetworkCmProxyDataService = Mock(NetworkCmProxyDataService) + def mockCmHandleQueryService = Mock(CmHandleQueryService) def mockTrustLevelManager = Mock(TrustLevelManager) def trustLevelPerDmiPlugin = [:] - def objectUnderTest = new DmiPluginWatchDog(mockDmiRestClient, - mockNetworkCmProxyDataService, - mockTrustLevelManager, - trustLevelPerDmiPlugin) + def objectUnderTest = new DmiPluginTrustLevelWatchDog(mockDmiRestClient, mockCmHandleQueryService, mockTrustLevelManager, trustLevelPerDmiPlugin) def 'watch dmi plugin health status for #dmiHealhStatus'() { given: 'the cache has been initialised and "knows" about dmi-1' diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/embeddedcache/TrustLevelCacheConfigSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/trustlevel/TrustLevelCacheConfigSpec.groovy index c213ab626..e79a47101 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/embeddedcache/TrustLevelCacheConfigSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/trustlevel/TrustLevelCacheConfigSpec.groovy @@ -18,11 +18,11 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.config.embeddedcache +package org.onap.cps.ncmp.impl.inventory.trustlevel import com.hazelcast.config.Config import com.hazelcast.core.Hazelcast -import org.onap.cps.ncmp.api.impl.trustlevel.TrustLevel +import org.onap.cps.ncmp.api.inventory.models.TrustLevel import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.context.SpringBootTest import spock.lang.Specification diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/trustlevel/TrustLevelManagerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/trustlevel/TrustLevelManagerSpec.groovy index 886648a7e..b5bfbc165 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/trustlevel/TrustLevelManagerSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/trustlevel/TrustLevelManagerSpec.groovy @@ -18,11 +18,12 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.trustlevel +package org.onap.cps.ncmp.impl.inventory.trustlevel -import org.onap.cps.ncmp.api.impl.events.avc.ncmptoclient.AvcEventPublisher -import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence -import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle +import org.onap.cps.ncmp.api.inventory.models.TrustLevel +import org.onap.cps.ncmp.impl.inventory.InventoryPersistence +import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle +import org.onap.cps.ncmp.utils.events.CmAvcEventPublisher import spock.lang.Specification class TrustLevelManagerSpec extends Specification { @@ -31,7 +32,7 @@ class TrustLevelManagerSpec extends Specification { def trustLevelPerDmiPlugin = [:] def mockInventoryPersistence = Mock(InventoryPersistence) - def mockAttributeValueChangeEventPublisher = Mock(AvcEventPublisher) + def mockAttributeValueChangeEventPublisher = Mock(CmAvcEventPublisher) def objectUnderTest = new TrustLevelManager(trustLevelPerCmHandle, trustLevelPerDmiPlugin, mockInventoryPersistence, mockAttributeValueChangeEventPublisher) def 'Initial cm handle registration'() { diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/utils/AlternateIdMatcherSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/utils/AlternateIdMatcherSpec.groovy new file mode 100644 index 000000000..ad8449582 --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/utils/AlternateIdMatcherSpec.groovy @@ -0,0 +1,66 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2024 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the 'License'); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an 'AS IS' BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.impl.utils + +import org.onap.cps.ncmp.exceptions.NoAlternateIdMatchFoundException +import org.onap.cps.ncmp.impl.inventory.InventoryPersistence +import org.onap.cps.spi.exceptions.DataNodeNotFoundException +import org.onap.cps.spi.model.DataNode +import spock.lang.Specification + +class AlternateIdMatcherSpec extends Specification { + + def mockInventoryPersistence = Mock(InventoryPersistence) + def objectUnderTest = new AlternateIdMatcher(mockInventoryPersistence) + + def setup() { + given: 'cm handle in the registry with alternate id /a/b' + mockInventoryPersistence.getCmHandleDataNodeByAlternateId('/a/b') >> new DataNode() + and: 'no other cm handle' + mockInventoryPersistence.getCmHandleDataNodeByAlternateId(_) >> { throw new DataNodeNotFoundException('', '') } + } + + def 'Finding longest alternate id matches.'() { + expect: 'querying for alternate id a matching result found' + assert objectUnderTest.getCmHandleDataNodeByLongestMatchingAlternateId(targetAlternateId, '/') != null + where: 'the following parameters are used' + scenario | targetAlternateId + 'exact match' | '/a/b' + 'parent match' | '/a/b/c' + 'grand parent match' | '/a/b/c/d' + 'trailing separator match' | '/a/b/' + } + + def 'Attempt to find longest alternate id match without any matches.'() { + when: 'attempt to find alternateId' + objectUnderTest.getCmHandleDataNodeByLongestMatchingAlternateId(targetAlternateId, '/') + then: 'no alternate id match found exception thrown' + def thrown = thrown(NoAlternateIdMatchFoundException) + and: 'the exception has the relevant details from the error response' + assert thrown.message == 'No matching cm handle found using alternate ids' + assert thrown.details == 'cannot find a datanode with alternate id ' + targetAlternateId + where: 'the following parameters are used' + scenario | targetAlternateId + 'no match for parent only' | '/a' + 'no match for other child' | '/a/c' + 'no match at all' | '/x/y' + } +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/YangDataConverterSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/utils/YangDataConverterSpec.groovy index 0ae348c07..bb45e8ad9 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/YangDataConverterSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/utils/YangDataConverterSpec.groovy @@ -18,7 +18,7 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.utils +package org.onap.cps.ncmp.impl.utils import org.onap.cps.spi.model.DataNode import spock.lang.Specification @@ -33,8 +33,7 @@ class YangDataConverterSpec extends Specification{ leaves: ['name': 'pubProp1', 'value': 'pubValue1']) def dataNodeCmHandle = new DataNode(leaves:['id':'sample-id'], childDataNodes:[dataNodeAdditionalProperties, dataNodePublicProperties]) when: 'the dataNode is converted' - def yangModelCmHandle = - YangDataConverter.convertCmHandleToYangModel(dataNodeCmHandle) + def yangModelCmHandle = YangDataConverter.toYangModelCmHandle(dataNodeCmHandle) then: 'the converted object has the correct id' assert yangModelCmHandle.id == 'sample-id' and: 'the additional (dmi, private) properties are included' @@ -50,7 +49,7 @@ class YangDataConverterSpec extends Specification{ def dataNodes = [new DataNode(xpath:'/dmi-registry/cm-handles[@id=\'some-cm-handle\']', leaves: ['id':'some-cm-handle']), new DataNode(xpath:'/dmi-registry/cm-handles[@id=\'another-cm-handle\']', leaves: ['id':'another-cm-handle'])] when: 'the data nodes are converted' - def yangModelCmHandles = YangDataConverter.convertDataNodesToYangModelCmHandles(dataNodes) + def yangModelCmHandles = YangDataConverter.toYangModelCmHandles(dataNodes) then: 'verify both have returned and CmHandleIds are correct' assert yangModelCmHandles.size() == 2 assert yangModelCmHandles.id.containsAll(['some-cm-handle', 'another-cm-handle']) diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/AbstractModelLoaderSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/AbstractModelLoaderSpec.groovy index b0be29d93..162a9831c 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/AbstractModelLoaderSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/AbstractModelLoaderSpec.groovy @@ -32,7 +32,7 @@ import org.onap.cps.spi.CascadeDeleteAllowed import org.onap.cps.spi.exceptions.AlreadyDefinedException import org.springframework.boot.SpringApplication import org.slf4j.LoggerFactory -import org.springframework.boot.context.event.ApplicationReadyEvent +import org.springframework.boot.context.event.ApplicationStartedEvent import org.springframework.context.annotation.AnnotationConfigApplicationContext import spock.lang.Specification @@ -64,18 +64,18 @@ class AbstractModelLoaderSpec extends Specification { applicationContext.close() } - def 'Application ready event'() { - when: 'Application (ready) event is triggered' - objectUnderTest.onApplicationEvent(Mock(ApplicationReadyEvent)) + def 'Application started event'() { + when: 'Application (started) event is triggered' + objectUnderTest.onApplicationEvent(Mock(ApplicationStartedEvent)) then: 'the onboard/upgrade method is executed' 1 * objectUnderTest.onboardOrUpgradeModel() } - def 'Application ready event with start up exception'() { + def 'Application started event with start up exception'() { given: 'a start up exception is thrown doing model onboarding' objectUnderTest.onboardOrUpgradeModel() >> { throw new NcmpStartUpException('test message','details are not logged') } - when: 'Application (ready) event is triggered' - objectUnderTest.onApplicationEvent(new ApplicationReadyEvent(new SpringApplication(), null, applicationContext, null)) + when: 'Application (started) event is triggered' + objectUnderTest.onApplicationEvent(new ApplicationStartedEvent(new SpringApplication(), null, applicationContext, null)) then: 'the exception message is logged' def logs = loggingListAppender.list.toString() assert logs.contains('test message') diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/CmDataSubscriptionModelLoaderSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/CmDataSubscriptionModelLoaderSpec.groovy index f3b405b11..dd1fd0b46 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/CmDataSubscriptionModelLoaderSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/CmDataSubscriptionModelLoaderSpec.groovy @@ -31,11 +31,11 @@ import org.onap.cps.ncmp.api.impl.exception.NcmpStartUpException import org.onap.cps.spi.exceptions.AlreadyDefinedException import org.onap.cps.spi.model.Dataspace import org.slf4j.LoggerFactory -import org.springframework.boot.context.event.ApplicationReadyEvent +import org.springframework.boot.context.event.ApplicationStartedEvent import org.springframework.context.annotation.AnnotationConfigApplicationContext import spock.lang.Specification -import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DATASPACE_NAME +import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DATASPACE_NAME class CmDataSubscriptionModelLoaderSpec extends Specification { @@ -65,11 +65,11 @@ class CmDataSubscriptionModelLoaderSpec extends Specification { applicationContext.close() } - def 'Onboard subscription model via application ready event.'() { + def 'Onboard subscription model via application started event.'() { given: 'dataspace is ready for use' mockCpsDataspaceService.getDataspace(NCMP_DATASPACE_NAME) >> new Dataspace('') when: 'the application is ready' - objectUnderTest.onApplicationEvent(Mock(ApplicationReadyEvent)) + objectUnderTest.onApplicationEvent(Mock(ApplicationStartedEvent)) then: 'the module service to create schema set is called once' 1 * mockCpsModuleService.createSchemaSet(NCMP_DATASPACE_NAME, 'cm-data-subscriptions', expectedYangResourcesToContentMap) and: 'the admin service to create an anchor set is called once' diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/InventoryModelLoaderSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/InventoryModelLoaderSpec.groovy index cd659bb52..76c1589be 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/InventoryModelLoaderSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/InventoryModelLoaderSpec.groovy @@ -20,23 +20,22 @@ package org.onap.cps.ncmp.init -import org.onap.cps.api.CpsAnchorService - -import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DATASPACE_NAME -import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR - import ch.qos.logback.classic.Level import ch.qos.logback.classic.Logger import ch.qos.logback.core.read.ListAppender -import org.onap.cps.api.CpsDataspaceService +import org.onap.cps.api.CpsAnchorService import org.onap.cps.api.CpsDataService +import org.onap.cps.api.CpsDataspaceService import org.onap.cps.api.CpsModuleService import org.onap.cps.spi.model.Dataspace import org.slf4j.LoggerFactory -import org.springframework.boot.context.event.ApplicationReadyEvent +import org.springframework.boot.context.event.ApplicationStartedEvent import org.springframework.context.annotation.AnnotationConfigApplicationContext import spock.lang.Specification +import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DATASPACE_NAME +import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR + class InventoryModelLoaderSpec extends Specification { def mockCpsAdminService = Mock(CpsDataspaceService) @@ -68,8 +67,8 @@ class InventoryModelLoaderSpec extends Specification { def 'Onboard subscription model via application ready event.'() { given: 'dataspace is ready for use' mockCpsAdminService.getDataspace(NCMP_DATASPACE_NAME) >> new Dataspace('') - when: 'the application is ready' - objectUnderTest.onApplicationEvent(Mock(ApplicationReadyEvent)) + when: 'the application is started' + objectUnderTest.onApplicationEvent(Mock(ApplicationStartedEvent)) then: 'the module service is used to create the new schema set from the correct resource' 1 * mockCpsModuleService.createSchemaSet(NCMP_DATASPACE_NAME, 'dmi-registry-2024-02-23', expectedYangResourceToContentMap) and: 'the admin service is used to update the anchor' diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/avc/ncmptoclient/AvcEventPublisherSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/utils/events/CmAvcEventPublisherSpec.groovy index 101a29b29..247f32183 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/avc/ncmptoclient/AvcEventPublisherSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/utils/events/CmAvcEventPublisherSpec.groovy @@ -18,7 +18,7 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.events.avc.ncmptoclient +package org.onap.cps.ncmp.utils.events import com.fasterxml.jackson.databind.ObjectMapper import io.cloudevents.CloudEvent @@ -30,13 +30,11 @@ import org.onap.cps.ncmp.events.avc.ncmp_to_client.AvcEvent import org.onap.cps.utils.JsonObjectMapper import org.springframework.test.context.ContextConfiguration -import static org.onap.cps.ncmp.api.impl.events.mapper.CloudEventMapper.toTargetEvent - @ContextConfiguration(classes = [CpsApplicationContext, ObjectMapper, JsonObjectMapper]) -class AvcEventPublisherSpec extends MessagingBaseSpec { +class CmAvcEventPublisherSpec extends MessagingBaseSpec { def mockEventsPublisher = Mock(EventsPublisher<CloudEvent>) - def objectUnderTest = new AvcEventPublisher(mockEventsPublisher) + def objectUnderTest = new CmAvcEventPublisher(mockEventsPublisher) def 'Publish an attribute value change event'() { given: 'the event key' @@ -52,7 +50,7 @@ class AvcEventPublisherSpec extends MessagingBaseSpec { then: 'the cloud event publisher is invoked with the correct data' 1 * mockEventsPublisher.publishCloudEvent(_, someEventKey, cloudEvent -> { - def actualAvcs = toTargetEvent(cloudEvent, AvcEvent.class).data.attributeValueChange + def actualAvcs = CloudEventMapper.toTargetEvent(cloudEvent, AvcEvent.class).data.attributeValueChange def expectedAvc = new Avc(attributeName: someAttributeName, oldAttributeValue: someOldAttributeValue, newAttributeValue: someNewAttributeValue) diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/utils/events/TopicValidatorSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/utils/events/TopicValidatorSpec.groovy new file mode 100644 index 000000000..26148f870 --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/utils/events/TopicValidatorSpec.groovy @@ -0,0 +1,46 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2022 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.utils.events + +import org.onap.cps.ncmp.exceptions.InvalidTopicException +import spock.lang.Specification + +class TopicValidatorSpec extends Specification { + + def 'Valid topic name validation.'() { + when: 'a valid topic name is validated' + TopicValidator.validateTopicName('my-valid-topic') + then: 'no exception is thrown' + noExceptionThrown() + } + + def 'Validating invalid topic names.'() { + when: 'the invalid topic name is validated' + TopicValidator.validateTopicName(topicName) + then: 'boolean response will be returned for #scenario' + thrown(InvalidTopicException) + where: 'the following names are used' + scenario | topicName + 'empty topic' | '' + 'blank topic' | ' ' + 'invalid non empty topic' | '1_5_*_#' + } +} |