[Cps Path Parser] Move NCMP-specific logic to NCMP 84/138884/3
authordanielhanrahan <daniel.hanrahan@est.tech>
Thu, 29 Aug 2024 19:00:40 +0000 (20:00 +0100)
committerdanielhanrahan <daniel.hanrahan@est.tech>
Wed, 4 Sep 2024 16:21:40 +0000 (17:21 +0100)
Some special case code to disable ancestor-axis was added for
CM-handle search (see CPS-2308). It is now relocated to NCMP.
This makes other needed improvements of Cps Path Parser easier.

- Move special case code into NCMP
- Add integration test to ensure CM-handle search works

Issue-ID: CPS-2365
Signed-off-by: danielhanrahan <daniel.hanrahan@est.tech>
Change-Id: I168d6156be559166f115aa42e21cd987d98b7d41

cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CmHandleQueryServiceImpl.java
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleQueryServiceImplSpec.groovy
cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathBuilder.java
cps-path-parser/src/test/groovy/org/onap/cps/cpspath/parser/CpsPathQuerySpec.groovy
integration-test/src/test/groovy/org/onap/cps/integration/functional/cps/QueryServiceIntegrationSpec.groovy
integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/RestApiSpec.groovy

index e36477f..a07954d 100644 (file)
@@ -34,6 +34,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.stream.Collectors;
 import lombok.RequiredArgsConstructor;
+import org.onap.cps.cpspath.parser.CpsPathUtil;
 import org.onap.cps.ncmp.api.inventory.models.TrustLevel;
 import org.onap.cps.ncmp.impl.inventory.models.CmHandleState;
 import org.onap.cps.ncmp.impl.inventory.models.ModelledDmiServiceLeaves;
@@ -96,6 +97,9 @@ public class CmHandleQueryServiceImpl implements CmHandleQueryService {
     @Override
     public List<DataNode> queryCmHandleAncestorsByCpsPath(final String cpsPath,
                                                           final FetchDescendantsOption fetchDescendantsOption) {
+        if (CpsPathUtil.getCpsPathQuery(cpsPath).getXpathPrefix().endsWith("/cm-handles")) {
+            return queryNcmpRegistryByCpsPath(cpsPath, fetchDescendantsOption);
+        }
         return queryNcmpRegistryByCpsPath(cpsPath + ANCESTOR_CM_HANDLES, fetchDescendantsOption);
     }
 
index 36fd755..e3847fa 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2022-2023 Nordix Foundation
+ *  Copyright (C) 2022-2024 Nordix Foundation
  *  Modifications Copyright (C) 2023 TechMahindra Ltd.
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
@@ -167,9 +167,9 @@ class CmHandleQueryServiceImplSpec extends Specification {
 
     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'
+            def cmHandleDataNode = new DataNode(xpath: "/dmi-registry/cm-handles[@id='ch-1']", leaves: ['id': 'ch-1'])
+            def cpsPath = "//state[@cm-handle-state='LOCKED']"
+        and: 'cps data service returns a valid data node for cm handle ancestor'
             mockCpsDataPersistenceService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
                 cpsPath + '/ancestor::cm-handles', INCLUDE_ALL_DESCENDANTS)
                 >> Arrays.asList(cmHandleDataNode)
@@ -179,6 +179,20 @@ class CmHandleQueryServiceImplSpec extends Specification {
             assert result.contains(cmHandleDataNode)
     }
 
+    def 'Retrieve cm handle by cps path querying cm handle directly'() {
+        given: 'a cm handle to query based on the cps path'
+            def cmHandleDataNode = new DataNode(xpath: "/dmi-registry/cm-handles[@id='ch-2']", leaves: ['id': 'ch-2'])
+            def cpsPath = "//cm-handles[@alternate-id='1']"
+        and: 'cps data service returns a valid data node'
+            mockCpsDataPersistenceService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
+                    cpsPath, 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()
index 0325bdc..ac535f5 100644 (file)
@@ -147,10 +147,6 @@ public class CpsPathBuilder extends CpsPathBaseListener {
         cpsPathQuery.setNormalizedXpath(normalizedXpathBuilder.toString());
         cpsPathQuery.setContainerNames(containerNames);
         cpsPathQuery.setBooleanOperators(booleanOperators);
-        if (cpsPathQuery.hasAncestorAxis() && cpsPathQuery.getXpathPrefix()
-                .endsWith("/" + cpsPathQuery.getAncestorSchemaNodeIdentifier())) {
-            cpsPathQuery.setAncestorSchemaNodeIdentifier("");
-        }
         return cpsPathQuery;
     }
 
index 730c826..d0df3c7 100644 (file)
@@ -253,21 +253,4 @@ class CpsPathQuerySpec extends Specification {
             '/test[@name2="value2" and @name1="value1"]' || 'name2'               | 'name1'
     }
 
-    def 'Ancestor axis matching prefix'() {
-        when: 'building a cps path query'
-            def result = parseXPathAndBuild(xpath)
-        then: 'ancestor axis is removed when same as prefix'
-            assert result.hasAncestorAxis() == expectAncestorAxis
-        where: 'the following xpaths are used'
-            xpath                     || expectAncestorAxis
-            '//abc/def/ancestor::abc' || true
-            '//abc/def/ancestor::def' || false
-            '//abc/def/ancestor::ef'  || true
-        }
-
-    def parseXPathAndBuild(xpath) {
-        def cpsPathBuilder = CpsPathUtil.getCpsPathBuilder(xpath)
-        cpsPathBuilder.build()
-    }
-
 }
index 69598a0..5c2a4fc 100644 (file)
@@ -271,7 +271,6 @@ class QueryServiceIntegrationSpec extends FunctionalSpecBase {
             'ancestor with parent list'                 | '//books/ancestor::bookstore/categories'              || ["/bookstore/categories[@code='1']", "/bookstore/categories[@code='2']", "/bookstore/categories[@code='3']", "/bookstore/categories[@code='4']", "/bookstore/categories[@code='5']"]
             'ancestor with parent list element'         | '//books/ancestor::bookstore/categories[@code="2"]'   || ["/bookstore/categories[@code='2']"]
             'ancestor combined with text condition'     | '//books/title[text()="Matilda"]/ancestor::bookstore' || ["/bookstore"]
-            'ancestor same as target type'              | '//books/title[text()="Matilda"]/ancestor::books'     || ["/bookstore/categories[@code='1']/books[@title='Matilda']"]
     }
 
     def 'Cps Path query across anchors with #scenario descendants.'() {
index 33973e5..1e1af55 100644 (file)
@@ -41,7 +41,7 @@ class RestApiSpec extends CpsIntegrationSpecBase {
                 'ch-3': ['M1', 'M3']
             ]
         when: 'a POST request is made to register the CM Handles'
-            def requestBody = '{"dmiPlugin":"'+DMI1_URL+'","createdCmHandles":[{"cmHandle":"ch-1"},{"cmHandle":"ch-2"},{"cmHandle":"ch-3"}]}'
+            def requestBody = '{"dmiPlugin":"'+DMI1_URL+'","createdCmHandles":[{"cmHandle":"ch-1","alternateId":"alt-1"},{"cmHandle":"ch-2","alternateId":"alt-2"},{"cmHandle":"ch-3","alternateId":"alt-3"}]}'
             mvc.perform(post('/ncmpInventory/v1/ch').contentType(MediaType.APPLICATION_JSON).content(requestBody))
                     .andExpect(status().is2xxSuccessful())
         then: 'CM-handles go to READY state'
@@ -76,6 +76,27 @@ class RestApiSpec extends CpsIntegrationSpecBase {
             'M3'       || ['ch-3']
     }
 
+    def 'Search for CM Handles using Cps Path Query.'() {
+        given: 'a JSON request body containing search parameter'
+            def requestBodyWithSearchCondition = """{
+                    "cmHandleQueryParameters": [
+                            {
+                                "conditionName": "cmHandleWithCpsPath",
+                                "conditionParameters": [ {"cpsPath" : "%s"} ]
+                            }
+                    ]
+                }""".formatted(cpsPath)
+        expect: "a search for cps path ${cpsPath} returns expected CM handles"
+            mvc.perform(post('/ncmp/v1/ch/id-searches').contentType(MediaType.APPLICATION_JSON).content(requestBodyWithSearchCondition))
+                    .andExpect(status().is2xxSuccessful())
+                    .andExpect(jsonPath('$[*]', containsInAnyOrder(expectedCmHandles.toArray())))
+                    .andExpect(jsonPath('$', hasSize(expectedCmHandles.size())));
+        where:
+            scenario                    | cpsPath                                 || expectedCmHandles
+            'All Ready CM handles'      | "//state[@cm-handle-state='READY']"     || ['ch-1', 'ch-2', 'ch-3']
+            'Having Alternate ID alt-3' | "//cm-handles[@alternate-id='alt-3']"   || ['ch-3']
+    }
+
     def 'De-register CM handles using REST API.'() {
         when: 'a POST request is made to deregister the CM Handle'
             def requestBody = '{"dmiPlugin":"'+DMI1_URL+'", "removedCmHandles": ["ch-1", "ch-2", "ch-3"]}'