From d7fa9601a1409ee3a156ac2f6a6ec11853989cd7 Mon Sep 17 00:00:00 2001 From: Arpit Singh Date: Thu, 7 Sep 2023 17:05:37 +0530 Subject: CPS Delta API 2: Delta between anchor and payload - Second API to get Delta between an anchor and JSON payload - added new API getDeltaByDataspaceAnchorAndPayload - added controller and service layer methods getDeltaByDataspaceAnchorAndPayload - Core Delta algorithm remains same as the first API. getDeltaByDataspaceAnchorAndPayload will call getDeltaBetweenDataNodes Issue-ID: CPS-1836 Signed-off-by: Arpit Singh Change-Id: Id74cd930ce48e5cb414aa62c5381b79675788a37 --- .../cps/api/impl/CpsDataServiceImplSpec.groovy | 74 +++++++++++++++++++++- .../onap/cps/api/impl/E2ENetworkSliceSpec.groovy | 11 +++- .../org/onap/cps/utils/YangParserSpec.groovy | 19 +++++- 3 files changed, 98 insertions(+), 6 deletions(-) (limited to 'cps-service/src/test/groovy/org/onap') diff --git a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy index 4542ecb67..edf25715b 100644 --- a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy @@ -23,6 +23,7 @@ package org.onap.cps.api.impl +import com.fasterxml.jackson.databind.ObjectMapper import ch.qos.logback.classic.Level import ch.qos.logback.classic.Logger import ch.qos.logback.core.read.ListAppender @@ -43,6 +44,9 @@ import org.onap.cps.spi.utils.CpsValidator import org.onap.cps.utils.ContentType import org.onap.cps.utils.YangParser import org.onap.cps.utils.YangParserHelper +import org.onap.cps.utils.JsonObjectMapper +import org.onap.cps.utils.PrefixResolver +import org.onap.cps.yang.TimedYangTextSchemaSourceSetBuilder import org.onap.cps.yang.YangTextSchemaSourceSet import org.onap.cps.yang.YangTextSchemaSourceSetBuilder import org.slf4j.LoggerFactory @@ -56,11 +60,15 @@ class CpsDataServiceImplSpec extends Specification { def mockCpsAnchorService = Mock(CpsAnchorService) def mockYangTextSchemaSourceSetCache = Mock(YangTextSchemaSourceSetCache) def mockCpsValidator = Mock(CpsValidator) - def yangParser = new YangParser(new YangParserHelper(), mockYangTextSchemaSourceSetCache) + def mockTimedYangTextSchemaSourceSetBuilder = Mock(TimedYangTextSchemaSourceSetBuilder) + def yangParser = new YangParser(new YangParserHelper(), mockYangTextSchemaSourceSetCache, mockTimedYangTextSchemaSourceSetBuilder) def mockCpsDeltaService = Mock(CpsDeltaService); def mockDataUpdateEventsService = Mock(CpsDataUpdateEventsService) + def jsonObjectMapper = new JsonObjectMapper(new ObjectMapper()) + def mockPrefixResolver = Mock(PrefixResolver) - def objectUnderTest = new CpsDataServiceImpl(mockCpsDataPersistenceService, mockDataUpdateEventsService, mockCpsAnchorService, mockCpsValidator, yangParser, mockCpsDeltaService) + def objectUnderTest = new CpsDataServiceImpl(mockCpsDataPersistenceService, mockDataUpdateEventsService, mockCpsAnchorService, + mockCpsValidator, yangParser, mockCpsDeltaService, jsonObjectMapper, mockPrefixResolver) def logger = (Logger) LoggerFactory.getLogger(objectUnderTest.class) def loggingListAppender @@ -230,6 +238,60 @@ class CpsDataServiceImplSpec extends Specification { 1 * mockCpsDeltaService.getDeltaReports(sourceDataNodes, targetDataNodes) } + def 'Get delta between anchor and payload with user provided schema #scenario'() { + given: 'user provided schema set ' + def yangResourcesNameToContentMap = TestUtils.getYangResourcesAsMap('bookstore.yang') + setupSchemaSetMocksForDelta(yangResourcesNameToContentMap) + when: 'attempt to get delta between an anchor and a JSON payload' + objectUnderTest.getDeltaByDataspaceAnchorAndPayload(dataspaceName, anchorName, xpath, yangResourcesNameToContentMap, jsonData, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) + then: 'dataspacename and anchor names are validated' + 1 * mockCpsValidator.validateNameCharacters(['some-dataspace', 'some-anchor']) + and: 'source data nodes are fetched using appropriate persistence layer method' + 1 * mockCpsDataPersistenceService.getDataNodes(dataspaceName, anchorName, xpath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> sourceDataNodes + and: 'appropriate delta service method is invoked once with correct source and target data nodes' + 1 * mockCpsDeltaService.getDeltaReports({sourceDataNodesRebuilt -> sourceDataNodesRebuilt.xpath[0] == expectedNodeXpath}, {targetDataNodes -> targetDataNodes.xpath[0] == expectedNodeXpath}) + where: 'following data was used' + scenario | xpath | sourceDataNodes | jsonData || expectedNodeXpath + 'root node xpath' | '/' | [new DataNodeBuilder().withXpath('/bookstore').build()] | '{"bookstore":{"bookstore-name":"Easons"}}' || '/bookstore' + 'parent xpath' | '/bookstore' | [new DataNodeBuilder().withXpath('/bookstore').build()] | '{"bookstore":{"bookstore-name":"Easons"}}' || '/bookstore' + 'non-root xpath' | '/bookstore/categories[@code="02"]' | [new DataNodeBuilder().withXpath('/bookstore/categories[@code="02"]').withLeaves(["code":"02"]).build()] | '{"categories":[{"name":"kids","code":"02"}]}' || '/bookstore/categories[@code=\'02\']' + } + + def 'Get delta between anchor and payload by using schema from anchor #scenario'() { + given: 'schema set for a given dataspace and anchor' + setupSchemaSetMocks("bookstore.yang") + when: 'attempt to get delta between an anchor and a JSON payload' + objectUnderTest.getDeltaByDataspaceAnchorAndPayload(dataspaceName, anchorName, xpath, [:], jsonData, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) + then: 'dataspacename and anchor names are validated' + 1 * mockCpsValidator.validateNameCharacters(['some-dataspace', 'some-anchor']) + and: 'source data nodes are fetched using appropriate persistence layer method' + 1 * mockCpsDataPersistenceService.getDataNodes(dataspaceName, anchorName, xpath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> sourceDataNodes + and: 'appropriate delta service method is invoked once with correct source and target data nodes' + 1 * mockCpsDeltaService.getDeltaReports({sourceDataNodesRebuilt -> sourceDataNodesRebuilt.xpath[0] == expectedNodeXpath}, {targetDataNodes -> targetDataNodes.xpath[0] == expectedNodeXpath}) + where: 'following data was used' + scenario | xpath | sourceDataNodes | jsonData || expectedNodeXpath + 'root node xpath' | '/' | [new DataNodeBuilder().withXpath('/bookstore').build()] | '{"bookstore":{"bookstore-name":"Easons"}}' || '/bookstore' + 'parent xpath' | '/bookstore' | [new DataNodeBuilder().withXpath('/bookstore').build()] | '{"bookstore":{"bookstore-name":"Easons"}}' || '/bookstore' + 'non-root xpath' | '/bookstore/categories[@code="02"]' | [new DataNodeBuilder().withXpath('/bookstore/categories[@code="02"]').withLeaves(["code":"02"]).build()] | '{"categories":[{"name":"kids","code":"02"}]}' || '/bookstore/categories[@code=\'02\']' + } + + def 'Delta between anchor and payload error scenario #scenario'() { + given: 'schema set for given anchor and dataspace references bookstore model' + def yangResourcesNameToContentMap = TestUtils.getYangResourcesAsMap('bookstore.yang') + setupSchemaSetMocksForDelta(yangResourcesNameToContentMap) + when: 'attempt to get delta between anchor and payload' + objectUnderTest.getDeltaByDataspaceAnchorAndPayload(dataspaceName, anchorName, xpath, yangResourcesNameToContentMap, jsonData, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) + then: 'expected exception is thrown' + thrown(DataValidationException) + where: 'following parameters were used' + scenario | xpath | jsonData + 'invalid json data with root node xpath' | '/' | '{"some-key": "some-value"' + 'empty json data with root node xpath' | '/' | '{}' + 'invalid json data with parent node xpath' | '/bookstore' | '{"some-key": "some-value"' + 'empty json data with parent node xpath' | '/bookstore' | '{}' + 'empty json data with xpath' | "/bookstore/categories[@code='02']" | '{}' + } + def 'Update data node leaves: #scenario.'() { given: 'schema set for given anchor and dataspace references test-tree model' setupSchemaSetMocks('test-tree.yang') @@ -503,4 +565,12 @@ class CpsDataServiceImplSpec extends Specification { mockYangTextSchemaSourceSet.getSchemaContext() >> schemaContext } + def setupSchemaSetMocksForDelta(Map yangResourcesNameToContentMap) { + def mockYangTextSchemaSourceSet = Mock(YangTextSchemaSourceSet) + mockTimedYangTextSchemaSourceSetBuilder.getYangTextSchemaSourceSet(yangResourcesNameToContentMap) >> mockYangTextSchemaSourceSet + mockYangTextSchemaSourceSetCache.get(_, _) >> mockYangTextSchemaSourceSet + def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourcesNameToContentMap).getSchemaContext() + mockYangTextSchemaSourceSet.getSchemaContext() >> schemaContext + } + } diff --git a/cps-service/src/test/groovy/org/onap/cps/api/impl/E2ENetworkSliceSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/api/impl/E2ENetworkSliceSpec.groovy index 57f2f8ea7..9e55e8f10 100755 --- a/cps-service/src/test/groovy/org/onap/cps/api/impl/E2ENetworkSliceSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/api/impl/E2ENetworkSliceSpec.groovy @@ -23,6 +23,7 @@ package org.onap.cps.api.impl +import com.fasterxml.jackson.databind.ObjectMapper import org.onap.cps.TestUtils import org.onap.cps.api.CpsAnchorService import org.onap.cps.api.CpsDeltaService @@ -31,6 +32,8 @@ import org.onap.cps.spi.CpsDataPersistenceService import org.onap.cps.spi.CpsModulePersistenceService import org.onap.cps.spi.model.Anchor import org.onap.cps.spi.utils.CpsValidator +import org.onap.cps.utils.JsonObjectMapper +import org.onap.cps.utils.PrefixResolver import org.onap.cps.utils.ContentType import org.onap.cps.utils.YangParser import org.onap.cps.utils.YangParserHelper @@ -45,15 +48,17 @@ class E2ENetworkSliceSpec extends Specification { def mockYangTextSchemaSourceSetCache = Mock(YangTextSchemaSourceSetCache) def mockCpsValidator = Mock(CpsValidator) def timedYangTextSchemaSourceSetBuilder = new TimedYangTextSchemaSourceSetBuilder() - def yangParser = new YangParser(new YangParserHelper(), mockYangTextSchemaSourceSetCache) + def yangParser = new YangParser(new YangParserHelper(), mockYangTextSchemaSourceSetCache, timedYangTextSchemaSourceSetBuilder) def mockCpsDeltaService = Mock(CpsDeltaService) + def jsonObjectMapper = new JsonObjectMapper(new ObjectMapper()) + def mockPrefixResolver = Mock(PrefixResolver) def cpsModuleServiceImpl = new CpsModuleServiceImpl(mockModuleStoreService, mockYangTextSchemaSourceSetCache, mockCpsAnchorService, mockCpsValidator,timedYangTextSchemaSourceSetBuilder) def mockDataUpdateEventsService = Mock(CpsDataUpdateEventsService) - def cpsDataServiceImpl = new CpsDataServiceImpl(mockDataStoreService, mockDataUpdateEventsService, mockCpsAnchorService, mockCpsValidator, yangParser, mockCpsDeltaService) - + def cpsDataServiceImpl = new CpsDataServiceImpl(mockDataStoreService, mockDataUpdateEventsService, mockCpsAnchorService, mockCpsValidator, + yangParser, mockCpsDeltaService, jsonObjectMapper, mockPrefixResolver) def dataspaceName = 'someDataspace' def anchorName = 'someAnchor' def schemaSetName = 'someSchemaSet' diff --git a/cps-service/src/test/groovy/org/onap/cps/utils/YangParserSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/utils/YangParserSpec.groovy index 99070fe72..18d0502e3 100644 --- a/cps-service/src/test/groovy/org/onap/cps/utils/YangParserSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/utils/YangParserSpec.groovy @@ -1,6 +1,7 @@ /* * ============LICENSE_START======================================================= * Copyright (C) 2024 Nordix Foundation + * Modifications Copyright (C) 2024 TechMahindra Ltd. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,9 +21,12 @@ package org.onap.cps.utils +import org.onap.cps.TestUtils import org.onap.cps.spi.exceptions.DataValidationException import org.onap.cps.spi.model.Anchor +import org.onap.cps.yang.TimedYangTextSchemaSourceSetBuilder import org.onap.cps.yang.YangTextSchemaSourceSet +import org.onap.cps.yang.YangTextSchemaSourceSetBuilder import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode import org.opendaylight.yangtools.yang.model.api.SchemaContext import spock.lang.Specification @@ -32,10 +36,12 @@ class YangParserSpec extends Specification { def mockYangParserHelper = Mock(YangParserHelper) def mockYangTextSchemaSourceSetCache = Mock(YangTextSchemaSourceSetCache) + def mockTimedYangTextSchemaSourceSetBuilder = Mock(TimedYangTextSchemaSourceSetBuilder) - def objectUnderTest = new YangParser(mockYangParserHelper, mockYangTextSchemaSourceSetCache) + def objectUnderTest = new YangParser(mockYangParserHelper, mockYangTextSchemaSourceSetCache, mockTimedYangTextSchemaSourceSetBuilder) def anchor = new Anchor(dataspaceName: 'my dataspace', schemaSetName: 'my schema') + def yangResourcesNameToContentMap = TestUtils.getYangResourcesAsMap('bookstore.yang') def mockYangTextSchemaSourceSet = Mock(YangTextSchemaSourceSet) def mockSchemaContext = Mock(SchemaContext) def containerNodeFromYangUtils = Mock(ContainerNode) @@ -82,4 +88,15 @@ class YangParserSpec extends Specification { 1 * mockYangTextSchemaSourceSetCache.removeFromCache('my dataspace', 'my schema') } + def 'Parsing data with yang resource to context map.'() { + given: 'the schema source set for the yang resource map is returned' + mockTimedYangTextSchemaSourceSetBuilder.getYangTextSchemaSourceSet(yangResourcesNameToContentMap) >> mockYangTextSchemaSourceSet + when: 'parsing some json data' + def result = objectUnderTest.parseData(ContentType.JSON, 'some json', yangResourcesNameToContentMap, noParent) + then: 'the yang parser helper always returns a container node' + 1 * mockYangParserHelper.parseData(ContentType.JSON, 'some json', mockSchemaContext, noParent) >> containerNodeFromYangUtils + and: 'the result is the same container node as return from yang utils' + assert result == containerNodeFromYangUtils + } + } -- cgit 1.2.3-korg