From dbed81b6cece07c8950fa2f32f6f05eba3bb991e Mon Sep 17 00:00:00 2001 From: ToineSiebelink Date: Thu, 13 Jun 2024 16:52:45 +0100 Subject: Introducing NCMP Facades - NetworkCmProxyDataService was polluted with many non-data operations - it is now split into: 1) NetworkCmProxyFacade, a single thin facade for the (main) controler to redirect calls to the correct services 2) CmHandleRegistrationService(Impl), methods related to registration only - introduced NetworkCmProxyInventoryFacade for the invenoty controller - renamed some services for consitency and clarification - Use facade to acces ncmp data request handlers (instead of direct from controller) - remove unnecesarry wrappings between request handlers and facade - split facades according to names: data & inventory (the REST controllers are not split properly so I think one rest controller will end up needing both facades) Issue-ID: CPS-2263 Change-Id: I250732aa16ec28b43ff642d2adf10ba36f67290e Signed-off-by: ToineSiebelink --- ...leRegistrationServicePropertyHandlerSpec.groovy | 296 +++++++++++++ .../impl/CmHandleRegistrationServiceSpec.groovy | 458 +++++++++++++++++++++ .../NcmpCachedResourceRequestHandlerSpec.groovy | 64 +++ .../impl/NcmpDatastoreRequestHandlerSpec.groovy | 67 ++- .../NetworkCmProxyCmHandleQueryServiceSpec.groovy | 223 ---------- ...rkCmProxyDataServiceImplRegistrationSpec.groovy | 421 ------------------- .../impl/NetworkCmProxyDataServiceImplSpec.groovy | 413 ------------------- ...orkCmProxyDataServicePropertyHandlerSpec.groovy | 285 ------------- .../ncmp/api/impl/NetworkCmProxyFacadeSpec.groovy | 108 +++++ .../impl/NetworkCmProxyInventoryFacadeSpec.groovy | 226 ++++++++++ .../ParameterizedCmHandleQueryServiceSpec.groovy | 222 ++++++++++ .../ncmp/api/impl/client/DmiRestClientSpec.groovy | 65 ++- .../impl/inventory/CmHandleQueriesImplSpec.groovy | 206 --------- .../inventory/CmHandleQueryServiceImplSpec.groovy | 206 +++++++++ .../inventory/InventoryPersistenceImplSpec.groovy | 2 +- .../sync/ModuleOperationsUtilsSpec.groovy | 6 +- .../inventory/sync/ModuleSyncServiceSpec.groovy | 4 +- .../impl/operations/DmiDataOperationsSpec.groovy | 14 +- .../impl/operations/DmiModelOperationsSpec.groovy | 16 +- .../dmiavailability/DmiPluginWatchDogSpec.groovy | 9 +- 20 files changed, 1659 insertions(+), 1652 deletions(-) create mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/CmHandleRegistrationServicePropertyHandlerSpec.groovy create mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/CmHandleRegistrationServiceSpec.groovy create mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NcmpCachedResourceRequestHandlerSpec.groovy delete mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyCmHandleQueryServiceSpec.groovy delete mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplRegistrationSpec.groovy delete mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplSpec.groovy delete mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServicePropertyHandlerSpec.groovy create mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyFacadeSpec.groovy create mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyInventoryFacadeSpec.groovy create mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/ParameterizedCmHandleQueryServiceSpec.groovy delete mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/CmHandleQueriesImplSpec.groovy create mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/CmHandleQueryServiceImplSpec.groovy (limited to 'cps-ncmp-service/src/test') diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/CmHandleRegistrationServicePropertyHandlerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/CmHandleRegistrationServicePropertyHandlerSpec.groovy new file mode 100644 index 000000000..5eac44b8f --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/CmHandleRegistrationServicePropertyHandlerSpec.groovy @@ -0,0 +1,296 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2022-2024 Nordix Foundation + * Modifications Copyright (C) 2022 Bell Canada + * 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. + * 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 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 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.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 + +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 + +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 CmHandleRegistrationServicePropertyHandler(mockInventoryPersistence, mockCpsDataService, jsonObjectMapper, mockAlternateIdChecker) + def logger = Spy(ListAppender) + + void setup() { + def setupLogger = ((Logger) LoggerFactory.getLogger(CmHandleRegistrationServicePropertyHandler.class)) + setupLogger.addAppender(logger) + setupLogger.setLevel(Level.DEBUG) + logger.start() + // Always accept all alternate IDs + mockAlternateIdChecker.getIdsOfCmHandlesWithRejectedAlternateId(*_) >> [] + } + + void cleanup() { + ((Logger) LoggerFactory.getLogger(CmHandleRegistrationServicePropertyHandler.class)).detachAndStopAllAppenders() + } + + def static cmHandleId = 'myHandle1' + def static cmHandleXpath = "/dmi-registry/cm-handles[@id='${cmHandleId}']" + + def static propertyDataNodes = [new DataNodeBuilder().withXpath("/dmi-registry/cm-handles[@id='${cmHandleId}']/additional-properties[@name='additionalProp1']").withLeaves(['name': 'additionalProp1', 'value': 'additionalValue1']).build(), + new DataNodeBuilder().withXpath("/dmi-registry/cm-handles[@id='${cmHandleId}']/additional-properties[@name='additionalProp2']").withLeaves(['name': 'additionalProp2', 'value': 'additionalValue2']).build(), + new DataNodeBuilder().withXpath("/dmi-registry/cm-handles[@id='${cmHandleId}']/public-properties[@name='publicProp3']").withLeaves(['name': 'publicProp3', 'value': 'publicValue3']).build(), + new DataNodeBuilder().withXpath("/dmi-registry/cm-handles[@id='${cmHandleId}']/public-properties[@name='publicProp4']").withLeaves(['name': 'publicProp4', 'value': 'publicValue4']).build()] + def static cmHandleDataNodeAsCollection = [new DataNode(xpath: cmHandleXpath, childDataNodes: propertyDataNodes, leaves: ['id': cmHandleId])] + + def 'Update CM Handle Public Properties: #scenario'() { + given: 'the CPS service return a CM handle' + mockInventoryPersistence.getCmHandleDataNodeByCmHandleId(cmHandleId) >> cmHandleDataNodeAsCollection + and: 'an update cm handle request with public properties updates' + def cmHandleUpdateRequest = [new NcmpServiceCmHandle(cmHandleId: cmHandleId, publicProperties: updatedPublicProperties)] + when: 'update data node leaves is called with the update request' + objectUnderTest.updateCmHandleProperties(cmHandleUpdateRequest) + then: 'the replace list method is called with correct params' + 1 * mockInventoryPersistence.replaceListContent(cmHandleXpath, _) >> { args -> + { + assert args[1].leaves.size() == expectedPropertiesAfterUpdate.size() + assert args[1].leaves.containsAll(convertToProperties(expectedPropertiesAfterUpdate)) + } + } + where: 'following public properties updates are made' + scenario | updatedPublicProperties || expectedPropertiesAfterUpdate + 'property added' | ['newPubProp1': 'pub-val'] || [['publicProp3': 'publicValue3'], ['publicProp4': 'publicValue4'], ['newPubProp1': 'pub-val']] + 'property updated' | ['publicProp4': 'newPubVal'] || [['publicProp3': 'publicValue3'], ['publicProp4': 'newPubVal']] + 'property removed' | ['publicProp4': null] || [['publicProp3': 'publicValue3']] + 'property ignored(value is null)' | ['pub-prop': null] || [['publicProp3': 'publicValue3'], ['publicProp4': 'publicValue4']] + } + + def 'Update DMI Properties: #scenario'() { + given: 'the CPS service return a CM handle' + mockInventoryPersistence.getCmHandleDataNodeByCmHandleId(cmHandleId) >> cmHandleDataNodeAsCollection + and: 'an update cm handle request with DMI properties updates' + def cmHandleUpdateRequest = [new NcmpServiceCmHandle(cmHandleId: cmHandleId, dmiProperties: updatedDmiProperties)] + when: 'update data node leaves is called with the update request' + objectUnderTest.updateCmHandleProperties(cmHandleUpdateRequest) + then: 'replace list method should is called with correct params' + expectedCallsToReplaceMethod * mockInventoryPersistence.replaceListContent(cmHandleXpath, _) >> { args -> + { + assert args[1].leaves.size() == expectedPropertiesAfterUpdate.size() + assert args[1].leaves.containsAll(convertToProperties(expectedPropertiesAfterUpdate)) + } + } + where: 'following DMI properties updates are made' + scenario | updatedDmiProperties || expectedPropertiesAfterUpdate | expectedCallsToReplaceMethod + 'property added' | ['newAdditionalProp1': 'add-value'] || [['additionalProp1': 'additionalValue1'], ['additionalProp2': 'additionalValue2'], ['newAdditionalProp1': 'add-value']] | 1 + 'property updated' | ['additionalProp1': 'newValue'] || [['additionalProp2': 'additionalValue2'], ['additionalProp1': 'newValue']] | 1 + 'property removed' | ['additionalProp1': null] || [['additionalProp2': 'additionalValue2']] | 1 + 'property ignored(value is null)' | ['new-prop': null] || [['additionalProp1': 'additionalValue1'], ['additionalProp2': 'additionalValue2']] | 1 + 'no property changes' | [:] || [['additionalProp1': 'additionalValue1'], ['additionalProp2': 'additionalValue2']] | 0 + } + + def 'Update CM Handle Properties, remove all properties: #scenario'() { + given: 'the CPS service return a CM handle' + def cmHandleDataNode = new DataNode(xpath: cmHandleXpath, leaves: ['id': cmHandleId], childDataNodes: originalPropertyDataNodes) + mockInventoryPersistence.getCmHandleDataNodeByCmHandleId(cmHandleId) >> [cmHandleDataNode] + and: 'an update cm handle request that removes all public properties(existing and non-existing)' + def cmHandleUpdateRequest = [new NcmpServiceCmHandle(cmHandleId: cmHandleId, publicProperties: ['publicProp3': null, 'publicProp4': null])] + when: 'update data node leaves is called with the update request' + objectUnderTest.updateCmHandleProperties(cmHandleUpdateRequest) + then: 'the replace list method is not called' + 0 * mockInventoryPersistence.replaceListContent(*_) + then: 'delete data node will be called for any existing property' + expectedCallsToDeleteDataNode * mockInventoryPersistence.deleteDataNode(_) >> { arg -> + { + assert arg[0].contains("@name='publicProp") + } + } + where: 'following public properties updates are made' + scenario | originalPropertyDataNodes || expectedCallsToDeleteDataNode + '2 original properties, both removed' | propertyDataNodes || 2 + 'no original properties' | [] || 0 + } + + def '#scenario error leads to #exception when we try to update cmHandle'() { + given: 'cm handles request' + def cmHandleUpdateRequest = [new NcmpServiceCmHandle(cmHandleId: cmHandleId, publicProperties: [:], dmiProperties: [:])] + and: 'data node cannot be found' + mockInventoryPersistence.getCmHandleDataNodeByCmHandleId(*_) >> { throw exception } + when: 'update data node leaves is called using correct parameters' + def response = objectUnderTest.updateCmHandleProperties(cmHandleUpdateRequest) + then: 'one failed registration response' + response.size() == 1 + and: 'it has expected error details' + with(response.get(0)) { + assert it.status == Status.FAILURE + assert it.cmHandle == cmHandleId + assert it.ncmpResponseStatus == expectedError + assert it.errorText == expectedErrorText + } + where: + scenario | cmHandleId | exception || expectedError | expectedErrorText + 'Cm Handle does not exist' | 'cmHandleId' | new DataNodeNotFoundException(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR) || CM_HANDLES_NOT_FOUND | 'cm handle id(s) not found' + 'Unknown' | 'cmHandleId' | new RuntimeException('Failed') || UNKNOWN_ERROR | 'Failed' + 'Invalid cm handle id' | 'cmHandleId with spaces' | new DataValidationException('Name Validation Error.', cmHandleId + 'contains an invalid character') || CM_HANDLE_INVALID_ID | 'cm-handle has an invalid character(s) in id' + } + + def 'Multiple update operations in a single request'() { + given: 'cm handles request' + def cmHandleUpdateRequest = [new NcmpServiceCmHandle(cmHandleId: cmHandleId, publicProperties: ['publicProp1': "value"], dmiProperties: [:]), + new NcmpServiceCmHandle(cmHandleId: cmHandleId, publicProperties: ['publicProp1': "value"], dmiProperties: [:]), + new NcmpServiceCmHandle(cmHandleId: cmHandleId, publicProperties: ['publicProp1': "value"], dmiProperties: [:])] + and: 'data node can be found for 1st and 3rd cm-handle but not for 2nd cm-handle' + mockInventoryPersistence.getCmHandleDataNodeByCmHandleId(*_) >> cmHandleDataNodeAsCollection >> { + throw new DataNodeNotFoundException(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR) } >> cmHandleDataNodeAsCollection + when: 'update data node leaves is called using correct parameters' + def cmHandleResponseList = objectUnderTest.updateCmHandleProperties(cmHandleUpdateRequest) + then: 'response has 3 values' + cmHandleResponseList.size() == 3 + and: 'the 1st and 3rd requests were processed successfully' + with(cmHandleResponseList.get(0)) { + assert it.status == Status.SUCCESS + assert it.cmHandle == cmHandleId + } + with(cmHandleResponseList.get(2)) { + assert it.status == Status.SUCCESS + assert it.cmHandle == cmHandleId + } + and: 'the 2nd request failed with correct error code' + with(cmHandleResponseList.get(1)) { + assert it.status == Status.FAILURE + assert it.cmHandle == cmHandleId + assert it.ncmpResponseStatus == CM_HANDLES_NOT_FOUND + assert it.errorText == 'cm handle id(s) not found' + } + then: 'the replace list method is called twice' + 2 * mockInventoryPersistence.replaceListContent(cmHandleXpath, _) + } + + def 'Update alternate id of existing CM Handle.'() { + given: 'cm handles request' + def cmHandleUpdateRequest = [new NcmpServiceCmHandle(cmHandleId: cmHandleId, alternateId: 'alt-1')] + and: 'a data node found' + def dataNode = new DataNode(xpath: cmHandleXpath, leaves: ['id': cmHandleId, 'alternate-id': 'alt-1']) + mockInventoryPersistence.getCmHandleDataNodeByCmHandleId(cmHandleId) >> [dataNode] + 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', _, _, ContentType.JSON) >> + { args -> + assert args[3].contains('alt-1') + } + and: 'one successful registration response' + response.size() == 1 + and: 'the response shows success for the given cm handle id' + assert response[0].status == Status.SUCCESS + assert response[0].cmHandle == cmHandleId + } + + def 'Update with rejected alternate id.'() { + given: 'cm handles request' + def updatedNcmpServiceCmHandles = [new NcmpServiceCmHandle(cmHandleId: cmHandleId, alternateId: 'alt-1')] + and: 'a data node found' + def dataNode = new DataNode(xpath: cmHandleXpath, leaves: ['id': cmHandleId, 'alternate-id': 'alt-1']) + mockInventoryPersistence.getCmHandleDataNodeByCmHandleId(cmHandleId) >> [dataNode] + when: 'attempt to update the cm handle' + def response = objectUnderTest.updateCmHandleProperties(updatedNcmpServiceCmHandles) + then: 'the update is NOT delegated to cps data service' + 0 * mockCpsDataService.updateNodeLeaves(*_) + and: 'the alternate id checker rejects the given cm handle (override default setup behavior)' + mockAlternateIdChecker.getIdsOfCmHandlesWithRejectedAlternateId(*_) >> [cmHandleId] + and: 'the response shows a failure for the given cm handle id' + assert response[0].status == Status.FAILURE + assert response[0].cmHandle == cmHandleId + } + + def 'Update CM Handle data producer identifier from #scenario'() { + 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: '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', _, _, ContentType.JSON) >> { args -> + assert args[3].contains('New Data Producer Identifier') + } + and: 'correct information is logged' + def lastLoggingEvent = logger.list[0] + assert lastLoggingEvent.level == Level.DEBUG + assert lastLoggingEvent.formattedMessage.contains('Updating data-producer-identifier') + where: 'the following scenarios are attempted' + scenario | oldDataProducerIdentifier + 'null to something' | null + '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']) + and: 'an update request with a new data producer identifier' + def ncmpServiceCmHandle = new NcmpServiceCmHandle(cmHandleId: cmHandleId, dataProducerIdentifier: 'someNewDataProducerIdentifier') + when: 'update data producer identifier is called with the update request' + objectUnderTest.updateDataProducerIdentifier(existingCmHandleDataNode, ncmpServiceCmHandle) + then: 'the update node leaves method is not invoked' + 0 * mockCpsDataService.updateNodeLeaves(*_) + and: 'correct information is logged' + def lastLoggingEvent = logger.list[0] + assert lastLoggingEvent.level == Level.WARN + assert lastLoggingEvent.formattedMessage.contains('Unable to update dataProducerIdentifier') + } + + def convertToProperties(expectedPropertiesAfterUpdateAsMap) { + def properties = [].withDefault { [:] } + expectedPropertiesAfterUpdateAsMap.forEach(property -> + property.forEach((key, val) -> { + properties.add(['name': key, 'value': val]) + })) + return properties + } +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/CmHandleRegistrationServiceSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/CmHandleRegistrationServiceSpec.groovy new file mode 100644 index 000000000..1c6e38420 --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/CmHandleRegistrationServiceSpec.groovy @@ -0,0 +1,458 @@ +/* + * ============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 + +import com.hazelcast.map.IMap +import org.onap.cps.api.CpsDataService +import org.onap.cps.api.CpsModuleService +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.inventory.CmHandleQueryService +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.InventoryPersistence +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.utils.AlternateIdChecker +import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle +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.models.UpgradedCmHandles +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 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_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.impl.ncmppersistence.NcmpPersistence.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME +import static org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.Status + +class CmHandleRegistrationServiceSpec extends Specification { + + def ncmpServiceCmHandle = new NcmpServiceCmHandle(cmHandleId: 'some-cm-handle-id') + def mockCpsModuleService = Mock(CpsModuleService) + def mockNetworkCmProxyDataServicePropertyHandler = Mock(CmHandleRegistrationServicePropertyHandler) + def mockInventoryPersistence = Mock(InventoryPersistence) + def mockCmHandleQueries = Mock(CmHandleQueryService) + def mockLcmEventsCmHandleStateHandler = Mock(LcmEventsCmHandleStateHandler) + def mockCpsDataService = Mock(CpsDataService) + def mockModuleSyncStartedOnCmHandles = Mock(IMap) + def trustLevelPerDmiPlugin = [:] + def mockTrustLevelManager = Mock(TrustLevelManager) + def mockAlternateIdChecker = Mock(AlternateIdChecker) + + def objectUnderTest = Spy(new CmHandleRegistrationService( + mockNetworkCmProxyDataServicePropertyHandler, mockInventoryPersistence, mockCpsDataService, mockLcmEventsCmHandleStateHandler, + mockModuleSyncStartedOnCmHandles, trustLevelPerDmiPlugin , mockTrustLevelManager, mockAlternateIdChecker)) + + def setup() { + // always accept all cm handles + mockAlternateIdChecker.getIdsOfCmHandlesWithRejectedAlternateId(*_) >> [] + + // always can find all cm handles in DB + mockInventoryPersistence.getYangModelCmHandles(_) >> { args -> args[0].collect { new YangModelCmHandle(id:it) } } + } + + def 'DMI Registration: Create, Update, Delete & Upgrade operations are processed in the right order'() { + given: 'a registration with operations of all types' + def dmiRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server') + dmiRegistration.setCreatedCmHandles([new NcmpServiceCmHandle(cmHandleId: 'cmhandle-1', publicProperties: ['publicProp1': 'value'], dmiProperties: [:])]) + dmiRegistration.setUpdatedCmHandles([new NcmpServiceCmHandle(cmHandleId: 'cmhandle-2', publicProperties: ['publicProp1': 'value'], dmiProperties: [:])]) + dmiRegistration.setRemovedCmHandles(['cmhandle-2']) + dmiRegistration.setUpgradedCmHandles(new UpgradedCmHandles(cmHandles: ['cmhandle-3'], moduleSetTag: 'some-module-set-tag')) + and: 'cm handles are persisted' + mockInventoryPersistence.getYangModelCmHandles(['cmhandle-2']) >> [new YangModelCmHandle()] + mockInventoryPersistence.getYangModelCmHandle('cmhandle-3') >> new YangModelCmHandle(id: 'cmhandle-3', moduleSetTag: '', compositeState: new CompositeState(cmHandleState: CmHandleState.READY)) + and: 'cm handle is in READY state' + mockCmHandleQueries.cmHandleHasState('cmhandle-3', CmHandleState.READY) >> true + when: 'registration is processed' + objectUnderTest.updateDmiRegistrationAndSyncModule(dmiRegistration) + then: 'cm-handles are removed first' + 1 * objectUnderTest.processRemovedCmHandles(*_) + and: 'de-registered cm handle entry is removed from in progress map' + 1 * mockModuleSyncStartedOnCmHandles.remove('cmhandle-2') + then: 'cm-handles are created' + 1 * objectUnderTest.processCreatedCmHandles(*_) + then: 'cm-handles are updated' + 1 * objectUnderTest.processUpdatedCmHandles(*_) + 1 * mockNetworkCmProxyDataServicePropertyHandler.updateCmHandleProperties(*_) >> [] + then: 'cm-handles are upgraded' + 1 * objectUnderTest.processUpgradedCmHandles(*_) + } + + def 'DMI Registration upgrade operation with upgrade node state #scenario'() { + given: 'a registration with upgrade operation' + def dmiRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server') + dmiRegistration.setUpgradedCmHandles(new UpgradedCmHandles(cmHandles: ['cmhandle-3'], moduleSetTag: 'some-module-set-tag')) + and: 'exception while checking cm handle state' + mockInventoryPersistence.getYangModelCmHandle('cmhandle-3') >> new YangModelCmHandle(id: 'cmhandle-3', moduleSetTag: '', compositeState: new CompositeState(cmHandleState: cmHandleState)) + when: 'registration is processed' + def result = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiRegistration) + then: 'upgrade operation contains expected error code' + assert result.upgradedCmHandles[0].status == expectedResponseStatus + where: 'the following parameters are used' + scenario | cmHandleState || expectedResponseStatus + 'READY' | CmHandleState.READY || Status.SUCCESS + 'Not READY' | CmHandleState.LOCKED || Status.FAILURE + } + + def 'DMI Registration upgrade with exception #scenario'() { + given: 'a registration with upgrade operation' + def dmiRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server') + dmiRegistration.setUpgradedCmHandles(new UpgradedCmHandles(cmHandles: ['cmhandle-3'], moduleSetTag: 'some-module-set-tag')) + and: 'exception while checking cm handle state' + mockInventoryPersistence.getYangModelCmHandle('cmhandle-3') >> { throw exception } + when: 'registration is processed' + def result = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiRegistration) + then: 'upgrade operation contains expected error code' + assert result.upgradedCmHandles.ncmpResponseStatus.code[0] == expectedErrorCode + where: 'the following parameters are used' + scenario | exception || expectedErrorCode + 'data node not found' | new DataNodeNotFoundException('some-dataspace-name', 'some-anchor-name') || '100' + 'cm handle is invalid' | new DataValidationException('some error message', 'some error details') || '110' + } + + def 'Create CM-handle Validation: Registration with valid Service names: #scenario'() { + given: 'a registration ' + def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: dmiPlugin, dmiModelPlugin: dmiModelPlugin, + dmiDataPlugin: dmiDataPlugin) + dmiPluginRegistration.createdCmHandles = [ncmpServiceCmHandle] + when: 'update registration and sync module is called with correct DMI plugin information' + objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + then: 'create cm handles registration and sync modules is called with the correct plugin information' + 1 * objectUnderTest.processCreatedCmHandles(dmiPluginRegistration, _) + and: 'dmi is added to the dmi trustLevel map' + assert trustLevelPerDmiPlugin.size() == 1 + assert trustLevelPerDmiPlugin.containsKey(expectedDmiPluginRegisteredName) + where: + scenario | dmiPlugin | dmiModelPlugin | dmiDataPlugin || expectedDmiPluginRegisteredName + 'combined DMI plugin' | 'service1' | '' | '' || 'service1' + 'data & model DMI plugins' | '' | 'service1' | 'service2' || 'service2' + 'data & model using same service' | '' | 'service1' | 'service1' || 'service1' + } + + def 'Create CM-handle Validation: Invalid DMI plugin service name with #scenario'() { + given: 'a registration ' + def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: dmiPlugin, dmiModelPlugin: dmiModelPlugin, + dmiDataPlugin: dmiDataPlugin) + dmiPluginRegistration.createdCmHandles = [ncmpServiceCmHandle] + when: 'registration is called with incorrect DMI plugin information' + objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + then: 'a DMI Request Exception is thrown with correct message details' + def exceptionThrown = thrown(DmiRequestException.class) + assert exceptionThrown.getMessage().contains(expectedMessageDetails) + and: 'registration is not called' + 0 * objectUnderTest.processCreatedCmHandles(*_) + where: + scenario | dmiPlugin | dmiModelPlugin | dmiDataPlugin || expectedMessageDetails + 'empty DMI plugins' | '' | '' | '' || 'No DMI plugin service names' + 'blank DMI plugins' | ' ' | ' ' | ' ' || 'No DMI plugin service names' + 'null DMI plugins' | null | null | null || 'No DMI plugin service names' + 'all DMI plugins' | 'service1' | 'service2' | 'service3' || 'Cannot register combined plugin service name and other service names' + '(combined)DMI and Data Plugin' | 'service1' | '' | 'service2' || 'Cannot register combined plugin service name and other service names' + '(combined)DMI and model Plugin' | 'service1' | 'service2' | '' || 'Cannot register combined plugin service name and other service names' + 'only model DMI plugin' | '' | 'service1' | '' || 'Cannot register just a Data or Model plugin service name' + 'only data DMI plugin' | '' | '' | 'service1' || 'Cannot register just a Data or Model plugin service name' + } + + def 'Create CM-Handle Successfully: #scenario.'() { + given: 'a registration without cm-handle properties' + def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server') + dmiPluginRegistration.createdCmHandles = [new NcmpServiceCmHandle(cmHandleId: 'cmhandle', dmiProperties: dmiProperties, publicProperties: publicProperties)] + when: 'registration is updated' + def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + then: 'a successful response is received' + response.createdCmHandles.size() == 1 + with(response.createdCmHandles[0]) { + assert it.status == Status.SUCCESS + assert it.cmHandle == 'cmhandle' + } + and: 'state handler is invoked with the expected parameters' + 1 * mockLcmEventsCmHandleStateHandler.initiateStateAdvised(_) >> { + args -> { + def yangModelCmHandles = args[0] + assert yangModelCmHandles.id == ['cmhandle'] + assert yangModelCmHandles.dmiServiceName == ['my-server'] + } + } + where: + scenario | dmiProperties | publicProperties || expectedDmiProperties | expectedPublicProperties + 'with dmi & public properties' | ['dmi-key': 'dmi-value'] | ['public-key': 'public-value'] || '[{"name":"dmi-key","value":"dmi-value"}]' | '[{"name":"public-key","value":"public-value"}]' + 'with only public properties' | [:] | ['public-key': 'public-value'] || [:] | '[{"name":"public-key","value":"public-value"}]' + 'with only dmi properties' | ['dmi-key': 'dmi-value'] | [:] || '[{"name":"dmi-key","value":"dmi-value"}]' | [:] + 'without dmi & public properties' | [:] | [:] || [:] | [:] + } + + def 'Add CM-Handle #scenario.'() { + given: ' registration details for one cm handles' + def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server', + createdCmHandles:[new NcmpServiceCmHandle(cmHandleId: 'ch-1', registrationTrustLevel: registrationTrustLevel)]) + when: 'registration is updated' + objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + then: 'trustLevel is set for the created cm-handle' + 1 * mockTrustLevelManager.handleInitialRegistrationOfTrustLevels(expectedMapping) + where: + scenario | registrationTrustLevel || expectedMapping + 'with trusted cm handle' | TrustLevel.COMPLETE || [ 'ch-1' : TrustLevel.COMPLETE ] + 'without trust level' | null || [ 'ch-1' : null ] + } + + def 'Create CM-Handle Multiple Requests: All cm-handles creation requests are processed with some failures'() { + given: 'a registration with three cm-handles to be created' + def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server', + createdCmHandles: [new NcmpServiceCmHandle(cmHandleId: 'cmhandle1'), + new NcmpServiceCmHandle(cmHandleId: 'cmhandle2'), + new NcmpServiceCmHandle(cmHandleId: 'cmhandle3')]) + and: 'cm-handle creation is successful for 1st and 3rd; failed for 2nd' + def xpath = "somePathWithId[@id='cmhandle2']" + mockLcmEventsCmHandleStateHandler.initiateStateAdvised(*_) >> { throw AlreadyDefinedException.forDataNodes([xpath], 'some-context') } + when: 'registration is updated to create cm-handles' + def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + then: 'a response is received for all cm-handles' + response.createdCmHandles.size() == 1 + and: 'all cm-handles creation fails' + response.createdCmHandles.each { + assert it.cmHandle == 'cmhandle2' + assert it.status == Status.FAILURE + assert it.ncmpResponseStatus == CM_HANDLE_ALREADY_EXIST + assert it.errorText == 'cm-handle already exists' + } + } + + def 'Create CM-Handle Error Handling: Registration fails: #scenario'() { + given: 'a registration without cm-handle properties' + def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server') + dmiPluginRegistration.createdCmHandles = [new NcmpServiceCmHandle(cmHandleId: 'cmhandle')] + and: 'cm-handler registration fails: #scenario' + mockLcmEventsCmHandleStateHandler.initiateStateAdvised(*_) >> { throw exception } + when: 'registration is updated' + def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + then: 'a failure response is received' + response.createdCmHandles.size() == 1 + with(response.createdCmHandles[0]) { + assert it.status == Status.FAILURE + assert it.cmHandle == 'cmhandle' + assert it.ncmpResponseStatus == expectedError + assert it.errorText == expectedErrorText + } + where: + scenario | exception || expectedError | expectedErrorText + 'cm-handle already exist' | AlreadyDefinedException.forDataNodes(["path[@id='cmhandle']"], 'some-context') || CM_HANDLE_ALREADY_EXIST | 'cm-handle already exists' + 'unknown exception while registering cm-handle' | new RuntimeException('Failed') || UNKNOWN_ERROR | 'Failed' + } + + def 'Update CM-Handle: Update Operation Response is added to the response'() { + given: 'a registration to update CmHandles' + def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server', updatedCmHandles: [{}]) + and: 'cm-handle updates can be processed successfully' + def updateOperationResponse = [CmHandleRegistrationResponse.createSuccessResponse('cm-handle-1'), + CmHandleRegistrationResponse.createFailureResponse('cm-handle-2', new Exception("Failed")), + CmHandleRegistrationResponse.createFailureResponse('cm-handle-3', CM_HANDLES_NOT_FOUND), + CmHandleRegistrationResponse.createFailureResponse('cm handle 4', CM_HANDLE_INVALID_ID)] + mockNetworkCmProxyDataServicePropertyHandler.updateCmHandleProperties(_) >> updateOperationResponse + when: 'registration is updated' + def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + then: 'the response contains updateOperationResponse' + assert response.updatedCmHandles.size() == 4 + assert response.updatedCmHandles.containsAll(updateOperationResponse) + } + + def 'Remove CmHandle Successfully: #scenario'() { + given: 'a registration' + def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server', removedCmHandles: ['cmhandle']) + and: '#scenario' + mockCpsModuleService.deleteSchemaSetsWithCascade(_, ['cmhandle']) >> { if (!schemaSetExist) { throw new SchemaSetNotFoundException('', '') } } + when: 'registration is updated to delete cmhandle' + def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + then: 'the cmHandle state is updated to "DELETING"' + 1 * mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch(_) >> + { args -> args[0].values()[0] == CmHandleState.DELETING } + then: 'method to delete relevant schema set is called once' + 1 * mockInventoryPersistence.deleteSchemaSetsWithCascade(_) + and: 'method to delete relevant list/list element is called once' + 1 * mockInventoryPersistence.deleteDataNodes(_) + and: 'successful response is received' + assert response.removedCmHandles.size() == 1 + with(response.removedCmHandles[0]) { + assert it.status == Status.SUCCESS + assert it.cmHandle == 'cmhandle' + } + and: 'the cmHandle state is updated to "DELETED"' + 1 * mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch(_) >> + { args -> args[0].values()[0] == CmHandleState.DELETED } + and: 'No cm handles state updates for "upgraded cm handles"' + 1 * mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch([:]) + where: + scenario | schemaSetExist + 'schema-set exists and can be deleted successfully' | true + 'schema-set does not exist' | false + } + + def 'Remove CmHandle: Partial Success'() { + given: 'a registration with three cm-handles to be deleted' + def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server', + removedCmHandles: ['cmhandle1', 'cmhandle2', 'cmhandle3']) + and: 'cm-handle deletion fails on batch' + mockInventoryPersistence.deleteDataNodes(_) >> { throw new RuntimeException("Failed") } + and: 'cm-handle deletion is successful for 1st and 3rd; failed for 2nd' + mockInventoryPersistence.deleteDataNode("/dmi-registry/cm-handles[@id='cmhandle2']") >> { throw new RuntimeException("Failed") } + when: 'registration is updated to delete cmhandles' + def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + then: 'the cmHandle states are all updated to "DELETING"' + 1 * mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch({ assert it.every { entry -> entry.value == CmHandleState.DELETING } }) + and: 'a response is received for all cm-handles' + response.removedCmHandles.size() == 3 + and: 'successfully de-registered cm handle 1 is removed from in progress map' + 1 * mockModuleSyncStartedOnCmHandles.remove('cmhandle1') + and: 'successfully de-registered cm handle 3 is removed from in progress map even though it was already being removed' + 1 * mockModuleSyncStartedOnCmHandles.remove('cmhandle3') >> 'already in progress' + and: 'failed de-registered cm handle entries should NOT be removed from in progress map' + 0 * mockModuleSyncStartedOnCmHandles.remove('cmhandle2') + and: '1st and 3rd cm-handle deletes successfully' + with(response.removedCmHandles[0]) { + assert it.status == Status.SUCCESS + assert it.cmHandle == 'cmhandle1' + } + with(response.removedCmHandles[2]) { + assert it.status == Status.SUCCESS + assert it.cmHandle == 'cmhandle3' + } + and: '2nd cm-handle deletion fails' + with(response.removedCmHandles[1]) { + assert it.status == Status.FAILURE + assert it.ncmpResponseStatus == UNKNOWN_ERROR + assert it.errorText == 'Failed' + assert it.cmHandle == 'cmhandle2' + } + and: 'the cmHandle state is updated to DELETED for 1st and 3rd' + 1 * mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch({ + assert it.size() == 2 + assert it.every { entry -> entry.value == CmHandleState.DELETED } + }) + and: 'No cm handles state updates for "upgraded cm handles"' + 1 * mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch([:]) + + } + + def 'Remove CmHandle Error Handling: Schema Set Deletion failed'() { + given: 'a registration' + def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server', + removedCmHandles: ['cmhandle']) + and: 'schema set batch deletion failed with unknown error' + mockInventoryPersistence.deleteSchemaSetsWithCascade(_) >> { throw new RuntimeException('Failed') } + and: 'schema set single deletion failed with unknown error' + mockInventoryPersistence.deleteSchemaSetWithCascade(_) >> { throw new RuntimeException('Failed') } + when: 'registration is updated to delete cmhandle' + def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + then: 'no exception is thrown' + noExceptionThrown() + and: 'cm-handle is not deleted' + 0 * mockInventoryPersistence.deleteDataNodes(_) + and: 'the cmHandle state is not updated to "DELETED"' + 0 * mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch([yangModelCmHandle: CmHandleState.DELETED]) + and: 'a failure response is received' + assert response.removedCmHandles.size() == 1 + with(response.removedCmHandles[0]) { + assert it.status == Status.FAILURE + assert it.cmHandle == 'cmhandle' + assert it.errorText == 'Failed' + assert it.ncmpResponseStatus == UNKNOWN_ERROR + } + } + + def 'Remove CmHandle Error Handling: #scenario'() { + given: 'a registration' + def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server', + removedCmHandles: ['cmhandle']) + and: 'cm-handle deletion fails on batch' + mockInventoryPersistence.deleteDataNodes(_) >> { throw deleteListElementException } + and: 'cm-handle deletion fails on individual delete' + mockInventoryPersistence.deleteDataNode(_) >> { throw deleteListElementException } + when: 'registration is updated to delete cmhandle' + def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + then: 'a failure response is received' + assert response.removedCmHandles.size() == 1 + with(response.removedCmHandles[0]) { + assert it.status == Status.FAILURE + assert it.cmHandle == 'cmhandle' + assert it.ncmpResponseStatus == expectedError + assert it.errorText == expectedErrorText + } + and: 'the cm handle state is not updated to "DELETED"' + 0 * mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch(_, CmHandleState.DELETED) + where: + scenario | cmHandleId | deleteListElementException || expectedError | expectedErrorText + 'cm-handle does not exist' | 'cmhandle' | new DataNodeNotFoundException('', '', '') || CM_HANDLES_NOT_FOUND | 'cm handle id(s) not found' + 'cm-handle has invalid name' | 'cm handle with space' | new DataValidationException('', '') || CM_HANDLE_INVALID_ID | 'cm-handle has an invalid character(s) in id' + '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/NcmpCachedResourceRequestHandlerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NcmpCachedResourceRequestHandlerSpec.groovy new file mode 100644 index 000000000..781b6204a --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NcmpCachedResourceRequestHandlerSpec.groovy @@ -0,0 +1,64 @@ +/* + * ============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 + +import org.onap.cps.api.CpsDataService +import org.onap.cps.ncmp.api.NetworkCmProxyQueryService +import org.onap.cps.ncmp.api.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/api/impl/NcmpDatastoreRequestHandlerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NcmpDatastoreRequestHandlerSpec.groovy index b73f9a46d..9a845c0ba 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NcmpDatastoreRequestHandlerSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NcmpDatastoreRequestHandlerSpec.groovy @@ -20,48 +20,44 @@ package org.onap.cps.ncmp.api.impl -import org.onap.cps.ncmp.api.NetworkCmProxyDataService import org.onap.cps.ncmp.api.impl.exception.InvalidDatastoreException import org.onap.cps.ncmp.api.impl.exception.InvalidOperationException +import org.onap.cps.ncmp.api.impl.operations.DmiDataOperations import org.onap.cps.ncmp.api.models.CmResourceAddress import org.onap.cps.ncmp.api.models.DataOperationDefinition import org.onap.cps.ncmp.api.models.DataOperationRequest import org.onap.cps.ncmp.exceptions.OperationNotSupportedException import org.onap.cps.ncmp.exceptions.PayloadTooLargeException -import org.springframework.http.HttpStatus 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 mockNetworkCmProxyDataService = Mock(NetworkCmProxyDataService) + def dmiDataOperations = Mock(DmiDataOperations) - def objectUnderTest = new NcmpPassthroughResourceRequestHandler(mockNetworkCmProxyDataService) + def objectUnderTest = new NcmpPassthroughResourceRequestHandler(dmiDataOperations) + def NO_TOPIC = null def NO_AUTH_HEADER = null - def setup() { - objectUnderTest.timeOutInMilliSeconds = 100 - } - 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 returns a response from dmi' - def resultFromDmi = new ResponseEntity('response from dmi',HttpStatus.I_AM_A_TEAPOT) - def synchronousResult = Mono.justOrEmpty(resultFromDmi) - mockNetworkCmProxyDataService.getResourceDataForCmHandle(cmResourceAddress, 'options', _, _, NO_AUTH_HEADER) >> synchronousResult + 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.toString().contains('response from dmi') - assert response.toString().contains("I'm a teapot") - } else { - // expect request id in a map + assert response == 'dmi response' + } else { // expect request id in a map assert response.keySet()[0] == 'requestId' } where: 'the following parameters are used' @@ -74,33 +70,20 @@ class NcmpDatastoreRequestHandlerSpec extends Specification { def 'Attempt to execute async data operation request with feature #scenario.'() { given: 'a extended request handler that supports bulk requests' - def objectUnderTest = new NcmpPassthroughResourceRequestHandler(mockNetworkCmProxyDataService) + def objectUnderTest = new NcmpPassthroughResourceRequestHandler(dmiDataOperations) and: 'notification feature is turned on/off' objectUnderTest.notificationFeatureEnabled = notificationFeatureEnabled when: 'data operation request is executed' - objectUnderTest.executeRequest('someTopic', new DataOperationRequest(), NO_AUTH_HEADER) + 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 * mockNetworkCmProxyDataService.executeDataOperationForCmHandles('someTopic', _, _, null) + expectedCalls * dmiDataOperations.requestResourceDataFromDmi('someTopic', _, _, NO_AUTH_HEADER) + and: + result.keySet()[0] == expectedKeyInMap where: 'the following parameters are used' - scenario | notificationFeatureEnabled || expectedCalls - 'on' | true || 1 - 'off' | false || 0 - } - - def 'Execute async data operation request with datastore #datastore.'() { - given: 'notification feature is turned on' - objectUnderTest.notificationFeatureEnabled = true - and: 'a data operation request with datastore: #datastore' - def dataOperationDefinition = new DataOperationDefinition(operation: 'read', datastore: datastore) - def dataOperationRequest = new DataOperationRequest(dataOperationDefinitions: [dataOperationDefinition]) - when: 'data operation request is executed' - def response = objectUnderTest.executeRequest('myTopic', dataOperationRequest, NO_AUTH_HEADER) - and: 'a map with request id is returned' - assert response.keySet()[0] == 'requestId' - then: 'the network service is invoked' - 1 * mockNetworkCmProxyDataService.executeDataOperationForCmHandles('myTopic', dataOperationRequest, _, NO_AUTH_HEADER) - where: 'the following datastores are used' - datastore << ['ncmp-datastore:passthrough-running', 'ncmp-datastore:passthrough-operational'] + scenario | notificationFeatureEnabled || expectedCalls || expectedKeyInMap + 'on' | true || 1 || 'requestId' + 'off' | false || 0 || 'status' } def 'Attempt to execute async data operation request with error #scenario'() { @@ -108,7 +91,7 @@ class NcmpDatastoreRequestHandlerSpec extends Specification { def dataOperationDefinition = new DataOperationDefinition(operation: 'read', datastore: datastore) when: 'data operation request is executed' def dataOperationRequest = new DataOperationRequest(dataOperationDefinitions: [dataOperationDefinition]) - objectUnderTest.executeRequest('myTopic', dataOperationRequest, NO_AUTH_HEADER) + objectUnderTest.executeAsynchronousRequest('myTopic', dataOperationRequest, NO_AUTH_HEADER) then: 'the correct error is thrown' def thrown = thrown(InvalidDatastoreException) assert thrown.message.contains(expectedErrorMessage) @@ -122,7 +105,7 @@ class NcmpDatastoreRequestHandlerSpec extends Specification { 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.executeRequest('someTopic', new DataOperationRequest(dataOperationDefinitions:[dataOperationDefinition]), NO_AUTH_HEADER) + 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' @@ -136,11 +119,11 @@ class NcmpDatastoreRequestHandlerSpec extends Specification { 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 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.executeRequest('someTopic', new DataOperationRequest(dataOperationDefinitions:[dataOperationDefinition]), NO_AUTH_HEADER) + 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' 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/api/impl/NetworkCmProxyCmHandleQueryServiceSpec.groovy deleted file mode 100644 index 7c410cc58..000000000 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyCmHandleQueryServiceSpec.groovy +++ /dev/null @@ -1,223 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2022-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 - -import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DMI_REGISTRY_PARENT - -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.spi.FetchDescendantsOption -import org.onap.cps.spi.exceptions.DataInUseException -import org.onap.cps.spi.exceptions.DataValidationException -import org.onap.cps.spi.model.ConditionProperties -import org.onap.cps.spi.model.DataNode -import spock.lang.Specification - -class NetworkCmProxyCmHandleQueryServiceSpec extends Specification { - - def cmHandleQueries = Mock(CmHandleQueries) - def partiallyMockedCmHandleQueries = Spy(CmHandleQueries) - 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 'Query cm handle ids with cpsPath.'() { - given: 'a cmHandleWithCpsPath condition property' - def cmHandleQueryParameters = new CmHandleQueryServiceParameters() - def conditionProperties = createConditionProperties('cmHandleWithCpsPath', [['cpsPath' : '/some/cps/path']]) - cmHandleQueryParameters.setCmHandleQueryParameters([conditionProperties]) - and: 'the query get the cm handle datanodes excluding all descendants returns a datanode' - cmHandleQueries.queryCmHandleAncestorsByCpsPath('/some/cps/path', FetchDescendantsOption.OMIT_DESCENDANTS) >> [new DataNode(leaves: ['id':'some-cmhandle-id'])] - when: 'the query is executed for cm handle ids' - def result = objectUnderTest.queryCmHandleIds(cmHandleQueryParameters) - then: 'the correct expected cm handles ids are returned' - assert result == ['some-cmhandle-id'] as Set - } - - def 'Cm handle ids query with error: #scenario.'() { - given: 'a cmHandleWithCpsPath condition property' - def cmHandleQueryParameters = new CmHandleQueryServiceParameters() - def conditionProperties = createConditionProperties('cmHandleWithCpsPath', [['cpsPath' : '/some/cps/path']]) - cmHandleQueryParameters.setCmHandleQueryParameters([conditionProperties]) - and: 'cmHandleQueries throws a path parsing exception' - cmHandleQueries.queryCmHandleAncestorsByCpsPath('/some/cps/path', FetchDescendantsOption.OMIT_DESCENDANTS) >> { throw thrownException } - when: 'the query is executed for cm handle ids' - objectUnderTest.queryCmHandleIds(cmHandleQueryParameters) - then: 'a data validation exception is thrown' - thrown(expectedException) - where: 'the following data is used' - scenario | thrownException || expectedException - 'PathParsingException' | new PathParsingException('some message', 'some details') || DataValidationException - 'any other Exception' | new DataInUseException('some message', 'some details') || DataInUseException - } - - def 'Cm handle ids cpsPath query for private properties (not allowed).'() { - given: 'a CpsPath condition property for private properties' - def cmHandleQueryParameters = new CmHandleQueryServiceParameters() - def conditionProperties = createConditionProperties('cmHandleWithCpsPath', [['cpsPath' : '/additional-properties']]) - cmHandleQueryParameters.setCmHandleQueryParameters([conditionProperties]) - when: 'the query is executed for cm handle ids' - def result = objectUnderTest.queryCmHandleIds(cmHandleQueryParameters) - then: 'empty result is returned' - assert result.isEmpty() - } - - def 'Query cm handle ids with module names when #scenario from query.'() { - given: 'a modules condition property' - def cmHandleQueryParameters = new CmHandleQueryServiceParameters() - def conditionProperties = createConditionProperties('hasAllModules', [['moduleName': 'some-module-name']]) - cmHandleQueryParameters.setCmHandleQueryParameters([conditionProperties]) - when: 'the query is executed for cm handle ids' - def result = objectUnderTest.queryCmHandleIds(cmHandleQueryParameters) - then: 'the inventory service is called with the correct module names' - 1 * mockInventoryPersistence.getCmHandleIdsWithGivenModules(['some-module-name']) >> cmHandleIdsFromService - and: 'the correct expected cm handles ids are returned' - assert result.size() == cmHandleIdsFromService.size() - assert result.containsAll(cmHandleIdsFromService) - where: 'the following data is used' - scenario | cmHandleIdsFromService - 'One anchor returned' | ['some-cmhandle-id'] - 'No anchors are returned' | [] - } - - def 'Query cm handles with some trust level query parameters'() { - given: 'a trust level condition property' - def trustLevelQueryParameters = new CmHandleQueryServiceParameters() - def trustLevelConditionProperties = createConditionProperties('cmHandleWithTrustLevel', [['trustLevel': 'COMPLETE'] as Map]) - trustLevelQueryParameters.setCmHandleQueryParameters([trustLevelConditionProperties]) - when: 'the query is being executed' - objectUnderTest.queryCmHandleIds(trustLevelQueryParameters) - then: 'the query is being delegated to the cm handle query service with correct parameter' - 1 * cmHandleQueries.queryCmHandlesByTrustLevel(['trustLevel': 'COMPLETE'] as Map) - } - - def 'Query cm handle details with module names when #scenario from query.'() { - given: 'a modules condition property' - def cmHandleQueryParameters = new CmHandleQueryServiceParameters() - def conditionProperties = createConditionProperties('hasAllModules', [['moduleName': 'some-module-name']]) - cmHandleQueryParameters.setCmHandleQueryParameters([conditionProperties]) - when: 'the query is executed for cm handle ids' - def result = objectUnderTest.queryCmHandles(cmHandleQueryParameters) - then: 'the inventory service is called with the correct module names' - 1 * mockInventoryPersistence.getCmHandleIdsWithGivenModules(['some-module-name']) >> ['ch1'] - and: 'the inventory service is called with teh correct if and returns a yang model cm handle' - 1 * mockInventoryPersistence.getYangModelCmHandles(['ch1']) >> - [new YangModelCmHandle(id: 'abc', dmiProperties: [new YangModelCmHandle.Property('name','value')], publicProperties: [])] - and: 'the expected cm handle(s) are returned as NCMP Service cm handles' - assert result[0] instanceof NcmpServiceCmHandle - assert result.size() == 1 - assert result[0].dmiProperties == [name:'value'] - } - - def 'Query cm handle ids when the query is empty.'() { - given: 'We use an empty query' - def cmHandleQueryParameters = new CmHandleQueryServiceParameters() - and: 'the inventory persistence returns the dmi registry datanode with just ids' - mockInventoryPersistence.getDataNode(NCMP_DMI_REGISTRY_PARENT, FetchDescendantsOption.DIRECT_CHILDREN_ONLY) >> [dmiRegistry] - when: 'the query is executed for both cm handle ids' - def result = objectUnderTest.queryCmHandleIds(cmHandleQueryParameters) - then: 'the correct expected cm handles are returned' - assert result.containsAll('PNFDemo1', 'PNFDemo2', 'PNFDemo3', 'PNFDemo4') - } - - def 'Query cm handle details when the query is empty.'() { - given: 'We use an empty query' - def cmHandleQueryParameters = new CmHandleQueryServiceParameters() - and: 'the inventory persistence returns the dmi registry datanode with just ids' - mockInventoryPersistence.getDataNode(NCMP_DMI_REGISTRY_PARENT) >> [dmiRegistry] - when: 'the query is executed for both cm handle details' - def result = objectUnderTest.queryCmHandles(cmHandleQueryParameters) - then: 'the correct cm handles are returned' - assert result.size() == 4 - assert result.cmHandleId.containsAll('PNFDemo1', 'PNFDemo2', 'PNFDemo3', 'PNFDemo4') - } - - def 'Query CMHandleId with #scenario.' () { - given: 'a query object created with #condition' - def cmHandleQueryParameters = new CmHandleQueryServiceParameters() - def conditionProperties = createConditionProperties(conditionName, [['some-key': 'some-value']]) - cmHandleQueryParameters.setCmHandleQueryParameters([conditionProperties]) - and: 'the inventoryPersistence returns different CmHandleIds' - partiallyMockedCmHandleQueries.queryCmHandlePublicProperties(*_) >> cmHandlesWithMatchingPublicProperties - partiallyMockedCmHandleQueries.queryCmHandleAdditionalProperties(*_) >> cmHandlesWithMatchingPrivateProperties - when: 'the query executed' - def result = objectUnderTestWithPartiallyMockedQueries.queryCmHandleIdsForInventory(cmHandleQueryParameters) - then: 'the expected number of results are returned.' - assert result.size() == expectedCmHandleIdsSize - where: 'the following data is used' - scenario | conditionName | cmHandlesWithMatchingPublicProperties | cmHandlesWithMatchingPrivateProperties || expectedCmHandleIdsSize - 'all properties, only public matching' | 'hasAllProperties' | ['h1', 'h2'] | null || 2 - 'all properties, no matching cm handles' | 'hasAllProperties' | [] | [] || 0 - 'additional properties, some matching cm handles' | 'hasAllAdditionalProperties' | [] | ['h1', 'h2'] || 2 - 'additional properties, no matching cm handles' | 'hasAllAdditionalProperties' | null | [] || 0 - } - - def 'Retrieve CMHandleIds by different DMI properties with #scenario.' () { - given: 'a query object created with dmi plugin as condition' - def cmHandleQueryParameters = new CmHandleQueryServiceParameters() - def conditionProperties = createConditionProperties('cmHandleWithDmiPlugin', [['some-key': 'some-value']]) - cmHandleQueryParameters.setCmHandleQueryParameters([conditionProperties]) - and: 'the inventoryPersistence returns different CmHandleIds' - partiallyMockedCmHandleQueries.getCmHandleIdsByDmiPluginIdentifier(*_) >> cmHandleQueryResult - when: 'the query executed' - def result = objectUnderTestWithPartiallyMockedQueries.queryCmHandleIdsForInventory(cmHandleQueryParameters) - then: 'the expected number of results are returned.' - assert result.size() == expectedCmHandleIdsSize - where: 'the following data is used' - scenario | cmHandleQueryResult || expectedCmHandleIdsSize - 'some matches' | ['h1','h2'] || 2 - 'no matches' | [] || 0 - } - - def 'Combine two query results where #scenario.'() { - when: 'two query results in the form of a map of NcmpServiceCmHandles are combined into a single query result' - def result = objectUnderTest.combineCmHandleQueryResults(firstQuery, secondQuery) - then: 'the returned result is the same as the expected result' - result == expectedResult - where: - scenario | firstQuery | secondQuery || expectedResult - 'two queries with unique and non unique entries exist' | ['PNFDemo', 'PNFDemo2'] | ['PNFDemo', 'PNFDemo3'] || ['PNFDemo'] - 'the first query contains entries and second query is empty' | ['PNFDemo', 'PNFDemo2'] | [] || [] - 'the second query contains entries and first query is empty' | [] | ['PNFDemo', 'PNFDemo3'] || [] - 'the first query contains entries and second query is null' | ['PNFDemo', 'PNFDemo2'] | null || ['PNFDemo', 'PNFDemo2'] - 'the second query contains entries and first query is null' | null | ['PNFDemo', 'PNFDemo3'] || ['PNFDemo', 'PNFDemo3'] - 'both queries are empty' | [] | [] || [] - 'both queries are null' | null | null || null - } - - def createConditionProperties(String conditionName, List> conditionParameters) { - return new ConditionProperties(conditionName : conditionName, conditionParameters : conditionParameters) - } - - def static createDataNodeList(dataNodeIds) { - def dataNodes =[] - dataNodeIds.each{ dataNodes << new DataNode(xpath: "/dmi-registry/cm-handles[@id='${it}']", leaves: ['id':it]) } - return dataNodes - } -} 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/api/impl/NetworkCmProxyDataServiceImplRegistrationSpec.groovy deleted file mode 100644 index fbfbac59e..000000000 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplRegistrationSpec.groovy +++ /dev/null @@ -1,421 +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 - -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.spi.exceptions.AlreadyDefinedException -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 { - - 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 mockInventoryPersistence = Mock(InventoryPersistence) - def mockCmHandleQueries = Mock(CmHandleQueries) - def stubbedNetworkCmProxyCmHandlerQueryService = Stub(NetworkCmProxyCmHandleQueryService) - def mockLcmEventsCmHandleStateHandler = Mock(LcmEventsCmHandleStateHandler) - def mockCpsDataService = Mock(CpsDataService) - def mockModuleSyncStartedOnCmHandles = Mock(IMap) - def trustLevelPerDmiPlugin = [:] - 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 setup() { - // always accept all cm handles - mockAlternateIdChecker.getIdsOfCmHandlesWithRejectedAlternateId(*_) >> [] - - // always can find all cm handles in DB - mockInventoryPersistence.getYangModelCmHandles(_) >> { args -> args[0].collect { new YangModelCmHandle(id:it) } } - } - - def 'DMI Registration: Create, Update, Delete & Upgrade operations are processed in the right order'() { - given: 'a registration with operations of all types' - def dmiRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server') - dmiRegistration.setCreatedCmHandles([new NcmpServiceCmHandle(cmHandleId: 'cmhandle-1', publicProperties: ['publicProp1': 'value'], dmiProperties: [:])]) - dmiRegistration.setUpdatedCmHandles([new NcmpServiceCmHandle(cmHandleId: 'cmhandle-2', publicProperties: ['publicProp1': 'value'], dmiProperties: [:])]) - dmiRegistration.setRemovedCmHandles(['cmhandle-2']) - dmiRegistration.setUpgradedCmHandles(new UpgradedCmHandles(cmHandles: ['cmhandle-3'], moduleSetTag: 'some-module-set-tag')) - and: 'cm handles are persisted' - mockInventoryPersistence.getYangModelCmHandles(['cmhandle-2']) >> [new YangModelCmHandle()] - mockInventoryPersistence.getYangModelCmHandle('cmhandle-3') >> new YangModelCmHandle(id: 'cmhandle-3', moduleSetTag: '', compositeState: new CompositeState(cmHandleState: CmHandleState.READY)) - and: 'cm handle is in READY state' - mockCmHandleQueries.cmHandleHasState('cmhandle-3', CmHandleState.READY) >> true - when: 'registration is processed' - objectUnderTest.updateDmiRegistrationAndSyncModule(dmiRegistration) - then: 'cm-handles are removed first' - 1 * objectUnderTest.processRemovedCmHandles(*_) - and: 'de-registered cm handle entry is removed from in progress map' - 1 * mockModuleSyncStartedOnCmHandles.remove('cmhandle-2') - then: 'cm-handles are created' - 1 * objectUnderTest.processCreatedCmHandles(*_) - then: 'cm-handles are updated' - 1 * objectUnderTest.processUpdatedCmHandles(*_) - 1 * mockNetworkCmProxyDataServicePropertyHandler.updateCmHandleProperties(*_) >> [] - then: 'cm-handles are upgraded' - 1 * objectUnderTest.processUpgradedCmHandles(*_) - } - - def 'DMI Registration upgrade operation with upgrade node state #scenario'() { - given: 'a registration with upgrade operation' - def dmiRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server') - dmiRegistration.setUpgradedCmHandles(new UpgradedCmHandles(cmHandles: ['cmhandle-3'], moduleSetTag: 'some-module-set-tag')) - and: 'exception while checking cm handle state' - mockInventoryPersistence.getYangModelCmHandle('cmhandle-3') >> new YangModelCmHandle(id: 'cmhandle-3', moduleSetTag: '', compositeState: new CompositeState(cmHandleState: cmHandleState)) - when: 'registration is processed' - def result = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiRegistration) - then: 'upgrade operation contains expected error code' - assert result.upgradedCmHandles[0].status == expectedResponseStatus - where: 'the following parameters are used' - scenario | cmHandleState || expectedResponseStatus - 'READY' | CmHandleState.READY || Status.SUCCESS - 'Not READY' | CmHandleState.LOCKED || Status.FAILURE - } - - def 'DMI Registration upgrade with exception #scenario'() { - given: 'a registration with upgrade operation' - def dmiRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server') - dmiRegistration.setUpgradedCmHandles(new UpgradedCmHandles(cmHandles: ['cmhandle-3'], moduleSetTag: 'some-module-set-tag')) - and: 'exception while checking cm handle state' - mockInventoryPersistence.getYangModelCmHandle('cmhandle-3') >> { throw exception } - when: 'registration is processed' - def result = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiRegistration) - then: 'upgrade operation contains expected error code' - assert result.upgradedCmHandles.ncmpResponseStatus.code[0] == expectedErrorCode - where: 'the following parameters are used' - scenario | exception || expectedErrorCode - 'data node not found' | new DataNodeNotFoundException('some-dataspace-name', 'some-anchor-name') || '100' - 'cm handle is invalid' | new DataValidationException('some error message', 'some error details') || '110' - } - - def 'Create CM-handle Validation: Registration with valid Service names: #scenario'() { - given: 'a registration ' - def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: dmiPlugin, dmiModelPlugin: dmiModelPlugin, - dmiDataPlugin: dmiDataPlugin) - dmiPluginRegistration.createdCmHandles = [ncmpServiceCmHandle] - when: 'update registration and sync module is called with correct DMI plugin information' - objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) - then: 'create cm handles registration and sync modules is called with the correct plugin information' - 1 * objectUnderTest.processCreatedCmHandles(dmiPluginRegistration, _) - and: 'dmi is added to the dmi trustLevel map' - assert trustLevelPerDmiPlugin.size() == 1 - assert trustLevelPerDmiPlugin.containsKey(expectedDmiPluginRegisteredName) - where: - scenario | dmiPlugin | dmiModelPlugin | dmiDataPlugin || expectedDmiPluginRegisteredName - 'combined DMI plugin' | 'service1' | '' | '' || 'service1' - 'data & model DMI plugins' | '' | 'service1' | 'service2' || 'service2' - 'data & model using same service' | '' | 'service1' | 'service1' || 'service1' - } - - def 'Create CM-handle Validation: Invalid DMI plugin service name with #scenario'() { - given: 'a registration ' - def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: dmiPlugin, dmiModelPlugin: dmiModelPlugin, - dmiDataPlugin: dmiDataPlugin) - dmiPluginRegistration.createdCmHandles = [ncmpServiceCmHandle] - when: 'registration is called with incorrect DMI plugin information' - objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) - then: 'a DMI Request Exception is thrown with correct message details' - def exceptionThrown = thrown(DmiRequestException.class) - assert exceptionThrown.getMessage().contains(expectedMessageDetails) - and: 'registration is not called' - 0 * objectUnderTest.processCreatedCmHandles(*_) - where: - scenario | dmiPlugin | dmiModelPlugin | dmiDataPlugin || expectedMessageDetails - 'empty DMI plugins' | '' | '' | '' || 'No DMI plugin service names' - 'blank DMI plugins' | ' ' | ' ' | ' ' || 'No DMI plugin service names' - 'null DMI plugins' | null | null | null || 'No DMI plugin service names' - 'all DMI plugins' | 'service1' | 'service2' | 'service3' || 'Cannot register combined plugin service name and other service names' - '(combined)DMI and Data Plugin' | 'service1' | '' | 'service2' || 'Cannot register combined plugin service name and other service names' - '(combined)DMI and model Plugin' | 'service1' | 'service2' | '' || 'Cannot register combined plugin service name and other service names' - 'only model DMI plugin' | '' | 'service1' | '' || 'Cannot register just a Data or Model plugin service name' - 'only data DMI plugin' | '' | '' | 'service1' || 'Cannot register just a Data or Model plugin service name' - } - - def 'Create CM-Handle Successfully: #scenario.'() { - given: 'a registration without cm-handle properties' - def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server') - dmiPluginRegistration.createdCmHandles = [new NcmpServiceCmHandle(cmHandleId: 'cmhandle', dmiProperties: dmiProperties, publicProperties: publicProperties)] - when: 'registration is updated' - def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) - then: 'a successful response is received' - response.createdCmHandles.size() == 1 - with(response.createdCmHandles[0]) { - assert it.status == Status.SUCCESS - assert it.cmHandle == 'cmhandle' - } - and: 'state handler is invoked with the expected parameters' - 1 * mockLcmEventsCmHandleStateHandler.initiateStateAdvised(_) >> { - args -> { - def yangModelCmHandles = args[0] - assert yangModelCmHandles.id == ['cmhandle'] - assert yangModelCmHandles.dmiServiceName == ['my-server'] - } - } - where: - scenario | dmiProperties | publicProperties || expectedDmiProperties | expectedPublicProperties - 'with dmi & public properties' | ['dmi-key': 'dmi-value'] | ['public-key': 'public-value'] || '[{"name":"dmi-key","value":"dmi-value"}]' | '[{"name":"public-key","value":"public-value"}]' - 'with only public properties' | [:] | ['public-key': 'public-value'] || [:] | '[{"name":"public-key","value":"public-value"}]' - 'with only dmi properties' | ['dmi-key': 'dmi-value'] | [:] || '[{"name":"dmi-key","value":"dmi-value"}]' | [:] - 'without dmi & public properties' | [:] | [:] || [:] | [:] - } - - def 'Add CM-Handle #scenario.'() { - given: ' registration details for one cm handles' - def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server', - createdCmHandles:[new NcmpServiceCmHandle(cmHandleId: 'ch-1', registrationTrustLevel: registrationTrustLevel)]) - when: 'registration is updated' - objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) - then: 'trustLevel is set for the created cm-handle' - 1 * mockTrustLevelManager.handleInitialRegistrationOfTrustLevels(expectedMapping) - where: - scenario | registrationTrustLevel || expectedMapping - 'with trusted cm handle' | TrustLevel.COMPLETE || [ 'ch-1' : TrustLevel.COMPLETE ] - 'without trust level' | null || [ 'ch-1' : null ] - } - - def 'Create CM-Handle Multiple Requests: All cm-handles creation requests are processed with some failures'() { - given: 'a registration with three cm-handles to be created' - def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server', - createdCmHandles: [new NcmpServiceCmHandle(cmHandleId: 'cmhandle1'), - new NcmpServiceCmHandle(cmHandleId: 'cmhandle2'), - new NcmpServiceCmHandle(cmHandleId: 'cmhandle3')]) - and: 'cm-handle creation is successful for 1st and 3rd; failed for 2nd' - def xpath = "somePathWithId[@id='cmhandle2']" - mockLcmEventsCmHandleStateHandler.initiateStateAdvised(*_) >> { throw AlreadyDefinedException.forDataNodes([xpath], 'some-context') } - when: 'registration is updated to create cm-handles' - def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) - then: 'a response is received for all cm-handles' - response.createdCmHandles.size() == 1 - and: 'all cm-handles creation fails' - response.createdCmHandles.each { - assert it.cmHandle == 'cmhandle2' - assert it.status == Status.FAILURE - assert it.ncmpResponseStatus == CM_HANDLE_ALREADY_EXIST - assert it.errorText == 'cm-handle already exists' - } - } - - def 'Create CM-Handle Error Handling: Registration fails: #scenario'() { - given: 'a registration without cm-handle properties' - def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server') - dmiPluginRegistration.createdCmHandles = [new NcmpServiceCmHandle(cmHandleId: 'cmhandle')] - and: 'cm-handler registration fails: #scenario' - mockLcmEventsCmHandleStateHandler.initiateStateAdvised(*_) >> { throw exception } - when: 'registration is updated' - def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) - then: 'a failure response is received' - response.createdCmHandles.size() == 1 - with(response.createdCmHandles[0]) { - assert it.status == Status.FAILURE - assert it.cmHandle == 'cmhandle' - assert it.ncmpResponseStatus == expectedError - assert it.errorText == expectedErrorText - } - where: - scenario | exception || expectedError | expectedErrorText - 'cm-handle already exist' | AlreadyDefinedException.forDataNodes(["path[@id='cmhandle']"], 'some-context') || CM_HANDLE_ALREADY_EXIST | 'cm-handle already exists' - 'unknown exception while registering cm-handle' | new RuntimeException('Failed') || UNKNOWN_ERROR | 'Failed' - } - - def 'Update CM-Handle: Update Operation Response is added to the response'() { - given: 'a registration to update CmHandles' - def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server', updatedCmHandles: [{}]) - and: 'cm-handle updates can be processed successfully' - def updateOperationResponse = [CmHandleRegistrationResponse.createSuccessResponse('cm-handle-1'), - CmHandleRegistrationResponse.createFailureResponse('cm-handle-2', new Exception("Failed")), - CmHandleRegistrationResponse.createFailureResponse('cm-handle-3', CM_HANDLES_NOT_FOUND), - CmHandleRegistrationResponse.createFailureResponse('cm handle 4', CM_HANDLE_INVALID_ID)] - mockNetworkCmProxyDataServicePropertyHandler.updateCmHandleProperties(_) >> updateOperationResponse - when: 'registration is updated' - def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) - then: 'the response contains updateOperationResponse' - assert response.updatedCmHandles.size() == 4 - assert response.updatedCmHandles.containsAll(updateOperationResponse) - } - - def 'Remove CmHandle Successfully: #scenario'() { - given: 'a registration' - def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server', removedCmHandles: ['cmhandle']) - and: '#scenario' - mockCpsModuleService.deleteSchemaSetsWithCascade(_, ['cmhandle']) >> { if (!schemaSetExist) { throw new SchemaSetNotFoundException('', '') } } - when: 'registration is updated to delete cmhandle' - def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) - then: 'the cmHandle state is updated to "DELETING"' - 1 * mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch(_) >> - { args -> args[0].values()[0] == CmHandleState.DELETING } - then: 'method to delete relevant schema set is called once' - 1 * mockInventoryPersistence.deleteSchemaSetsWithCascade(_) - and: 'method to delete relevant list/list element is called once' - 1 * mockInventoryPersistence.deleteDataNodes(_) - and: 'successful response is received' - assert response.removedCmHandles.size() == 1 - with(response.removedCmHandles[0]) { - assert it.status == Status.SUCCESS - assert it.cmHandle == 'cmhandle' - } - and: 'the cmHandle state is updated to "DELETED"' - 1 * mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch(_) >> - { args -> args[0].values()[0] == CmHandleState.DELETED } - and: 'No cm handles state updates for "upgraded cm handles"' - 1 * mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch([:]) - where: - scenario | schemaSetExist - 'schema-set exists and can be deleted successfully' | true - 'schema-set does not exist' | false - } - - def 'Remove CmHandle: Partial Success'() { - given: 'a registration with three cm-handles to be deleted' - def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server', - removedCmHandles: ['cmhandle1', 'cmhandle2', 'cmhandle3']) - and: 'cm-handle deletion fails on batch' - mockInventoryPersistence.deleteDataNodes(_) >> { throw new RuntimeException("Failed") } - and: 'cm-handle deletion is successful for 1st and 3rd; failed for 2nd' - mockInventoryPersistence.deleteDataNode("/dmi-registry/cm-handles[@id='cmhandle2']") >> { throw new RuntimeException("Failed") } - when: 'registration is updated to delete cmhandles' - def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) - then: 'the cmHandle states are all updated to "DELETING"' - 1 * mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch({ assert it.every { entry -> entry.value == CmHandleState.DELETING } }) - and: 'a response is received for all cm-handles' - response.removedCmHandles.size() == 3 - and: 'successfully de-registered cm handle 1 is removed from in progress map' - 1 * mockModuleSyncStartedOnCmHandles.remove('cmhandle1') - and: 'successfully de-registered cm handle 3 is removed from in progress map even though it was already being removed' - 1 * mockModuleSyncStartedOnCmHandles.remove('cmhandle3') >> 'already in progress' - and: 'failed de-registered cm handle entries should NOT be removed from in progress map' - 0 * mockModuleSyncStartedOnCmHandles.remove('cmhandle2') - and: '1st and 3rd cm-handle deletes successfully' - with(response.removedCmHandles[0]) { - assert it.status == Status.SUCCESS - assert it.cmHandle == 'cmhandle1' - } - with(response.removedCmHandles[2]) { - assert it.status == Status.SUCCESS - assert it.cmHandle == 'cmhandle3' - } - and: '2nd cm-handle deletion fails' - with(response.removedCmHandles[1]) { - assert it.status == Status.FAILURE - assert it.ncmpResponseStatus == UNKNOWN_ERROR - assert it.errorText == 'Failed' - assert it.cmHandle == 'cmhandle2' - } - and: 'the cmHandle state is updated to DELETED for 1st and 3rd' - 1 * mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch({ - assert it.size() == 2 - assert it.every { entry -> entry.value == CmHandleState.DELETED } - }) - and: 'No cm handles state updates for "upgraded cm handles"' - 1 * mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch([:]) - - } - - def 'Remove CmHandle Error Handling: Schema Set Deletion failed'() { - given: 'a registration' - def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server', - removedCmHandles: ['cmhandle']) - and: 'schema set batch deletion failed with unknown error' - mockInventoryPersistence.deleteSchemaSetsWithCascade(_) >> { throw new RuntimeException('Failed') } - and: 'schema set single deletion failed with unknown error' - mockInventoryPersistence.deleteSchemaSetWithCascade(_) >> { throw new RuntimeException('Failed') } - when: 'registration is updated to delete cmhandle' - def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) - then: 'no exception is thrown' - noExceptionThrown() - and: 'cm-handle is not deleted' - 0 * mockInventoryPersistence.deleteDataNodes(_) - and: 'the cmHandle state is not updated to "DELETED"' - 0 * mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch([yangModelCmHandle: CmHandleState.DELETED]) - and: 'a failure response is received' - assert response.removedCmHandles.size() == 1 - with(response.removedCmHandles[0]) { - assert it.status == Status.FAILURE - assert it.cmHandle == 'cmhandle' - assert it.errorText == 'Failed' - assert it.ncmpResponseStatus == UNKNOWN_ERROR - } - } - - def 'Remove CmHandle Error Handling: #scenario'() { - given: 'a registration' - def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server', - removedCmHandles: ['cmhandle']) - and: 'cm-handle deletion fails on batch' - mockInventoryPersistence.deleteDataNodes(_) >> { throw deleteListElementException } - and: 'cm-handle deletion fails on individual delete' - mockInventoryPersistence.deleteDataNode(_) >> { throw deleteListElementException } - when: 'registration is updated to delete cmhandle' - def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) - then: 'a failure response is received' - assert response.removedCmHandles.size() == 1 - with(response.removedCmHandles[0]) { - assert it.status == Status.FAILURE - assert it.cmHandle == 'cmhandle' - assert it.ncmpResponseStatus == expectedError - assert it.errorText == expectedErrorText - } - and: 'the cm handle state is not updated to "DELETED"' - 0 * mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch(_, CmHandleState.DELETED) - where: - scenario | cmHandleId | deleteListElementException || expectedError | expectedErrorText - 'cm-handle does not exist' | 'cmhandle' | new DataNodeNotFoundException('', '', '') || CM_HANDLES_NOT_FOUND | 'cm handle id(s) not found' - 'cm-handle has invalid name' | 'cm handle with space' | new DataValidationException('', '') || CM_HANDLE_INVALID_ID | 'cm-handle has an invalid character(s) in id' - 'an unexpected exception' | 'cmhandle' | new RuntimeException('Failed') || UNKNOWN_ERROR | 'Failed' - } - -} 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 d91c79d33..000000000 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplSpec.groovy +++ /dev/null @@ -1,413 +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 reactor.core.publisher.Mono - -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) - def stubTrustLevelPerDmiPlugin = Stub(Map) - 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 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) >> - Mono.just(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).block() - 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 - 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/NetworkCmProxyDataServicePropertyHandlerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServicePropertyHandlerSpec.groovy deleted file mode 100644 index b0024b19b..000000000 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServicePropertyHandlerSpec.groovy +++ /dev/null @@ -1,285 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2022-2024 Nordix Foundation - * Modifications Copyright (C) 2022 Bell Canada - * 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. - * 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 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 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.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 - -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 - -class NetworkCmProxyDataServicePropertyHandlerSpec 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 logger = Spy(ListAppender) - - void setup() { - def setupLogger = ((Logger) LoggerFactory.getLogger(NetworkCmProxyDataServicePropertyHandler.class)) - setupLogger.addAppender(logger) - setupLogger.setLevel(Level.DEBUG) - logger.start() - // Always accept all alternate IDs - mockAlternateIdChecker.getIdsOfCmHandlesWithRejectedAlternateId(*_) >> [] - } - - void cleanup() { - ((Logger) LoggerFactory.getLogger(NetworkCmProxyDataServicePropertyHandler.class)).detachAndStopAllAppenders() - } - - def static cmHandleId = 'myHandle1' - def static cmHandleXpath = "/dmi-registry/cm-handles[@id='${cmHandleId}']" - - def static propertyDataNodes = [new DataNodeBuilder().withXpath("/dmi-registry/cm-handles[@id='${cmHandleId}']/additional-properties[@name='additionalProp1']").withLeaves(['name': 'additionalProp1', 'value': 'additionalValue1']).build(), - new DataNodeBuilder().withXpath("/dmi-registry/cm-handles[@id='${cmHandleId}']/additional-properties[@name='additionalProp2']").withLeaves(['name': 'additionalProp2', 'value': 'additionalValue2']).build(), - new DataNodeBuilder().withXpath("/dmi-registry/cm-handles[@id='${cmHandleId}']/public-properties[@name='publicProp3']").withLeaves(['name': 'publicProp3', 'value': 'publicValue3']).build(), - new DataNodeBuilder().withXpath("/dmi-registry/cm-handles[@id='${cmHandleId}']/public-properties[@name='publicProp4']").withLeaves(['name': 'publicProp4', 'value': 'publicValue4']).build()] - def static cmHandleDataNodeAsCollection = [new DataNode(xpath: cmHandleXpath, childDataNodes: propertyDataNodes, leaves: ['id': cmHandleId])] - - def 'Update CM Handle Public Properties: #scenario'() { - given: 'the CPS service return a CM handle' - mockInventoryPersistence.getCmHandleDataNodeByCmHandleId(cmHandleId) >> cmHandleDataNodeAsCollection - and: 'an update cm handle request with public properties updates' - def cmHandleUpdateRequest = [new NcmpServiceCmHandle(cmHandleId: cmHandleId, publicProperties: updatedPublicProperties)] - when: 'update data node leaves is called with the update request' - objectUnderTest.updateCmHandleProperties(cmHandleUpdateRequest) - then: 'the replace list method is called with correct params' - 1 * mockInventoryPersistence.replaceListContent(cmHandleXpath, _) >> { args -> - { - assert args[1].leaves.size() == expectedPropertiesAfterUpdate.size() - assert args[1].leaves.containsAll(convertToProperties(expectedPropertiesAfterUpdate)) - } - } - where: 'following public properties updates are made' - scenario | updatedPublicProperties || expectedPropertiesAfterUpdate - 'property added' | ['newPubProp1': 'pub-val'] || [['publicProp3': 'publicValue3'], ['publicProp4': 'publicValue4'], ['newPubProp1': 'pub-val']] - 'property updated' | ['publicProp4': 'newPubVal'] || [['publicProp3': 'publicValue3'], ['publicProp4': 'newPubVal']] - 'property removed' | ['publicProp4': null] || [['publicProp3': 'publicValue3']] - 'property ignored(value is null)' | ['pub-prop': null] || [['publicProp3': 'publicValue3'], ['publicProp4': 'publicValue4']] - } - - def 'Update DMI Properties: #scenario'() { - given: 'the CPS service return a CM handle' - mockInventoryPersistence.getCmHandleDataNodeByCmHandleId(cmHandleId) >> cmHandleDataNodeAsCollection - and: 'an update cm handle request with DMI properties updates' - def cmHandleUpdateRequest = [new NcmpServiceCmHandle(cmHandleId: cmHandleId, dmiProperties: updatedDmiProperties)] - when: 'update data node leaves is called with the update request' - objectUnderTest.updateCmHandleProperties(cmHandleUpdateRequest) - then: 'replace list method should is called with correct params' - expectedCallsToReplaceMethod * mockInventoryPersistence.replaceListContent(cmHandleXpath, _) >> { args -> - { - assert args[1].leaves.size() == expectedPropertiesAfterUpdate.size() - assert args[1].leaves.containsAll(convertToProperties(expectedPropertiesAfterUpdate)) - } - } - where: 'following DMI properties updates are made' - scenario | updatedDmiProperties || expectedPropertiesAfterUpdate | expectedCallsToReplaceMethod - 'property added' | ['newAdditionalProp1': 'add-value'] || [['additionalProp1': 'additionalValue1'], ['additionalProp2': 'additionalValue2'], ['newAdditionalProp1': 'add-value']] | 1 - 'property updated' | ['additionalProp1': 'newValue'] || [['additionalProp2': 'additionalValue2'], ['additionalProp1': 'newValue']] | 1 - 'property removed' | ['additionalProp1': null] || [['additionalProp2': 'additionalValue2']] | 1 - 'property ignored(value is null)' | ['new-prop': null] || [['additionalProp1': 'additionalValue1'], ['additionalProp2': 'additionalValue2']] | 1 - 'no property changes' | [:] || [['additionalProp1': 'additionalValue1'], ['additionalProp2': 'additionalValue2']] | 0 - } - - def 'Update CM Handle Properties, remove all properties: #scenario'() { - given: 'the CPS service return a CM handle' - def cmHandleDataNode = new DataNode(xpath: cmHandleXpath, leaves: ['id': cmHandleId], childDataNodes: originalPropertyDataNodes) - mockInventoryPersistence.getCmHandleDataNodeByCmHandleId(cmHandleId) >> [cmHandleDataNode] - and: 'an update cm handle request that removes all public properties(existing and non-existing)' - def cmHandleUpdateRequest = [new NcmpServiceCmHandle(cmHandleId: cmHandleId, publicProperties: ['publicProp3': null, 'publicProp4': null])] - when: 'update data node leaves is called with the update request' - objectUnderTest.updateCmHandleProperties(cmHandleUpdateRequest) - then: 'the replace list method is not called' - 0 * mockInventoryPersistence.replaceListContent(*_) - then: 'delete data node will be called for any existing property' - expectedCallsToDeleteDataNode * mockInventoryPersistence.deleteDataNode(_) >> { arg -> - { - assert arg[0].contains("@name='publicProp") - } - } - where: 'following public properties updates are made' - scenario | originalPropertyDataNodes || expectedCallsToDeleteDataNode - '2 original properties, both removed' | propertyDataNodes || 2 - 'no original properties' | [] || 0 - } - - def '#scenario error leads to #exception when we try to update cmHandle'() { - given: 'cm handles request' - def cmHandleUpdateRequest = [new NcmpServiceCmHandle(cmHandleId: cmHandleId, publicProperties: [:], dmiProperties: [:])] - and: 'data node cannot be found' - mockInventoryPersistence.getCmHandleDataNodeByCmHandleId(*_) >> { throw exception } - when: 'update data node leaves is called using correct parameters' - def response = objectUnderTest.updateCmHandleProperties(cmHandleUpdateRequest) - then: 'one failed registration response' - response.size() == 1 - and: 'it has expected error details' - with(response.get(0)) { - assert it.status == Status.FAILURE - assert it.cmHandle == cmHandleId - assert it.ncmpResponseStatus == expectedError - assert it.errorText == expectedErrorText - } - where: - scenario | cmHandleId | exception || expectedError | expectedErrorText - 'Cm Handle does not exist' | 'cmHandleId' | new DataNodeNotFoundException(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR) || CM_HANDLES_NOT_FOUND | 'cm handle id(s) not found' - 'Unknown' | 'cmHandleId' | new RuntimeException('Failed') || UNKNOWN_ERROR | 'Failed' - 'Invalid cm handle id' | 'cmHandleId with spaces' | new DataValidationException('Name Validation Error.', cmHandleId + 'contains an invalid character') || CM_HANDLE_INVALID_ID | 'cm-handle has an invalid character(s) in id' - } - - def 'Multiple update operations in a single request'() { - given: 'cm handles request' - def cmHandleUpdateRequest = [new NcmpServiceCmHandle(cmHandleId: cmHandleId, publicProperties: ['publicProp1': "value"], dmiProperties: [:]), - new NcmpServiceCmHandle(cmHandleId: cmHandleId, publicProperties: ['publicProp1': "value"], dmiProperties: [:]), - new NcmpServiceCmHandle(cmHandleId: cmHandleId, publicProperties: ['publicProp1': "value"], dmiProperties: [:])] - and: 'data node can be found for 1st and 3rd cm-handle but not for 2nd cm-handle' - mockInventoryPersistence.getCmHandleDataNodeByCmHandleId(*_) >> cmHandleDataNodeAsCollection >> { - throw new DataNodeNotFoundException(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR) } >> cmHandleDataNodeAsCollection - when: 'update data node leaves is called using correct parameters' - def cmHandleResponseList = objectUnderTest.updateCmHandleProperties(cmHandleUpdateRequest) - then: 'response has 3 values' - cmHandleResponseList.size() == 3 - and: 'the 1st and 3rd requests were processed successfully' - with(cmHandleResponseList.get(0)) { - assert it.status == Status.SUCCESS - assert it.cmHandle == cmHandleId - } - with(cmHandleResponseList.get(2)) { - assert it.status == Status.SUCCESS - assert it.cmHandle == cmHandleId - } - and: 'the 2nd request failed with correct error code' - with(cmHandleResponseList.get(1)) { - assert it.status == Status.FAILURE - assert it.cmHandle == cmHandleId - assert it.ncmpResponseStatus == CM_HANDLES_NOT_FOUND - assert it.errorText == 'cm handle id(s) not found' - } - then: 'the replace list method is called twice' - 2 * mockInventoryPersistence.replaceListContent(cmHandleXpath, _) - } - - def 'Update alternate id of existing CM Handle.'() { - given: 'cm handles request' - def cmHandleUpdateRequest = [new NcmpServiceCmHandle(cmHandleId: cmHandleId, alternateId: 'alt-1')] - and: 'a data node found' - def dataNode = new DataNode(xpath: cmHandleXpath, leaves: ['id': cmHandleId, 'alternate-id': 'alt-1']) - mockInventoryPersistence.getCmHandleDataNodeByCmHandleId(cmHandleId) >> [dataNode] - 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', _, _, ContentType.JSON) >> - { args -> - assert args[3].contains('alt-1') - } - and: 'one successful registration response' - response.size() == 1 - and: 'the response shows success for the given cm handle id' - assert response[0].status == Status.SUCCESS - assert response[0].cmHandle == cmHandleId - } - - def 'Update with rejected alternate id.'() { - given: 'cm handles request' - def updatedNcmpServiceCmHandles = [new NcmpServiceCmHandle(cmHandleId: cmHandleId, alternateId: 'alt-1')] - and: 'a data node found' - def dataNode = new DataNode(xpath: cmHandleXpath, leaves: ['id': cmHandleId, 'alternate-id': 'alt-1']) - mockInventoryPersistence.getCmHandleDataNodeByCmHandleId(cmHandleId) >> [dataNode] - when: 'attempt to update the cm handle' - def response = objectUnderTest.updateCmHandleProperties(updatedNcmpServiceCmHandles) - then: 'the update is NOT delegated to cps data service' - 0 * mockCpsDataService.updateNodeLeaves(*_) - and: 'the alternate id checker rejects the given cm handle (override default setup behavior)' - mockAlternateIdChecker.getIdsOfCmHandlesWithRejectedAlternateId(*_) >> [cmHandleId] - and: 'the response shows a failure for the given cm handle id' - assert response[0].status == Status.FAILURE - assert response[0].cmHandle == cmHandleId - } - - def 'Update CM Handle data producer identifier from #scenario'() { - 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') - 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', _, _, ContentType.JSON) >> { args -> - assert args[3].contains('someDataProducerIdentifier') - } - and: 'correct information is logged' - def lastLoggingEvent = logger.list[0] - assert lastLoggingEvent.level == Level.DEBUG - assert lastLoggingEvent.formattedMessage.contains('Updating data-producer-identifier') - where: 'the following scenarios are attempted' - scenario | oldDataProducerIdentifier - 'null to something' | null - 'blank to something' | '' - } - - 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']) - and: 'an update request with a new data producer identifier' - def ncmpServiceCmHandle = new NcmpServiceCmHandle(cmHandleId: cmHandleId, dataProducerIdentifier: 'someNewDataProducerIdentifier') - when: 'update data producer identifier is called with the update request' - objectUnderTest.updateDataProducerIdentifier(existingCmHandleDataNode, ncmpServiceCmHandle) - then: 'the update node leaves method is not invoked' - 0 * mockCpsDataService.updateNodeLeaves(*_) - and: 'correct information is logged' - def lastLoggingEvent = logger.list[0] - assert lastLoggingEvent.level == Level.WARN - assert lastLoggingEvent.formattedMessage.contains('Unable to update dataProducerIdentifier') - } - - def convertToProperties(expectedPropertiesAfterUpdateAsMap) { - def properties = [].withDefault { [:] } - expectedPropertiesAfterUpdateAsMap.forEach(property -> - property.forEach((key, val) -> { - properties.add(['name': key, 'value': val]) - })) - return properties - } -} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyFacadeSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyFacadeSpec.groovy new file mode 100644 index 000000000..f79e0ee2e --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/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.api.impl + +import org.onap.cps.ncmp.api.impl.operations.DmiDataOperations +import org.onap.cps.ncmp.api.models.CmResourceAddress +import org.onap.cps.ncmp.api.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.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 + +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/NetworkCmProxyInventoryFacadeSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyInventoryFacadeSpec.groovy new file mode 100644 index 000000000..f704dbb3d --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyInventoryFacadeSpec.groovy @@ -0,0 +1,226 @@ +/* + * ============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 com.fasterxml.jackson.databind.ObjectMapper +import org.onap.cps.ncmp.api.ParameterizedCmHandleQueryService +import org.onap.cps.ncmp.api.impl.inventory.CmHandleQueryService +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.InventoryPersistence +import org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory +import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle +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.spi.model.ConditionProperties +import org.onap.cps.utils.JsonObjectMapper +import spock.lang.Specification + +import java.util.stream.Collectors + +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 objectUnderTest = new NetworkCmProxyInventoryFacade(mockCmHandleRegistrationService, mockCmHandleQueryService, mockParameterizedCmHandleQueryService, mockInventoryPersistence, spiedJsonObjectMapper) + + 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 + 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: '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 '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 '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 return with a data node list' + mockParameterizedCmHandleQueryService.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(cmHandle -> cmHandle.cmHandleId).collect(Collectors.toSet()) == ['cm-handle-id-1'] as Set + } + + 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/ParameterizedCmHandleQueryServiceSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/ParameterizedCmHandleQueryServiceSpec.groovy new file mode 100644 index 000000000..a928a0432 --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/ParameterizedCmHandleQueryServiceSpec.groovy @@ -0,0 +1,222 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2022-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 + +import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DMI_REGISTRY_PARENT + +import org.onap.cps.cpspath.parser.PathParsingException +import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle +import org.onap.cps.ncmp.api.impl.inventory.CmHandleQueryService +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.spi.FetchDescendantsOption +import org.onap.cps.spi.exceptions.DataInUseException +import org.onap.cps.spi.exceptions.DataValidationException +import org.onap.cps.spi.model.ConditionProperties +import org.onap.cps.spi.model.DataNode +import spock.lang.Specification + +class ParameterizedCmHandleQueryServiceSpec extends Specification { + + 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 ParameterizedCmHandleQueryServiceImpl(cmHandleQueries, mockInventoryPersistence) + def objectUnderTestWithPartiallyMockedQueries = new ParameterizedCmHandleQueryServiceImpl(partiallyMockedCmHandleQueries, mockInventoryPersistence) + + def 'Query cm handle ids with cpsPath.'() { + given: 'a cmHandleWithCpsPath condition property' + def cmHandleQueryParameters = new CmHandleQueryServiceParameters() + def conditionProperties = createConditionProperties('cmHandleWithCpsPath', [['cpsPath' : '/some/cps/path']]) + cmHandleQueryParameters.setCmHandleQueryParameters([conditionProperties]) + and: 'the query get the cm handle datanodes excluding all descendants returns a datanode' + cmHandleQueries.queryCmHandleAncestorsByCpsPath('/some/cps/path', FetchDescendantsOption.OMIT_DESCENDANTS) >> [new DataNode(leaves: ['id':'some-cmhandle-id'])] + when: 'the query is executed for cm handle ids' + def result = objectUnderTest.queryCmHandleIds(cmHandleQueryParameters) + then: 'the correct expected cm handles ids are returned' + assert result == ['some-cmhandle-id'] as Set + } + + def 'Cm handle ids query with error: #scenario.'() { + given: 'a cmHandleWithCpsPath condition property' + def cmHandleQueryParameters = new CmHandleQueryServiceParameters() + def conditionProperties = createConditionProperties('cmHandleWithCpsPath', [['cpsPath' : '/some/cps/path']]) + cmHandleQueryParameters.setCmHandleQueryParameters([conditionProperties]) + and: 'cmHandleQueries throws a path parsing exception' + cmHandleQueries.queryCmHandleAncestorsByCpsPath('/some/cps/path', FetchDescendantsOption.OMIT_DESCENDANTS) >> { throw thrownException } + when: 'the query is executed for cm handle ids' + objectUnderTest.queryCmHandleIds(cmHandleQueryParameters) + then: 'a data validation exception is thrown' + thrown(expectedException) + where: 'the following data is used' + scenario | thrownException || expectedException + 'PathParsingException' | new PathParsingException('some message', 'some details') || DataValidationException + 'any other Exception' | new DataInUseException('some message', 'some details') || DataInUseException + } + + def 'Cm handle ids cpsPath query for private properties (not allowed).'() { + given: 'a CpsPath condition property for private properties' + def cmHandleQueryParameters = new CmHandleQueryServiceParameters() + def conditionProperties = createConditionProperties('cmHandleWithCpsPath', [['cpsPath' : '/additional-properties']]) + cmHandleQueryParameters.setCmHandleQueryParameters([conditionProperties]) + when: 'the query is executed for cm handle ids' + def result = objectUnderTest.queryCmHandleIds(cmHandleQueryParameters) + then: 'empty result is returned' + assert result.isEmpty() + } + + def 'Query cm handle ids with module names when #scenario from query.'() { + given: 'a modules condition property' + def cmHandleQueryParameters = new CmHandleQueryServiceParameters() + def conditionProperties = createConditionProperties('hasAllModules', [['moduleName': 'some-module-name']]) + cmHandleQueryParameters.setCmHandleQueryParameters([conditionProperties]) + when: 'the query is executed for cm handle ids' + def result = objectUnderTest.queryCmHandleIds(cmHandleQueryParameters) + then: 'the inventory service is called with the correct module names' + 1 * mockInventoryPersistence.getCmHandleIdsWithGivenModules(['some-module-name']) >> cmHandleIdsFromService + and: 'the correct expected cm handles ids are returned' + assert result.size() == cmHandleIdsFromService.size() + assert result.containsAll(cmHandleIdsFromService) + where: 'the following data is used' + scenario | cmHandleIdsFromService + 'One anchor returned' | ['some-cmhandle-id'] + 'No anchors are returned' | [] + } + + def 'Query cm handles with some trust level query parameters'() { + given: 'a trust level condition property' + def trustLevelQueryParameters = new CmHandleQueryServiceParameters() + def trustLevelConditionProperties = createConditionProperties('cmHandleWithTrustLevel', [['trustLevel': 'COMPLETE'] as Map]) + trustLevelQueryParameters.setCmHandleQueryParameters([trustLevelConditionProperties]) + when: 'the query is being executed' + objectUnderTest.queryCmHandleIds(trustLevelQueryParameters) + then: 'the query is being delegated to the cm handle query service with correct parameter' + 1 * cmHandleQueries.queryCmHandlesByTrustLevel(['trustLevel': 'COMPLETE'] as Map) + } + + def 'Query cm handle details with module names when #scenario from query.'() { + given: 'a modules condition property' + def cmHandleQueryParameters = new CmHandleQueryServiceParameters() + def conditionProperties = createConditionProperties('hasAllModules', [['moduleName': 'some-module-name']]) + cmHandleQueryParameters.setCmHandleQueryParameters([conditionProperties]) + when: 'the query is executed for cm handle ids' + def result = objectUnderTest.queryCmHandles(cmHandleQueryParameters) + then: 'the inventory service is called with the correct module names' + 1 * mockInventoryPersistence.getCmHandleIdsWithGivenModules(['some-module-name']) >> ['ch1'] + and: 'the inventory service is called with teh correct if and returns a yang model cm handle' + 1 * mockInventoryPersistence.getYangModelCmHandles(['ch1']) >> + [new YangModelCmHandle(id: 'abc', dmiProperties: [new YangModelCmHandle.Property('name','value')], publicProperties: [])] + and: 'the expected cm handle(s) are returned as NCMP Service cm handles' + assert result[0] instanceof NcmpServiceCmHandle + assert result.size() == 1 + assert result[0].dmiProperties == [name:'value'] + } + + def 'Query cm handle ids when the query is empty.'() { + given: 'We use an empty query' + def cmHandleQueryParameters = new CmHandleQueryServiceParameters() + and: 'the inventory persistence returns the dmi registry datanode with just ids' + mockInventoryPersistence.getDataNode(NCMP_DMI_REGISTRY_PARENT, FetchDescendantsOption.DIRECT_CHILDREN_ONLY) >> [dmiRegistry] + when: 'the query is executed for both cm handle ids' + def result = objectUnderTest.queryCmHandleIds(cmHandleQueryParameters) + then: 'the correct expected cm handles are returned' + assert result.containsAll('PNFDemo1', 'PNFDemo2', 'PNFDemo3', 'PNFDemo4') + } + + def 'Query cm handle details when the query is empty.'() { + given: 'We use an empty query' + def cmHandleQueryParameters = new CmHandleQueryServiceParameters() + and: 'the inventory persistence returns the dmi registry datanode with just ids' + mockInventoryPersistence.getDataNode(NCMP_DMI_REGISTRY_PARENT) >> [dmiRegistry] + when: 'the query is executed for both cm handle details' + def result = objectUnderTest.queryCmHandles(cmHandleQueryParameters) + then: 'the correct cm handles are returned' + assert result.size() == 4 + assert result.cmHandleId.containsAll('PNFDemo1', 'PNFDemo2', 'PNFDemo3', 'PNFDemo4') + } + + def 'Query CMHandleId with #scenario.' () { + given: 'a query object created with #condition' + def cmHandleQueryParameters = new CmHandleQueryServiceParameters() + def conditionProperties = createConditionProperties(conditionName, [['some-key': 'some-value']]) + cmHandleQueryParameters.setCmHandleQueryParameters([conditionProperties]) + and: 'the inventoryPersistence returns different CmHandleIds' + partiallyMockedCmHandleQueries.queryCmHandlePublicProperties(*_) >> cmHandlesWithMatchingPublicProperties + partiallyMockedCmHandleQueries.queryCmHandleAdditionalProperties(*_) >> cmHandlesWithMatchingPrivateProperties + when: 'the query executed' + def result = objectUnderTestWithPartiallyMockedQueries.queryCmHandleIdsForInventory(cmHandleQueryParameters) + then: 'the expected number of results are returned.' + assert result.size() == expectedCmHandleIdsSize + where: 'the following data is used' + scenario | conditionName | cmHandlesWithMatchingPublicProperties | cmHandlesWithMatchingPrivateProperties || expectedCmHandleIdsSize + 'all properties, only public matching' | 'hasAllProperties' | ['h1', 'h2'] | null || 2 + 'all properties, no matching cm handles' | 'hasAllProperties' | [] | [] || 0 + 'additional properties, some matching cm handles' | 'hasAllAdditionalProperties' | [] | ['h1', 'h2'] || 2 + 'additional properties, no matching cm handles' | 'hasAllAdditionalProperties' | null | [] || 0 + } + + def 'Retrieve CMHandleIds by different DMI properties with #scenario.' () { + given: 'a query object created with dmi plugin as condition' + def cmHandleQueryParameters = new CmHandleQueryServiceParameters() + def conditionProperties = createConditionProperties('cmHandleWithDmiPlugin', [['some-key': 'some-value']]) + cmHandleQueryParameters.setCmHandleQueryParameters([conditionProperties]) + and: 'the inventoryPersistence returns different CmHandleIds' + partiallyMockedCmHandleQueries.getCmHandleIdsByDmiPluginIdentifier(*_) >> cmHandleQueryResult + when: 'the query executed' + def result = objectUnderTestWithPartiallyMockedQueries.queryCmHandleIdsForInventory(cmHandleQueryParameters) + then: 'the expected number of results are returned.' + assert result.size() == expectedCmHandleIdsSize + where: 'the following data is used' + scenario | cmHandleQueryResult || expectedCmHandleIdsSize + 'some matches' | ['h1','h2'] || 2 + 'no matches' | [] || 0 + } + + def 'Combine two query results where #scenario.'() { + when: 'two query results in the form of a map of NcmpServiceCmHandles are combined into a single query result' + def result = objectUnderTest.combineCmHandleQueryResults(firstQuery, secondQuery) + then: 'the returned result is the same as the expected result' + result == expectedResult + where: + scenario | firstQuery | secondQuery || expectedResult + 'two queries with unique and non unique entries exist' | ['PNFDemo', 'PNFDemo2'] | ['PNFDemo', 'PNFDemo3'] || ['PNFDemo'] + 'the first query contains entries and second query is empty' | ['PNFDemo', 'PNFDemo2'] | [] || [] + 'the second query contains entries and first query is empty' | [] | ['PNFDemo', 'PNFDemo3'] || [] + 'the first query contains entries and second query is null' | ['PNFDemo', 'PNFDemo2'] | null || ['PNFDemo', 'PNFDemo2'] + 'the second query contains entries and first query is null' | null | ['PNFDemo', 'PNFDemo3'] || ['PNFDemo', 'PNFDemo3'] + 'both queries are empty' | [] | [] || [] + 'both queries are null' | null | null || null + } + + def createConditionProperties(String conditionName, List> conditionParameters) { + return new ConditionProperties(conditionName : conditionName, conditionParameters : conditionParameters) + } + + def static createDataNodeList(dataNodeIds) { + def dataNodes =[] + dataNodeIds.each{ dataNodes << new DataNode(xpath: "/dmi-registry/cm-handles[@id='${it}']", leaves: ['id':it]) } + return dataNodes + } +} 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 2c2212773..bb73c6879 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 @@ -21,33 +21,32 @@ package org.onap.cps.ncmp.api.impl.client -import static org.onap.cps.ncmp.api.NcmpResponseStatus.UNKNOWN_ERROR -import static org.onap.cps.ncmp.api.impl.operations.OperationType.CREATE -import static org.onap.cps.ncmp.api.impl.operations.OperationType.PATCH -import static org.onap.cps.ncmp.api.impl.operations.OperationType.READ -import static org.onap.cps.ncmp.api.impl.operations.RequiredDmiService.DATA -import static org.springframework.http.HttpStatus.SERVICE_UNAVAILABLE -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 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.DmiProperties import org.onap.cps.ncmp.api.impl.exception.DmiClientRequestException import org.onap.cps.ncmp.api.impl.exception.InvalidDmiResourceUrlException -import org.onap.cps.ncmp.api.impl.config.DmiProperties import org.onap.cps.ncmp.utils.TestUtils import org.onap.cps.utils.JsonObjectMapper import org.springframework.http.HttpHeaders import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity -import org.springframework.web.client.HttpServerErrorException import org.springframework.web.reactive.function.client.WebClient -import org.springframework.web.reactive.function.client.WebClientResponseException 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.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.impl.operations.OperationType.CREATE +import static org.onap.cps.ncmp.api.impl.operations.OperationType.PATCH +import static org.onap.cps.ncmp.api.impl.operations.OperationType.READ +import static org.onap.cps.ncmp.api.impl.operations.RequiredDmiService.DATA +import static org.onap.cps.ncmp.api.impl.operations.RequiredDmiService.MODEL + class DmiRestClientSpec extends Specification { static final NO_AUTH_HEADER = null @@ -61,7 +60,6 @@ class DmiRestClientSpec extends Specification { def mockRequestBody = Mock(WebClient.RequestBodyUriSpec) def mockResponse = Mock(WebClient.ResponseSpec) - def responseBody = [message: 'Success'] def mockDmiProperties = Mock(DmiProperties) JsonObjectMapper jsonObjectMapper = new JsonObjectMapper(new ObjectMapper()) @@ -75,34 +73,31 @@ class DmiRestClientSpec extends Specification { mockRequestBody.retrieve() >> mockResponse } - def 'DMI POST Operation with JSON for status #httpStatusCode'() { - given: 'the web client returns a valid response entity for the expected parameters' + 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<>(responseBody, httpStatusCode)) - when: 'POST operation is invoked' - def response = objectUnderTest.postOperationWithJsonData(DATA, '/my/url', 'some json', READ, NO_AUTH_HEADER) + 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' - assert response.statusCode == httpStatusCode - assert response.body == responseBody - where: 'the following status codes are used' - httpStatusCode << [HttpStatus.OK, HttpStatus.CREATED, HttpStatus.ACCEPTED] + assert response.statusCode == HttpStatus.I_AM_A_TEAPOT + assert response.body == 'from Data service' } - def 'Failing DMI POST operation for server error'() { - given: 'the web client throws an exception' - mockDataServicesWebClient.post() >> { throw new HttpServerErrorException(SERVICE_UNAVAILABLE, null, null, null) } - when: 'POST operation is invoked' - objectUnderTest.postOperationWithJsonData(DATA, '/some', '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' - thrown.ncmpResponseStatus.code == '102' - thrown.httpStatusCode == 503 + 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.postOperationWithJsonData(DATA, '/invalid dmi url', null, null, NO_AUTH_HEADER) + 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' @@ -117,7 +112,7 @@ class DmiRestClientSpec extends Specification { mockDataServicesWebClient.post() >> mockRequestBody mockResponse.toEntity(Object.class) >> Mono.error(exceptionType) when: 'POST operation is invoked' - objectUnderTest.postOperationWithJsonData(DATA, '/my/url', 'some json', READ, NO_AUTH_HEADER) + 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' @@ -176,4 +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 } -} \ No newline at end of file +} 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/api/impl/inventory/CmHandleQueriesImplSpec.groovy deleted file mode 100644 index 2f9d26494..000000000 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/CmHandleQueriesImplSpec.groovy +++ /dev/null @@ -1,206 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2022-2023 Nordix Foundation - * 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.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.spi.CpsDataPersistenceService -import org.onap.cps.spi.model.DataNode -import spock.lang.Shared -import spock.lang.Specification - -class CmHandleQueriesImplSpec extends Specification { - - def mockCpsDataPersistenceService = Mock(CpsDataPersistenceService) - - def trustLevelPerDmiPlugin = [:] - - def trustLevelPerCmHandle = [ 'PNFDemo': TrustLevel.COMPLETE, 'PNFDemo2': TrustLevel.NONE, 'PNFDemo4': TrustLevel.NONE ] - - def mockCpsValidator = Mock(CpsValidator) - - def objectUnderTest = new CmHandleQueriesImpl(mockCpsDataPersistenceService, trustLevelPerDmiPlugin, trustLevelPerCmHandle, mockCpsValidator) - - @Shared - def static sampleDataNodes = [new DataNode()] - - def dataNodeWithPrivateField = '//additional-properties[@name=\"Contact3\" and @value=\"newemailforstore3@bookstore.com\"]/ancestor::cm-handles' - - def static pnfDemo = createDataNode('PNFDemo') - def static pnfDemo2 = createDataNode('PNFDemo2') - def static pnfDemo3 = createDataNode('PNFDemo3') - def static pnfDemo4 = createDataNode('PNFDemo4') - def static pnfDemo5 = createDataNode('PNFDemo5') - - def 'Query CmHandles with public properties query pair.'() { - given: 'the DataNodes queried for a given cpsPath are returned from the persistence service.' - mockResponses() - when: 'a query on cmhandle public properties is performed with a public property pair' - def result = objectUnderTest.queryCmHandlePublicProperties(publicPropertyPairs) - then: 'the correct cm handle data objects are returned' - result.containsAll(expectedCmHandleIds) - result.size() == expectedCmHandleIds.size() - where: 'the following data is used' - scenario | publicPropertyPairs || expectedCmHandleIds - 'single property matches' | [Contact: 'newemailforstore@bookstore.com'] || ['PNFDemo', 'PNFDemo2', 'PNFDemo4'] - 'public property does not match' | [wont_match: 'wont_match'] || [] - '2 properties, only one match' | [Contact: 'newemailforstore@bookstore.com', Contact2: 'newemailforstore2@bookstore.com'] || ['PNFDemo4'] - '2 properties, no matches' | [Contact: 'newemailforstore@bookstore.com', Contact2: ''] || [] - } - - def 'Query cm handles on trust level'() { - given: 'query properties for trust level COMPLETE' - def trustLevelPropertyQueryPairs = ['trustLevel' : TrustLevel.COMPLETE.toString()] - and: 'the dmi cache has been initialised and "knows" about my-dmi-plugin-identifier' - trustLevelPerDmiPlugin.put('my-dmi-plugin-identifier', TrustLevel.COMPLETE) - and: 'the DataNodes queried for a given cpsPath are returned from the persistence service' - mockResponses() - when: 'the query is run' - def result = objectUnderTest.queryCmHandlesByTrustLevel(trustLevelPropertyQueryPairs) - then: 'the result contain trusted PNFDemo' - assert result.size() == 1 - assert result[0] == 'PNFDemo' - } - - def 'Query CmHandles using empty public properties query pair.'() { - when: 'a query on CmHandle public properties is executed using an empty map' - def result = objectUnderTest.queryCmHandlePublicProperties([:]) - then: 'no cm handles are returned' - result.size() == 0 - } - - def 'Query CmHandles using empty private properties query pair.'() { - when: 'a query on CmHandle private properties is executed using an empty map' - def result = objectUnderTest.queryCmHandleAdditionalProperties([:]) - then: 'no cm handles are returned' - result.size() == 0 - } - - def 'Query CmHandles by a private field\'s value.'() { - given: 'a data node exists with a certain additional-property' - mockCpsDataPersistenceService.queryDataNodes(_, _, dataNodeWithPrivateField, _) >> [pnfDemo5] - when: 'a query on CmHandle private properties is executed using a map' - def result = objectUnderTest.queryCmHandleAdditionalProperties(['Contact3': 'newemailforstore3@bookstore.com']) - then: 'one cm handle is returned' - result.size() == 1 - } - - def 'Get CmHandles by it\'s state.'() { - given: 'a cm handle state to query' - def cmHandleState = CmHandleState.ADVISED - and: 'the persistence service returns a list of data nodes' - mockCpsDataPersistenceService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, - '//state[@cm-handle-state="ADVISED"]/ancestor::cm-handles', INCLUDE_ALL_DESCENDANTS) >> sampleDataNodes - when: 'cm handles are fetched by state' - def result = objectUnderTest.queryCmHandlesByState(cmHandleState) - then: 'the returned result matches the result from the persistence service' - assert result == sampleDataNodes - } - - def 'Check the state of a cmHandle when #scenario.'() { - given: 'a cm handle state to compare' - def cmHandleState = state - and: 'the persistence service returns a list of data nodes' - mockCpsDataPersistenceService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, - NCMP_DMI_REGISTRY_PARENT + '/cm-handles[@id=\'some-cm-handle\']/state', - OMIT_DESCENDANTS) >> [new DataNode(leaves: ['cm-handle-state': 'READY'])] - when: 'cm handles are compared by state' - def result = objectUnderTest.cmHandleHasState('some-cm-handle', cmHandleState) - then: 'the returned result matches the expected result from the persistence service' - result == expectedResult - where: - scenario | state || expectedResult - 'the provided state matches' | CmHandleState.READY || true - 'the provided state does not match'| CmHandleState.DELETED || false - } - - def 'Get Cm Handles state by Cm-Handle Id'() { - given: 'a cm handle state to query' - def cmHandleState = CmHandleState.READY - and: 'cps data service returns a list of data nodes' - mockCpsDataPersistenceService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, - NCMP_DMI_REGISTRY_PARENT + '/cm-handles[@id=\'some-cm-handle\']/state', - OMIT_DESCENDANTS) >> [new DataNode(leaves: ['cm-handle-state': 'READY'])] - when: 'cm handles are fetched by state and id' - def result = objectUnderTest.getCmHandleState('some-cm-handle') - then: 'the returned result is a list of data nodes returned by cps data service' - assert result == new DataNode(leaves: ['cm-handle-state': 'READY']) - } - - def 'Retrieve Cm Handles By Operational Sync State : UNSYNCHRONIZED'() { - given: 'a cm handle state to query' - def cmHandleState = CmHandleState.READY - and: 'cps data service returns a list of data nodes' - mockCpsDataPersistenceService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, - '//state/datastores/operational[@sync-state="'+'UNSYNCHRONIZED'+'"]/ancestor::cm-handles', OMIT_DESCENDANTS) >> sampleDataNodes - when: 'cm handles are fetched by the UNSYNCHRONIZED operational sync state' - def result = objectUnderTest.queryCmHandlesByOperationalSyncState(DataStoreSyncState.UNSYNCHRONIZED) - then: 'the returned result is a list of data nodes returned by cps data service' - assert result == sampleDataNodes - } - - def 'Retrieve cm handle by cps path '() { - given: 'a cm handle state to query based on the cps path' - def cmHandleDataNode = new DataNode(xpath: 'xpath', leaves: ['cm-handle-state': 'LOCKED']) - def cpsPath = '//cps-path' - and: 'cps data service returns a valid data node' - mockCpsDataPersistenceService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, - cpsPath + '/ancestor::cm-handles', INCLUDE_ALL_DESCENDANTS) - >> Arrays.asList(cmHandleDataNode) - when: 'get cm handles by cps path is invoked' - def result = objectUnderTest.queryCmHandleAncestorsByCpsPath(cpsPath, INCLUDE_ALL_DESCENDANTS) - then: 'the returned result is a list of data nodes returned by cps data service' - assert result.contains(cmHandleDataNode) - } - - def 'Get all cm handles by dmi plugin identifier'() { - given: 'the DataNodes queried for a given cpsPath are returned from the persistence service.' - mockResponses() - when: 'cm Handles are fetched for a given dmi plugin identifier' - def result = objectUnderTest.getCmHandleIdsByDmiPluginIdentifier('my-dmi-plugin-identifier') - then: 'result is the correct size' - assert result.size() == 3 - and: 'result contains the correct cm handles' - assert result.containsAll('PNFDemo', 'PNFDemo2', 'PNFDemo4') - } - - void mockResponses() { - mockCpsDataPersistenceService.queryDataNodes(_, _, '//public-properties[@name=\"Contact\" and @value=\"newemailforstore@bookstore.com\"]/ancestor::cm-handles', _) >> [pnfDemo, pnfDemo2, pnfDemo4] - mockCpsDataPersistenceService.queryDataNodes(_, _, '//public-properties[@name=\"wont_match\" and @value=\"wont_match\"]/ancestor::cm-handles', _) >> [] - mockCpsDataPersistenceService.queryDataNodes(_, _, '//public-properties[@name=\"Contact2\" and @value=\"newemailforstore2@bookstore.com\"]/ancestor::cm-handles', _) >> [pnfDemo4] - mockCpsDataPersistenceService.queryDataNodes(_, _, '//public-properties[@name=\"Contact2\" and @value=\"\"]/ancestor::cm-handles', _) >> [] - mockCpsDataPersistenceService.queryDataNodes(_, _, '//state[@cm-handle-state=\"READY\"]/ancestor::cm-handles', _) >> [pnfDemo, pnfDemo3] - mockCpsDataPersistenceService.queryDataNodes(_, _, '//state[@cm-handle-state=\"LOCKED\"]/ancestor::cm-handles', _) >> [pnfDemo2, pnfDemo4] - mockCpsDataPersistenceService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, '/dmi-registry/cm-handles[@dmi-service-name=\'my-dmi-plugin-identifier\']', OMIT_DESCENDANTS) >> [pnfDemo, pnfDemo2] - mockCpsDataPersistenceService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, '/dmi-registry/cm-handles[@dmi-data-service-name=\'my-dmi-plugin-identifier\']', OMIT_DESCENDANTS) >> [pnfDemo, pnfDemo4] - mockCpsDataPersistenceService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, '/dmi-registry/cm-handles[@dmi-model-service-name=\'my-dmi-plugin-identifier\']', OMIT_DESCENDANTS) >> [pnfDemo2, pnfDemo4] - } - - def static createDataNode(dataNodeId) { - return new DataNode(xpath: '/dmi-registry/cm-handles[@id=\'' + dataNodeId + '\']', leaves: ['id':dataNodeId]) - } -} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/CmHandleQueryServiceImplSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/CmHandleQueryServiceImplSpec.groovy new file mode 100644 index 000000000..bdf431855 --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/CmHandleQueryServiceImplSpec.groovy @@ -0,0 +1,206 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2022-2023 Nordix Foundation + * 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.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.spi.CpsDataPersistenceService +import org.onap.cps.spi.model.DataNode +import spock.lang.Shared +import spock.lang.Specification + +class CmHandleQueryServiceImplSpec extends Specification { + + def mockCpsDataPersistenceService = Mock(CpsDataPersistenceService) + + def trustLevelPerDmiPlugin = [:] + + def trustLevelPerCmHandle = [ 'PNFDemo': TrustLevel.COMPLETE, 'PNFDemo2': TrustLevel.NONE, 'PNFDemo4': TrustLevel.NONE ] + + def mockCpsValidator = Mock(CpsValidator) + + def objectUnderTest = new CmHandleQueryServiceImpl(mockCpsDataPersistenceService, trustLevelPerDmiPlugin, trustLevelPerCmHandle, mockCpsValidator) + + @Shared + def static sampleDataNodes = [new DataNode()] + + def dataNodeWithPrivateField = '//additional-properties[@name=\"Contact3\" and @value=\"newemailforstore3@bookstore.com\"]/ancestor::cm-handles' + + def static pnfDemo = createDataNode('PNFDemo') + def static pnfDemo2 = createDataNode('PNFDemo2') + def static pnfDemo3 = createDataNode('PNFDemo3') + def static pnfDemo4 = createDataNode('PNFDemo4') + def static pnfDemo5 = createDataNode('PNFDemo5') + + def 'Query CmHandles with public properties query pair.'() { + given: 'the DataNodes queried for a given cpsPath are returned from the persistence service.' + mockResponses() + when: 'a query on cmhandle public properties is performed with a public property pair' + def result = objectUnderTest.queryCmHandlePublicProperties(publicPropertyPairs) + then: 'the correct cm handle data objects are returned' + result.containsAll(expectedCmHandleIds) + result.size() == expectedCmHandleIds.size() + where: 'the following data is used' + scenario | publicPropertyPairs || expectedCmHandleIds + 'single property matches' | [Contact: 'newemailforstore@bookstore.com'] || ['PNFDemo', 'PNFDemo2', 'PNFDemo4'] + 'public property does not match' | [wont_match: 'wont_match'] || [] + '2 properties, only one match' | [Contact: 'newemailforstore@bookstore.com', Contact2: 'newemailforstore2@bookstore.com'] || ['PNFDemo4'] + '2 properties, no matches' | [Contact: 'newemailforstore@bookstore.com', Contact2: ''] || [] + } + + def 'Query cm handles on trust level'() { + given: 'query properties for trust level COMPLETE' + def trustLevelPropertyQueryPairs = ['trustLevel' : TrustLevel.COMPLETE.toString()] + and: 'the dmi cache has been initialised and "knows" about my-dmi-plugin-identifier' + trustLevelPerDmiPlugin.put('my-dmi-plugin-identifier', TrustLevel.COMPLETE) + and: 'the DataNodes queried for a given cpsPath are returned from the persistence service' + mockResponses() + when: 'the query is run' + def result = objectUnderTest.queryCmHandlesByTrustLevel(trustLevelPropertyQueryPairs) + then: 'the result contain trusted PNFDemo' + assert result.size() == 1 + assert result[0] == 'PNFDemo' + } + + def 'Query CmHandles using empty public properties query pair.'() { + when: 'a query on CmHandle public properties is executed using an empty map' + def result = objectUnderTest.queryCmHandlePublicProperties([:]) + then: 'no cm handles are returned' + result.size() == 0 + } + + def 'Query CmHandles using empty private properties query pair.'() { + when: 'a query on CmHandle private properties is executed using an empty map' + def result = objectUnderTest.queryCmHandleAdditionalProperties([:]) + then: 'no cm handles are returned' + result.size() == 0 + } + + def 'Query CmHandles by a private field\'s value.'() { + given: 'a data node exists with a certain additional-property' + mockCpsDataPersistenceService.queryDataNodes(_, _, dataNodeWithPrivateField, _) >> [pnfDemo5] + when: 'a query on CmHandle private properties is executed using a map' + def result = objectUnderTest.queryCmHandleAdditionalProperties(['Contact3': 'newemailforstore3@bookstore.com']) + then: 'one cm handle is returned' + result.size() == 1 + } + + def 'Get CmHandles by it\'s state.'() { + given: 'a cm handle state to query' + def cmHandleState = CmHandleState.ADVISED + and: 'the persistence service returns a list of data nodes' + mockCpsDataPersistenceService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, + '//state[@cm-handle-state="ADVISED"]/ancestor::cm-handles', INCLUDE_ALL_DESCENDANTS) >> sampleDataNodes + when: 'cm handles are fetched by state' + def result = objectUnderTest.queryCmHandlesByState(cmHandleState) + then: 'the returned result matches the result from the persistence service' + assert result == sampleDataNodes + } + + def 'Check the state of a cmHandle when #scenario.'() { + given: 'a cm handle state to compare' + def cmHandleState = state + and: 'the persistence service returns a list of data nodes' + mockCpsDataPersistenceService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, + NCMP_DMI_REGISTRY_PARENT + '/cm-handles[@id=\'some-cm-handle\']/state', + OMIT_DESCENDANTS) >> [new DataNode(leaves: ['cm-handle-state': 'READY'])] + when: 'cm handles are compared by state' + def result = objectUnderTest.cmHandleHasState('some-cm-handle', cmHandleState) + then: 'the returned result matches the expected result from the persistence service' + result == expectedResult + where: + scenario | state || expectedResult + 'the provided state matches' | CmHandleState.READY || true + 'the provided state does not match'| CmHandleState.DELETED || false + } + + def 'Get Cm Handles state by Cm-Handle Id'() { + given: 'a cm handle state to query' + def cmHandleState = CmHandleState.READY + and: 'cps data service returns a list of data nodes' + mockCpsDataPersistenceService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, + NCMP_DMI_REGISTRY_PARENT + '/cm-handles[@id=\'some-cm-handle\']/state', + OMIT_DESCENDANTS) >> [new DataNode(leaves: ['cm-handle-state': 'READY'])] + when: 'cm handles are fetched by state and id' + def result = objectUnderTest.getCmHandleState('some-cm-handle') + then: 'the returned result is a list of data nodes returned by cps data service' + assert result == new DataNode(leaves: ['cm-handle-state': 'READY']) + } + + def 'Retrieve Cm Handles By Operational Sync State : UNSYNCHRONIZED'() { + given: 'a cm handle state to query' + def cmHandleState = CmHandleState.READY + and: 'cps data service returns a list of data nodes' + mockCpsDataPersistenceService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, + '//state/datastores/operational[@sync-state="'+'UNSYNCHRONIZED'+'"]/ancestor::cm-handles', OMIT_DESCENDANTS) >> sampleDataNodes + when: 'cm handles are fetched by the UNSYNCHRONIZED operational sync state' + def result = objectUnderTest.queryCmHandlesByOperationalSyncState(DataStoreSyncState.UNSYNCHRONIZED) + then: 'the returned result is a list of data nodes returned by cps data service' + assert result == sampleDataNodes + } + + def 'Retrieve cm handle by cps path '() { + given: 'a cm handle state to query based on the cps path' + def cmHandleDataNode = new DataNode(xpath: 'xpath', leaves: ['cm-handle-state': 'LOCKED']) + def cpsPath = '//cps-path' + and: 'cps data service returns a valid data node' + mockCpsDataPersistenceService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, + cpsPath + '/ancestor::cm-handles', INCLUDE_ALL_DESCENDANTS) + >> Arrays.asList(cmHandleDataNode) + when: 'get cm handles by cps path is invoked' + def result = objectUnderTest.queryCmHandleAncestorsByCpsPath(cpsPath, INCLUDE_ALL_DESCENDANTS) + then: 'the returned result is a list of data nodes returned by cps data service' + assert result.contains(cmHandleDataNode) + } + + def 'Get all cm handles by dmi plugin identifier'() { + given: 'the DataNodes queried for a given cpsPath are returned from the persistence service.' + mockResponses() + when: 'cm Handles are fetched for a given dmi plugin identifier' + def result = objectUnderTest.getCmHandleIdsByDmiPluginIdentifier('my-dmi-plugin-identifier') + then: 'result is the correct size' + assert result.size() == 3 + and: 'result contains the correct cm handles' + assert result.containsAll('PNFDemo', 'PNFDemo2', 'PNFDemo4') + } + + void mockResponses() { + mockCpsDataPersistenceService.queryDataNodes(_, _, '//public-properties[@name=\"Contact\" and @value=\"newemailforstore@bookstore.com\"]/ancestor::cm-handles', _) >> [pnfDemo, pnfDemo2, pnfDemo4] + mockCpsDataPersistenceService.queryDataNodes(_, _, '//public-properties[@name=\"wont_match\" and @value=\"wont_match\"]/ancestor::cm-handles', _) >> [] + mockCpsDataPersistenceService.queryDataNodes(_, _, '//public-properties[@name=\"Contact2\" and @value=\"newemailforstore2@bookstore.com\"]/ancestor::cm-handles', _) >> [pnfDemo4] + mockCpsDataPersistenceService.queryDataNodes(_, _, '//public-properties[@name=\"Contact2\" and @value=\"\"]/ancestor::cm-handles', _) >> [] + mockCpsDataPersistenceService.queryDataNodes(_, _, '//state[@cm-handle-state=\"READY\"]/ancestor::cm-handles', _) >> [pnfDemo, pnfDemo3] + mockCpsDataPersistenceService.queryDataNodes(_, _, '//state[@cm-handle-state=\"LOCKED\"]/ancestor::cm-handles', _) >> [pnfDemo2, pnfDemo4] + mockCpsDataPersistenceService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, '/dmi-registry/cm-handles[@dmi-service-name=\'my-dmi-plugin-identifier\']', OMIT_DESCENDANTS) >> [pnfDemo, pnfDemo2] + mockCpsDataPersistenceService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, '/dmi-registry/cm-handles[@dmi-data-service-name=\'my-dmi-plugin-identifier\']', OMIT_DESCENDANTS) >> [pnfDemo, pnfDemo4] + mockCpsDataPersistenceService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, '/dmi-registry/cm-handles[@dmi-model-service-name=\'my-dmi-plugin-identifier\']', OMIT_DESCENDANTS) >> [pnfDemo2, pnfDemo4] + } + + def static createDataNode(dataNodeId) { + return new DataNode(xpath: '/dmi-registry/cm-handles[@id=\'' + dataNodeId + '\']', leaves: ['id':dataNodeId]) + } +} 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/api/impl/inventory/InventoryPersistenceImplSpec.groovy index 66fd7d88e..775824bb0 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/api/impl/inventory/InventoryPersistenceImplSpec.groovy @@ -61,7 +61,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) 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/api/impl/inventory/sync/ModuleOperationsUtilsSpec.groovy index 44bc18200..e9038ecfc 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/api/impl/inventory/sync/ModuleOperationsUtilsSpec.groovy @@ -35,7 +35,7 @@ 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.CmHandleQueryService 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 @@ -52,7 +52,7 @@ import java.util.stream.Collectors 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/api/impl/inventory/sync/ModuleSyncServiceSpec.groovy index cb933fafb..9f7b95300 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/api/impl/inventory/sync/ModuleSyncServiceSpec.groovy @@ -32,7 +32,7 @@ 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.CmHandleQueryService import org.onap.cps.ncmp.api.impl.inventory.CompositeStateBuilder import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle import org.onap.cps.spi.CascadeDeleteAllowed @@ -46,7 +46,7 @@ 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/operations/DmiDataOperationsSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiDataOperationsSpec.groovy index b286e9fb1..a0c6d0027 100644 --- 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 @@ -78,7 +78,7 @@ class DmiDataOperationsSpec extends DmiOperationsBaseSpec { def responseFromDmi = Mono.just(new ResponseEntity('{some-key:some-value}', HttpStatus.OK)) def expectedUrl = "${dmiServiceBaseUrl}${expectedDatastoreInUrl}?resourceIdentifier=${resourceIdentifier}${expectedOptionsInUrl}" def expectedJson = '{"operation":"read","cmHandleProperties":' + expectedProperties + ',"moduleSetTag":""}' - mockDmiRestClient.postOperationWithJsonDataAsync(DATA, expectedUrl, expectedJson, READ, NO_AUTH_HEADER) >> responseFromDmi + mockDmiRestClient.asynchronousPostOperationWithJsonData(DATA, expectedUrl, expectedJson, READ, NO_AUTH_HEADER) >> responseFromDmi 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).block() @@ -105,11 +105,11 @@ class DmiDataOperationsSpec extends DmiOperationsBaseSpec { def responseFromDmi = Mono.just(new ResponseEntity(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.postOperationWithJsonDataAsync(DATA, expectedDmiBatchResourceDataUrl, _, READ, NO_AUTH_HEADER) >> responseFromDmi + 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.postOperationWithJsonDataAsync(DATA, expectedDmiBatchResourceDataUrl, expectedBatchRequestAsJson, READ, NO_AUTH_HEADER) + 1 * mockDmiRestClient.asynchronousPostOperationWithJsonData(DATA, expectedDmiBatchResourceDataUrl, expectedBatchRequestAsJson, READ, NO_AUTH_HEADER) } def 'Execute (async) data operation from DMI service with Exception.'() { @@ -122,7 +122,7 @@ class DmiDataOperationsSpec extends DmiOperationsBaseSpec { 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.postOperationWithJsonDataAsync(*_) >> { Mono.error(new DmiClientRequestException(123, '', '', UNKNOWN_ERROR)) } + 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' @@ -141,9 +141,9 @@ class DmiDataOperationsSpec extends DmiOperationsBaseSpec { def responseFromDmi = new ResponseEntity(HttpStatus.OK) def expectedUrl = dmiServiceBaseUrl + "passthrough-operational?resourceIdentifier=/" def expectedJson = '{"operation":"read","cmHandleProperties":{"prop1":"val1"},"moduleSetTag":"my-module-set-tag"}' - mockDmiRestClient.postOperationWithJsonData(DATA, expectedUrl, expectedJson, READ, null) >> responseFromDmi + mockDmiRestClient.synchronousPostOperationWithJsonData(DATA, expectedUrl, expectedJson, READ, null) >> responseFromDmi when: 'get resource data is invoked' - def result = objectUnderTest.getResourceDataFromDmi( PASSTHROUGH_OPERATIONAL.datastoreName, cmHandleId, NO_REQUEST_ID) + def result = objectUnderTest.getAllResourceDataFromDmi(cmHandleId, NO_REQUEST_ID) then: 'the result is the response from the DMI service' assert result == responseFromDmi } @@ -155,7 +155,7 @@ class DmiDataOperationsSpec extends DmiOperationsBaseSpec { def expectedUrl = "${dmiServiceBaseUrl}passthrough-running?resourceIdentifier=${resourceIdentifier}" def expectedJson = '{"operation":"' + expectedOperationInUrl + '","dataType":"some data type","data":"requestData","cmHandleProperties":{"prop1":"val1"},"moduleSetTag":""}' def responseFromDmi = new ResponseEntity(HttpStatus.OK) - mockDmiRestClient.postOperationWithJsonData(DATA, expectedUrl, expectedJson, operation, NO_AUTH_HEADER) >> responseFromDmi + mockDmiRestClient.synchronousPostOperationWithJsonData(DATA, 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' 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/api/impl/operations/DmiModelOperationsSpec.groovy index db7f26f5f..9ab52b946 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/api/impl/operations/DmiModelOperationsSpec.groovy @@ -59,7 +59,7 @@ class DmiModelOperationsSpec extends DmiOperationsBaseSpec { def moduleReferencesAsLisOfMaps = [[moduleName: 'mod1', revision: 'A'], [moduleName: 'mod2', revision: 'X']] def expectedUrl = "${dmiServiceName}/dmi/v1/ch/${cmHandleId}/modules" def responseFromDmi = new ResponseEntity([schemas: moduleReferencesAsLisOfMaps], HttpStatus.OK) - mockDmiRestClient.postOperationWithJsonData(MODEL, 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 +72,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 +90,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(HttpStatus.OK) - mockDmiRestClient.postOperationWithJsonData(MODEL, "${dmiServiceName}/dmi/v1/ch/${cmHandleId}/modules", + mockDmiRestClient.synchronousPostOperationWithJsonData(MODEL, "${dmiServiceName}/dmi/v1/ch/${cmHandleId}/modules", '{"cmHandleProperties":' + expectedAdditionalPropertiesInRequest + ',"moduleSetTag":""}', READ, NO_AUTH_HEADER) >> responseFromDmi when: 'a get module references is called' def result = objectUnderTest.getModuleReferences(yangModelCmHandle) @@ -109,7 +109,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(MODEL, "${dmiServiceName}/dmi/v1/ch/${cmHandleId}/moduleResources", + mockDmiRestClient.synchronousPostOperationWithJsonData(MODEL, "${dmiServiceName}/dmi/v1/ch/${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 +125,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 +141,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(MODEL, "${dmiServiceName}/dmi/v1/ch/${cmHandleId}/moduleResources", + mockDmiRestClient.synchronousPostOperationWithJsonData(MODEL, "${dmiServiceName}/dmi/v1/ch/${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,7 +159,7 @@ 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(MODEL, "${dmiServiceName}/dmi/v1/ch/${cmHandleId}/moduleResources", + mockDmiRestClient.synchronousPostOperationWithJsonData(MODEL, "${dmiServiceName}/dmi/v1/ch/${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' @@ -180,7 +180,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/trustlevel/dmiavailability/DmiPluginWatchDogSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/trustlevel/dmiavailability/DmiPluginWatchDogSpec.groovy index 446126be9..53057573f 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/api/impl/trustlevel/dmiavailability/DmiPluginWatchDogSpec.groovy @@ -20,8 +20,8 @@ package org.onap.cps.ncmp.api.impl.trustlevel.dmiavailability -import org.onap.cps.ncmp.api.NetworkCmProxyDataService import org.onap.cps.ncmp.api.impl.client.DmiRestClient +import org.onap.cps.ncmp.api.impl.inventory.CmHandleQueryService import org.onap.cps.ncmp.api.impl.trustlevel.TrustLevel import org.onap.cps.ncmp.api.impl.trustlevel.TrustLevelManager import spock.lang.Specification @@ -29,15 +29,12 @@ import spock.lang.Specification class DmiPluginWatchDogSpec 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 DmiPluginWatchDog(mockDmiRestClient, mockCmHandleQueryService, mockTrustLevelManager, trustLevelPerDmiPlugin) def 'watch dmi plugin health status for #dmiHealhStatus'() { given: 'the cache has been initialised and "knows" about dmi-1' -- cgit 1.2.3-korg