Inconsistency With JSON Response(List Items) Using GetANode API 45/140045/31
authorRudrangi Anupriya <ra00745022@techmahindra.com>
Mon, 5 May 2025 14:34:39 +0000 (20:04 +0530)
committerRudrangi Anupriya <ra00745022@techmahindra.com>
Tue, 6 May 2025 12:12:11 +0000 (17:42 +0530)
Jira - https://lf-onap.atlassian.net/browse/CPS-2566
Documentation -https://lf-onap.atlassian.net/wiki/x/OQA1AQ

- Introduced versioned V3 approach to retrieve list elements
- added controller method getNodeByDataspaceAndAnchorV3
- Updated listelementAsMap,containerElementsAsMap to group list elements

Issue-ID: CPS-2566
Change-Id: Ice9ffb99aabb03702355321c6d5c2eade0e7ef5b
Signed-off-by: Rudrangi Anupriya <ra00745022@techmahindra.com>
17 files changed:
cps-rest/docs/openapi/components.yml
cps-rest/docs/openapi/cpsDataV2Deprecated.yml [moved from cps-rest/docs/openapi/cpsDataV2.yml with 96% similarity]
cps-rest/docs/openapi/cpsDataV3.yml [new file with mode: 0644]
cps-rest/docs/openapi/openapi.yml
cps-rest/src/main/java/org/onap/cps/rest/controller/DataRestController.java
cps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy
cps-service/src/main/java/org/onap/cps/api/CpsFacade.java
cps-service/src/main/java/org/onap/cps/impl/CpsFacadeImpl.java
cps-service/src/main/java/org/onap/cps/utils/DataMapUtils.java
cps-service/src/main/java/org/onap/cps/utils/DataMapper.java
cps-service/src/main/java/org/onap/cps/utils/XmlFileUtils.java
cps-service/src/test/groovy/org/onap/cps/impl/CpsFacadeImplSpec.groovy
cps-service/src/test/groovy/org/onap/cps/utils/XmlFileUtilsSpec.groovy
docs/api/swagger/cps/openapi.yaml
docs/api/swagger/ncmp/openapi-inventory.yaml
docs/api/swagger/ncmp/openapi.yaml
docs/test_ScrapeMetrics.py

index 43a3118..fe09c41 100644 (file)
@@ -104,21 +104,131 @@ components:
           categories:
             - code: 01
               name: SciFi
+            - books:
+                - title: Book 1
+                  lang: N/A
+                  price: 11
+                  editions:
+                    - 2009
+            - books:
+                - title: Book 2
+                  lang: German
+                  price: 39
+                  editions:
+                    - 2007
+                    - 2013
+                    - 2021
             - code: 02
               name: kids
+            - books:
+                - title: Book 3
+                  lang: English
+                  price: 15
+                  editions:
+                    - 2010
+    dataSampleForV3:
+      value:
+        test:bookstore:
+          bookstore-name: Chapters
+          categories:
+            - code: 01
+              name: SciFi
+              books:
+                - title: Book 1
+                  lang: N/A
+                  price: 11
+                  editions:
+                    - 2009
+                - title: Book 2
+                  lang: German
+                  price: 39
+                  editions:
+                    - 2007
+                    - 2013
+                    - 2021
+            - code: 02
+              name: kids
+              books:
+                - title: Book 3
+                  lang: English
+                  price: 15
+                  editions:
+                    - 2010
     dataSampleXml:
         value:
           <stores xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
+              <bookstore xmlns="org:onap:ccsdk:sample">
+                  <bookstore-name>Chapters</bookstore-name>
+                  <categories>
+                      <code>1</code>
+                      <name>SciFi</name>
+                  </categories>
+                  <categories>
+                      <books>
+                          <title>Book 1</title>
+                          <lang>N/A</lang>
+                          <price>11</price>
+                          <editions>2009</editions>
+                      </books>
+                  </categories>
+                  <categories>
+                      <books>
+                          <title>Book 2</title>
+                          <lang>German</lang>
+                          <price>39</price>
+                          <editions>2007</editions>
+                          <editions>2013</editions>
+                          <editions>2021</editions>
+                      </books>
+                  </categories>
+                  <categories>
+                      <code>2</code>
+                      <name>kids
+                      </name>
+                      <books>
+                          <title>Book 3</title>
+                          <lang>English</lang>
+                          <price>15</price>
+                          <editions>2010</editions>
+                      </books>
+                  </categories>
+              </bookstore>
+          </stores>
+    dataSampleXmlForV3:
+      value:
+        <stores xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
             <bookstore xmlns="org:onap:ccsdk:sample">
-              <bookstore-name>Chapters</bookstore-name>
-              <categories>
-                <code>1</code>
-                <name>SciFi</name>
-                <code>2</code>
-                <name>kids</name>
-              </categories>
+                <bookstore-name>Chapters</bookstore-name>
+                <categories>
+                    <code>1</code>
+                    <name>SciFi</name>
+                    <books>
+                        <title>Book 1</title>
+                        <lang>N/A</lang>
+                        <price>11</price>
+                        <editions>2009</editions>
+                    </books>
+                    <books>
+                        <title>Book 2</title>
+                        <lang>German</lang>
+                        <price>39</price>
+                        <editions>2007</editions>
+                        <editions>2013</editions>
+                        <editions>2021</editions>
+                    </books>
+                </categories>
+                <categories>
+                    <code>2</code>
+                    <name>kids</name>
+                    <books>
+                        <title>Book 3</title>
+                        <lang>English</lang>
+                        <price>15</price>
+                        <editions>2010</editions>
+                    </books>
+                </categories>
             </bookstore>
-          </stores>
+        </stores>
     dataSampleAcrossAnchors:
       value:
         - anchorName: bookstore1
similarity index 96%
rename from cps-rest/docs/openapi/cpsDataV2.yml
rename to cps-rest/docs/openapi/cpsDataV2Deprecated.yml
index 7afda70..0d17b18 100644 (file)
@@ -1,5 +1,6 @@
 # ============LICENSE_START=======================================================
 # Copyright (c) 2022-2025 TechMahindra Ltd.
+# Modifications Copyright (C) 2025 TechMahindra Ltd.
 # ================================================================================
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -19,6 +20,7 @@
 nodeByDataspaceAndAnchor:
   get:
     description: Get a node with an option to retrieve all the children for a given anchor and dataspace
+    deprecated: true
     tags:
       - cps-data
     summary: Get a node
diff --git a/cps-rest/docs/openapi/cpsDataV3.yml b/cps-rest/docs/openapi/cpsDataV3.yml
new file mode 100644 (file)
index 0000000..9b296d8
--- /dev/null
@@ -0,0 +1,56 @@
+# ============LICENSE_START=======================================================
+# Copyright (c) 2025 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=========================================================
+
+nodeByDataspaceAndAnchor:
+  get:
+    description: Get a node with an option to retrieve all the children for a given anchor and dataspace
+    tags:
+      - cps-data
+    summary: Get a node
+    operationId: getNodeByDataspaceAndAnchorV3
+    parameters:
+      - $ref: 'components.yml#/components/parameters/dataspaceNameInPath'
+      - $ref: 'components.yml#/components/parameters/anchorNameInPath'
+      - $ref: 'components.yml#/components/parameters/xpathInQuery'
+      - $ref: 'components.yml#/components/parameters/descendantsInQuery'
+      - $ref: 'components.yml#/components/parameters/contentTypeInHeader'
+    responses:
+      '200':
+        description: OK
+        content:
+          application/json:
+            schema:
+              type: object
+            examples:
+              dataSample:
+                $ref: 'components.yml#/components/examples/dataSampleForV3'
+          application/xml:
+            schema:
+              type: object
+              xml:
+                name: stores
+            examples:
+              dataSample:
+                $ref: 'components.yml#/components/examples/dataSampleXmlForV3'
+      '400':
+        $ref: 'components.yml#/components/responses/BadRequest'
+      '403':
+        $ref: 'components.yml#/components/responses/Forbidden'
+      '500':
+        $ref: 'components.yml#/components/responses/InternalServerError'
+    x-codegen-request-body-name: xpath
\ No newline at end of file
index 747531b..e33720c 100644 (file)
@@ -89,7 +89,10 @@ paths:
     $ref: 'cpsDataV1Deprecated.yml#/nodeByDataspaceAndAnchor'
 
   /v2/dataspaces/{dataspace-name}/anchors/{anchor-name}/node:
-    $ref: 'cpsDataV2.yml#/nodeByDataspaceAndAnchor'
+    $ref: 'cpsDataV2Deprecated.yml#/nodeByDataspaceAndAnchor'
+
+  /v3/dataspaces/{dataspace-name}/anchors/{anchor-name}/node:
+    $ref: 'cpsDataV3.yml#/nodeByDataspaceAndAnchor'
 
   /{apiVersion}/dataspaces/{dataspace-name}/anchors/{anchor-name}/nodes:
     $ref: 'cpsData.yml#/nodesByDataspaceAndAnchor'
index 90500f3..f2dc4e9 100755 (executable)
@@ -130,6 +130,20 @@ public class DataRestController implements CpsDataApi {
         return buildResponseEntity(dataNodesAsMaps, contentType);
     }
 
+    @Override
+    @Timed(value = "cps.data.controller.datanode.get.v3", description = "Time taken to get data node")
+    public ResponseEntity<Object> getNodeByDataspaceAndAnchorV3(final String dataspaceName, final String anchorName,
+                                                                final String xpath,
+                                                                final String fetchDescendantsOptionAsString,
+                                                                final String contentTypeInHeader) {
+        final ContentType contentType = ContentType.fromString(contentTypeInHeader);
+        final FetchDescendantsOption fetchDescendantsOption =
+            FetchDescendantsOption.getFetchDescendantsOption(fetchDescendantsOptionAsString);
+        final Map<String, Object> dataNodesAsMap =
+            cpsFacade.getDataNodesByAnchorV3(dataspaceName, anchorName, xpath, fetchDescendantsOption);
+        return buildResponseEntity(dataNodesAsMap, contentType);
+    }
+
     @Override
     public ResponseEntity<Object> updateNodeLeaves(final String apiVersion, final String dataspaceName,
                                                    final String anchorName, final String nodeData,
@@ -187,8 +201,7 @@ public class DataRestController implements CpsDataApi {
         return new ResponseEntity<>(HttpStatus.NO_CONTENT);
     }
 
-    private ResponseEntity<Object> buildResponseEntity(final List<Map<String, Object>> dataMaps,
-                                               final ContentType contentType) {
+    private ResponseEntity<Object> buildResponseEntity(final Object dataMaps, final ContentType contentType) {
         final String responseData;
         if (ContentType.XML.equals(contentType)) {
             responseData = XmlFileUtils.convertDataMapsToXml(dataMaps);
index ba5104a..3762b6e 100755 (executable)
@@ -68,6 +68,7 @@ class DataRestControllerSpec extends Specification {
 
     def dataNodeBaseEndpointV1
     def dataNodeBaseEndpointV2
+    def dataNodeBaseEndpointV3
     def dataspaceName = 'my_dataspace'
     def anchorName = 'my_anchor'
     def noTimestamp = null
@@ -87,6 +88,7 @@ class DataRestControllerSpec extends Specification {
     def setup() {
         dataNodeBaseEndpointV1 = "$basePath/v1/dataspaces/$dataspaceName"
         dataNodeBaseEndpointV2 = "$basePath/v2/dataspaces/$dataspaceName"
+        dataNodeBaseEndpointV3 = "$basePath/v3/dataspaces/$dataspaceName"
     }
 
     def 'Create a node: #scenario.'() {
@@ -302,6 +304,29 @@ class DataRestControllerSpec extends Specification {
             'JSON'   | MediaType.APPLICATION_JSON || '[{"mocked":"result1"},{"mocked":"result2"}]'
     }
 
+    def 'Get data node with #scenario using V3. output type #scenario.'() {
+        given: 'the service returns data nodes with #scenario'
+            def xpath = 'some xPath'
+            def endpoint = "$dataNodeBaseEndpointV3/anchors/$anchorName/node"
+        when: 'V3 of get request is performed through REST API'
+            def response =
+                mvc.perform(get(endpoint)
+                    .contentType(contentType)
+                    .param('xpath', xpath)
+                    .param('descendants', 'all'))
+                    .andReturn().response
+        then: 'the cps service facade is called with the correct parameters and returns some data'
+            1 * mockCpsFacade.getDataNodesByAnchorV3(dataspaceName, anchorName, xpath, INCLUDE_ALL_DESCENDANTS) >> [books: [[title: 'Book 1'], [title: 'Book 2']]]
+        and: 'a success response is returned'
+            assert response.status == HttpStatus.OK.value()
+        and: 'the response is in the expected format'
+            assert response.contentAsString == expectedResult
+        where: 'the following content types are used'
+            scenario | contentType                || expectedResult
+            'XML'    | MediaType.APPLICATION_XML  || '<books><title>Book 1</title></books><books><title>Book 2</title></books>'
+            'JSON'   | MediaType.APPLICATION_JSON || '{"books":[{"title":"Book 1"},{"title":"Book 2"}]}'
+    }
+
     def 'Update data node leaves: #scenario.'() {
         given: 'endpoint to update a node '
             def endpoint = "$dataNodeBaseEndpointV1/anchors/$anchorName/nodes"
index 8933f02..5a9f048 100644 (file)
@@ -1,6 +1,7 @@
 /*
  *  ============LICENSE_START=======================================================
  *  Copyright (C) 2025 Nordix Foundation
+ *  Modifications Copyright (C) 2025 TechMahindra Ltd.
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
@@ -55,6 +56,28 @@ public interface CpsFacade {
                                                    String xpath,
                                                    FetchDescendantsOption fetchDescendantsOption);
 
+    /**
+     * Get data nodes for a given dataspace, anchor and xpath.
+     * This method ensures that list nodes are returned as a single entry with their list items
+     * grouped under the list node name.
+     *
+     *
+     * @param dataspaceName          the name of the dataspace
+     * @param anchorName             the name of the anchor
+     * @param xpath                  the xpath
+     * @param fetchDescendantsOption control what level of descendants should be returned
+     * @return                       a map where each key represents a data node name (e.g., container or list),
+     *                               and each value is either:
+     *                              - a leaf values as key-value pairs,
+     *                              - a nested map (for containers),
+     *                              - or a list of maps (for lists containing multiple elements)
+     */
+
+    Map<String, Object> getDataNodesByAnchorV3(String dataspaceName,
+                                               String anchorName,
+                                               String xpath,
+                                               FetchDescendantsOption fetchDescendantsOption);
+
     /**
      * Query the given anchor using a cps path expression.
      *
index 35a0368..09c24f4 100644 (file)
@@ -1,6 +1,7 @@
 /*
  *  ============LICENSE_START=======================================================
  *  Copyright (C) 2025 Nordix Foundation
+ *  Modifications Copyright (C) 2025 TechMahindra Ltd.
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
@@ -64,6 +65,16 @@ public class CpsFacadeImpl implements CpsFacade {
         return dataMapper.toDataMaps(dataspaceName, anchorName, dataNodes);
     }
 
+    @Override
+    public Map<String, Object> getDataNodesByAnchorV3(final String dataspaceName,
+                                                      final String anchorName,
+                                                      final String xpath,
+                                                      final FetchDescendantsOption fetchDescendantsOption) {
+        final Collection<DataNode> dataNodes = cpsDataService.getDataNodes(dataspaceName, anchorName, xpath,
+            fetchDescendantsOption);
+        return dataMapper.toDataMapForApiV3(dataspaceName, anchorName, dataNodes);
+    }
+
     @Override
     public List<Map<String, Object>> executeAnchorQuery(final String dataspaceName,
                                                         final String anchorName,
index 52cb0ea..fabae6d 100644 (file)
@@ -3,7 +3,7 @@
  *  Copyright (C) 2021 Pantheon.tech
  *  Modifications (C) 2021-2023 Nordix Foundation
  *  Modifications Copyright (C) 2022 Bell Canada
- *  Modifications Copyright (C) 2022-2023 TechMahindra Ltd.
+ *  Modifications Copyright (C) 2022-2025 TechMahindra Ltd.
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
@@ -101,10 +101,8 @@ public class DataMapUtils {
             .putAll(
                 dataNodes.stream()
                     .filter(dataNode -> isListElement(dataNode.getXpath()))
-                    .collect(groupingBy(
-                        dataNode -> getNodeIdentifier(dataNode.getXpath()),
-                        mapping(DataMapUtils::toDataMap, toUnmodifiableList())
-                    ))
+                    .collect(groupingBy(DataMapUtils::getNodeIdentifierWithPrefix,
+                        mapping(DataMapUtils::toDataMap, toUnmodifiableList())))
             ).build();
     }
 
@@ -114,11 +112,7 @@ public class DataMapUtils {
         }
         return dataNodes.stream()
             .filter(dataNode -> isContainerNode(dataNode.getXpath()))
-            .collect(
-                toUnmodifiableMap(
-                    dataNode -> getNodeIdentifier(dataNode.getXpath()),
-                    DataMapUtils::toDataMap
-                ));
+            .collect(toUnmodifiableMap(DataMapUtils::getNodeIdentifierWithPrefix, DataMapUtils::toDataMap));
     }
 
     private static String getNodeIdentifier(String xpath) {
@@ -137,6 +131,10 @@ public class DataMapUtils {
         return getNodeIdentifier(xpath);
     }
 
+    private static String getNodeIdentifierWithPrefix(final DataNode dataNode) {
+        return getNodeIdentifierWithPrefix(dataNode.getXpath(), dataNode.getModuleNamePrefix());
+    }
+
     private static boolean isContainerNode(final String xpath) {
         return !isListElement(xpath);
     }
index 29d61ff..b686a1d 100644 (file)
@@ -1,6 +1,7 @@
 /*
  *  ============LICENSE_START=======================================================
  *  Copyright (C) 2025 Nordix Foundation.
+ *  Modifications Copyright (C) 2025 TechMahindra Ltd.
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
@@ -30,6 +31,7 @@ import lombok.RequiredArgsConstructor;
 import org.onap.cps.api.CpsAnchorService;
 import org.onap.cps.api.model.Anchor;
 import org.onap.cps.api.model.DataNode;
+import org.onap.cps.impl.DataNodeBuilder;
 import org.springframework.stereotype.Service;
 
 @Service
@@ -106,6 +108,30 @@ public class DataMapper {
         return dataNodesAsMaps;
     }
 
+    /**
+     * Convert a collection of data nodes to a list of data maps.
+     * List nodes are returned as a map entry where the key is the list node name and the value is
+     * a list of maps, each representing an individual list item. Container nodes are returned as
+     * nested maps under their respective parent node names.
+     *
+     * @param dataspaceName the name dataspace name
+     * @param anchorName    the name of the anchor
+     * @param dataNodes     the data nodes to convert
+     * @return a map reflecting the complete data node structure, where:
+     *                - leaf values are returned as key-value pairs,
+     *                - containers are returned as nested maps,
+     *                - and list nodes are grouped under a single key as a list of map entries.
+     */
+
+    public Map<String, Object> toDataMapForApiV3(final String dataspaceName, final String anchorName,
+                                                 final Collection<DataNode> dataNodes) {
+        final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
+        dataNodes.forEach(dataNode ->
+            dataNode.setModuleNamePrefix(prefixResolver.getPrefix(anchor, dataNode.getXpath())));
+        final DataNode containerNode = new DataNodeBuilder().withChildDataNodes(dataNodes).build();
+        return DataMapUtils.toDataMap(containerNode);
+    }
+
     /**
      * Converts list of attributes values to a list of data maps.
      * @param attributeName   attribute name
index c9c1ece..01093d7 100644 (file)
@@ -2,7 +2,7 @@
  *  ============LICENSE_START=======================================================
  *  Copyright (C) 2022 Deutsche Telekom AG
  *  Modifications Copyright (C) 2023-2024 Nordix Foundation.
- *  Modifications Copyright (C) 2024 TechMahindra Ltd.
+ *  Modifications Copyright (C) 2024-2025 TechMahindra Ltd.
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
@@ -171,13 +171,19 @@ public class XmlFileUtils {
      * @return XML string representation of the data maps
      */
     @SuppressFBWarnings(value = "DCN_NULLPOINTER_EXCEPTION")
-    public static String convertDataMapsToXml(final List<Map<String, Object>> dataMaps) {
+    public static String convertDataMapsToXml(final Object dataMaps) {
         try {
             final DocumentBuilder documentBuilder = getDocumentBuilderFactory().newDocumentBuilder();
             final Document document = documentBuilder.newDocument();
             final DocumentFragment documentFragment = document.createDocumentFragment();
-            for (final Map<String, Object> dataMap : dataMaps) {
-                createXmlElements(document, documentFragment, dataMap);
+            if (dataMaps instanceof Map) {
+                createXmlElements(document, documentFragment, (Map<String, Object>) dataMaps);
+            } else if (dataMaps instanceof List) {
+                for (final Map<String, Object> dataMap : (List<Map<String, Object>>) dataMaps) {
+                    createXmlElements(document, documentFragment, dataMap);
+                }
+            } else {
+                throw new IllegalArgumentException("Unsupported data type for XML conversion");
             }
             return transformFragmentToString(documentFragment);
         } catch (final DOMException |  NullPointerException | ParserConfigurationException | TransformerException
index 36cee6d..03348f8 100644 (file)
@@ -1,6 +1,7 @@
 /*
  *  ============LICENSE_START=======================================================
  *  Copyright (C) 2025 Nordix Foundation
+ *  Modifications Copyright (C) 2025 TechMahindra Ltd.
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
@@ -84,6 +85,13 @@ class CpsFacadeImplSpec extends Specification {
             assert result[1].keySet()[0] == 'prefix2:path2'
     }
 
+    def 'Get multiple data nodes V3.'() {
+        when: 'get data node by dataspace and anchor'
+            def result = objectUnderTest.getDataNodesByAnchorV3('my dataspace', 'my anchor', 'my path', myFetchDescendantsOption)
+        then: 'all nodes (from the data service result) are returned'
+            assert result.size() == 2
+    }
+
     def 'Execute anchor query with attribute-axis.'() {
         given: 'the cps query service returns two attribute values'
             mockCpsQueryService.queryDataLeaf('my dataspace', 'my anchor', '/my/path/@myAttribute', Object) >> ['value1', 'value2']
index 268bdc7..5f9db83 100644 (file)
@@ -2,7 +2,7 @@
  * ============LICENSE_START=======================================================
  *  Copyright (C) 2022 Deutsche Telekom AG
  *  Modifications Copyright (c) 2023-2024 Nordix Foundation
- *  Modifications Copyright (C) 2024 TechMahindra Ltd.
+ *  Modifications Copyright (C) 2024-2025 TechMahindra Ltd.
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
@@ -79,11 +79,11 @@ class XmlFileUtilsSpec extends Specification {
         then: 'the result contains the expected XML'
             assert result == expectedXmlOutput
         where:
-            scenario                              | dataMaps                                                                                                                                 || expectedXmlOutput
-            'single XML branch'                   | [['branch': ['name': 'Left', 'nest': ['name': 'Small', 'birds': ['Sparrow', 'Owl']]]]]                                                   || '<branch><name>Left</name><nest><name>Small</name><birds>Sparrow</birds><birds>Owl</birds></nest></branch>'
-            'nested XML branch'                   | [['test-tree': [branch: [name: 'Left', nest: [name: 'Small', birds: 'Sparrow']]]]]                                                       || '<test-tree><branch><name>Left</name><nest><name>Small</name><birds>Sparrow</birds></nest></branch></test-tree>'
-            'list of branch within a test tree'   | [['test-tree': [branch: [[name: 'Left', nest: [name: 'Small', birds: 'Sparrow']], [name: 'Right', nest: [name: 'Big', birds: 'Owl']]]]]] || '<test-tree><branch><name>Left</name><nest><name>Small</name><birds>Sparrow</birds></nest></branch><branch><name>Right</name><nest><name>Big</name><birds>Owl</birds></nest></branch></test-tree>'
-            'list of birds under a nest'          | [['nest': ['name': 'Small', 'birds': ['Sparrow']]]]                                                                                      || '<nest><name>Small</name><birds>Sparrow</birds></nest>'
+            scenario                            | dataMaps                                                                                                                                 || expectedXmlOutput
+            'single XML branch'                 | ['branch': ['name': 'Left']]                                                                                                             || '<branch><name>Left</name></branch>'
+            'nested XML branch'                 | [['test-tree': [branch: [name: 'Left', nest: [name: 'Small', birds: 'Sparrow']]]]]                                                       || '<test-tree><branch><name>Left</name><nest><name>Small</name><birds>Sparrow</birds></nest></branch></test-tree>'
+            'list of branch within a test tree' | [['test-tree': [branch: [[name: 'Left', nest: [name: 'Small', birds: 'Sparrow']], [name: 'Right', nest: [name: 'Big', birds: 'Owl']]]]]] || '<test-tree><branch><name>Left</name><nest><name>Small</name><birds>Sparrow</birds></nest></branch><branch><name>Right</name><nest><name>Big</name><birds>Owl</birds></nest></branch></test-tree>'
+            'list of birds under a nest'        | [['nest': ['name': 'Small', 'birds': ['Sparrow']]]]                                                                                      || '<nest><name>Small</name><birds>Sparrow</birds></nest>'
     }
 
     def 'Convert data maps to XML with null or empty maps and lists'() {
@@ -102,6 +102,14 @@ class XmlFileUtilsSpec extends Specification {
             'mixed list with null values'    | [['branch': ['name': 'Left', 'nest': ['name': 'Small', 'birds': [null, 'Sparrow', null]]]]] || '<branch><name>Left</name><nest><name>Small</name><birds/><birds>Sparrow</birds><birds/></nest></branch>'
     }
 
+    def 'Converting data maps to xml with unsupported input type'() {
+        when: 'a non-Map and non-List object is passed'
+            convertDataMapsToXml("invalid")
+        then: 'an IllegalArgumentException is thrown'
+            def exception = thrown(IllegalArgumentException)
+            assert exception.message == "Unsupported data type for XML conversion"
+    }
+
     def 'Converting data maps to xml with no data'() {
         given: 'A list of maps where entry is null'
             def dataMapWithNull = [null]
index ccfddac..f944f34 100644 (file)
@@ -12,8 +12,6 @@ info:
   version: 3.6.2
 servers:
 - url: /cps/api
-security:
-- basicAuth: []
 tags:
 - description: cps Admin
   name: cps-admin
@@ -1183,6 +1181,7 @@ paths:
       x-codegen-request-body-name: xpath
   /v2/dataspaces/{dataspace-name}/anchors/{anchor-name}/node:
     get:
+      deprecated: true
       description: Get a node with an option to retrieve all the children for a given
         anchor and dataspace
       operationId: getNodeByDataspaceAndAnchorV2
@@ -1287,6 +1286,112 @@ paths:
       tags:
       - cps-data
       x-codegen-request-body-name: xpath
+  /v3/dataspaces/{dataspace-name}/anchors/{anchor-name}/node:
+    get:
+      description: Get a node with an option to retrieve all the children for a given
+        anchor and dataspace
+      operationId: getNodeByDataspaceAndAnchorV3
+      parameters:
+      - description: dataspace-name
+        in: path
+        name: dataspace-name
+        required: true
+        schema:
+          example: my-dataspace
+          type: string
+      - description: anchor-name
+        in: path
+        name: anchor-name
+        required: true
+        schema:
+          example: my-anchor
+          type: string
+      - description: "For more details on xpath, please refer https://docs.onap.org/projects/onap-cps/en/latest/xpath.html"
+        examples:
+          container xpath:
+            value: /shops/bookstore
+          list attributes xpath:
+            value: "/shops/bookstore/categories[@code=1]"
+        in: query
+        name: xpath
+        required: false
+        schema:
+          default: /
+          type: string
+      - description: "Number of descendants to query. Allowed values are 'none', 'all',\
+          \ 'direct', 1 (for direct), -1 (for all), 0 (for none) and any positive\
+          \ number."
+        in: query
+        name: descendants
+        required: false
+        schema:
+          default: none
+          example: "3"
+          type: string
+      - description: Content type in header
+        in: header
+        name: Content-Type
+        required: false
+        schema:
+          default: application/json
+          enum:
+          - application/json
+          - application/xml
+          type: string
+      responses:
+        "200":
+          content:
+            application/json:
+              examples:
+                dataSample:
+                  $ref: '#/components/examples/dataSampleForV3'
+                  value: null
+              schema:
+                type: object
+            application/xml:
+              examples:
+                dataSample:
+                  $ref: '#/components/examples/dataSampleXmlForV3'
+                  value: null
+              schema:
+                type: object
+                xml:
+                  name: stores
+          description: OK
+        "400":
+          content:
+            application/json:
+              example:
+                status: 400
+                message: Bad Request
+                details: The provided request is not valid
+              schema:
+                $ref: '#/components/schemas/ErrorMessage'
+          description: Bad Request
+        "403":
+          content:
+            application/json:
+              example:
+                status: 403
+                message: Request Forbidden
+                details: This request is forbidden
+              schema:
+                $ref: '#/components/schemas/ErrorMessage'
+          description: Forbidden
+        "500":
+          content:
+            application/json:
+              example:
+                status: 500
+                message: Internal Server Error
+                details: Internal Server Error occurred
+              schema:
+                $ref: '#/components/schemas/ErrorMessage'
+          description: Internal Server Error
+      summary: Get a node
+      tags:
+      - cps-data
+      x-codegen-request-body-name: xpath
   /{apiVersion}/dataspaces/{dataspace-name}/anchors/{anchor-name}/nodes:
     delete:
       description: Delete a datanode for a given dataspace and anchor given a node
@@ -2775,12 +2880,75 @@ components:
           categories:
           - code: 1
             name: SciFi
+          - books:
+            - title: Book 1
+              lang: N/A
+              price: 11
+              editions:
+              - 2009
+          - books:
+            - title: Book 2
+              lang: German
+              price: 39
+              editions:
+              - 2007
+              - 2013
+              - 2021
           - code: 2
             name: kids
+          - books:
+            - title: Book 3
+              lang: English
+              price: 15
+              editions:
+              - 2010
     dataSampleXml:
       value: <stores xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"> <bookstore xmlns="org:onap:ccsdk:sample">
         <bookstore-name>Chapters</bookstore-name> <categories> <code>1</code> <name>SciFi</name>
-        <code>2</code> <name>kids</name> </categories> </bookstore> </stores>
+        </categories> <categories> <books> <title>Book 1</title> <lang>N/A</lang>
+        <price>11</price> <editions>2009</editions> </books> </categories> <categories>
+        <books> <title>Book 2</title> <lang>German</lang> <price>39</price> <editions>2007</editions>
+        <editions>2013</editions> <editions>2021</editions> </books> </categories>
+        <categories> <code>2</code> <name>kids </name> <books> <title>Book 3</title>
+        <lang>English</lang> <price>15</price> <editions>2010</editions> </books>
+        </categories> </bookstore> </stores>
+    dataSampleForV3:
+      value:
+        test:bookstore:
+          bookstore-name: Chapters
+          categories:
+          - code: 1
+            name: SciFi
+            books:
+            - title: Book 1
+              lang: N/A
+              price: 11
+              editions:
+              - 2009
+            - title: Book 2
+              lang: German
+              price: 39
+              editions:
+              - 2007
+              - 2013
+              - 2021
+          - code: 2
+            name: kids
+            books:
+            - title: Book 3
+              lang: English
+              price: 15
+              editions:
+              - 2010
+    dataSampleXmlForV3:
+      value: <stores xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"> <bookstore xmlns="org:onap:ccsdk:sample">
+        <bookstore-name>Chapters</bookstore-name> <categories> <code>1</code> <name>SciFi</name>
+        <books> <title>Book 1</title> <lang>N/A</lang> <price>11</price> <editions>2009</editions>
+        </books> <books> <title>Book 2</title> <lang>German</lang> <price>39</price>
+        <editions>2007</editions> <editions>2013</editions> <editions>2021</editions>
+        </books> </categories> <categories> <code>2</code> <name>kids</name> <books>
+        <title>Book 3</title> <lang>English</lang> <price>15</price> <editions>2010</editions>
+        </books> </categories> </bookstore> </stores>
     deltaReportSample:
       value:
       - action: create
@@ -3198,7 +3366,3 @@ components:
       required:
       - json
       type: object
-  securitySchemes:
-    basicAuth:
-      scheme: basic
-      type: http
index 069239b..c1ca0ae 100644 (file)
@@ -5,8 +5,6 @@ info:
   version: 3.6.2
 servers:
 - url: /ncmpInventory
-security:
-- basicAuth: []
 paths:
   /v1/ch:
     post:
@@ -510,7 +508,3 @@ components:
         moduleName:
           example: my-module
           type: string
-  securitySchemes:
-    basicAuth:
-      scheme: basic
-      type: http
index ee9c46b..aa72e4b 100644 (file)
@@ -5,8 +5,6 @@ info:
   version: 3.6.2
 servers:
 - url: /ncmp
-security:
-- basicAuth: []
 paths:
   /v1/ch/{cm-handle}/data/ds/{datastore-name}:
     delete:
@@ -2113,7 +2111,3 @@ components:
           example: Bad Request
           type: string
       type: object
-  securitySchemes:
-    basicAuth:
-      scheme: basic
-      type: http
index f59e4fc..f222901 100644 (file)
@@ -91,7 +91,7 @@ class TestScrapeMetrics(unittest.TestCase):
         with open(metrics_file, 'r') as f:
             lines = f.readlines()
 
-        expected_number_of_metrics = 57
+        expected_number_of_metrics = 58
         expected_number_of_lines = expected_number_of_metrics + 1  # Header
         self.assertEqual(len(lines), expected_number_of_lines, f"metrics.csv does not have {expected_number_of_lines} lines.")