From ff2096e9e81040c0a64a714a358694e058847ebd Mon Sep 17 00:00:00 2001 From: Rudrangi Anupriya Date: Mon, 5 May 2025 20:04:39 +0530 Subject: [PATCH] Inconsistency With JSON Response(List Items) Using GetANode API 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 --- cps-rest/docs/openapi/components.yml | 126 ++++++++++++++- .../{cpsDataV2.yml => cpsDataV2Deprecated.yml} | 2 + cps-rest/docs/openapi/cpsDataV3.yml | 56 +++++++ cps-rest/docs/openapi/openapi.yml | 5 +- .../cps/rest/controller/DataRestController.java | 17 +- .../rest/controller/DataRestControllerSpec.groovy | 25 +++ .../src/main/java/org/onap/cps/api/CpsFacade.java | 23 +++ .../main/java/org/onap/cps/impl/CpsFacadeImpl.java | 11 ++ .../main/java/org/onap/cps/utils/DataMapUtils.java | 18 +-- .../main/java/org/onap/cps/utils/DataMapper.java | 26 +++ .../main/java/org/onap/cps/utils/XmlFileUtils.java | 14 +- .../org/onap/cps/impl/CpsFacadeImplSpec.groovy | 8 + .../org/onap/cps/utils/XmlFileUtilsSpec.groovy | 20 ++- docs/api/swagger/cps/openapi.yaml | 178 ++++++++++++++++++++- docs/api/swagger/ncmp/openapi-inventory.yaml | 6 - docs/api/swagger/ncmp/openapi.yaml | 6 - docs/test_ScrapeMetrics.py | 2 +- 17 files changed, 492 insertions(+), 51 deletions(-) rename cps-rest/docs/openapi/{cpsDataV2.yml => cpsDataV2Deprecated.yml} (96%) create mode 100644 cps-rest/docs/openapi/cpsDataV3.yml diff --git a/cps-rest/docs/openapi/components.yml b/cps-rest/docs/openapi/components.yml index 43a311872a..fe09c41e0a 100644 --- a/cps-rest/docs/openapi/components.yml +++ b/cps-rest/docs/openapi/components.yml @@ -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: + + Chapters + + 1 + SciFi + + + + Book 1 + N/A + 11 + 2009 + + + + + Book 2 + German + 39 + 2007 + 2013 + 2021 + + + + 2 + kids + + + Book 3 + English + 15 + 2010 + + + + + dataSampleXmlForV3: + value: + - Chapters - - 1 - SciFi - 2 - kids - + Chapters + + 1 + SciFi + + Book 1 + N/A + 11 + 2009 + + + Book 2 + German + 39 + 2007 + 2013 + 2021 + + + + 2 + kids + + Book 3 + English + 15 + 2010 + + - + dataSampleAcrossAnchors: value: - anchorName: bookstore1 diff --git a/cps-rest/docs/openapi/cpsDataV2.yml b/cps-rest/docs/openapi/cpsDataV2Deprecated.yml similarity index 96% rename from cps-rest/docs/openapi/cpsDataV2.yml rename to cps-rest/docs/openapi/cpsDataV2Deprecated.yml index 7afda705f7..0d17b18273 100644 --- a/cps-rest/docs/openapi/cpsDataV2.yml +++ b/cps-rest/docs/openapi/cpsDataV2Deprecated.yml @@ -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 index 0000000000..9b296d81ce --- /dev/null +++ b/cps-rest/docs/openapi/cpsDataV3.yml @@ -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 diff --git a/cps-rest/docs/openapi/openapi.yml b/cps-rest/docs/openapi/openapi.yml index 747531b30e..e33720c63f 100644 --- a/cps-rest/docs/openapi/openapi.yml +++ b/cps-rest/docs/openapi/openapi.yml @@ -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' diff --git a/cps-rest/src/main/java/org/onap/cps/rest/controller/DataRestController.java b/cps-rest/src/main/java/org/onap/cps/rest/controller/DataRestController.java index 90500f3955..f2dc4e9208 100755 --- a/cps-rest/src/main/java/org/onap/cps/rest/controller/DataRestController.java +++ b/cps-rest/src/main/java/org/onap/cps/rest/controller/DataRestController.java @@ -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 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 dataNodesAsMap = + cpsFacade.getDataNodesByAnchorV3(dataspaceName, anchorName, xpath, fetchDescendantsOption); + return buildResponseEntity(dataNodesAsMap, contentType); + } + @Override public ResponseEntity 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 buildResponseEntity(final List> dataMaps, - final ContentType contentType) { + private ResponseEntity buildResponseEntity(final Object dataMaps, final ContentType contentType) { final String responseData; if (ContentType.XML.equals(contentType)) { responseData = XmlFileUtils.convertDataMapsToXml(dataMaps); diff --git a/cps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy b/cps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy index ba5104acf9..3762b6e853 100755 --- a/cps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy +++ b/cps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy @@ -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 || 'Book 1Book 2' + '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" diff --git a/cps-service/src/main/java/org/onap/cps/api/CpsFacade.java b/cps-service/src/main/java/org/onap/cps/api/CpsFacade.java index 8933f02cb4..5a9f048caa 100644 --- a/cps-service/src/main/java/org/onap/cps/api/CpsFacade.java +++ b/cps-service/src/main/java/org/onap/cps/api/CpsFacade.java @@ -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 getDataNodesByAnchorV3(String dataspaceName, + String anchorName, + String xpath, + FetchDescendantsOption fetchDescendantsOption); + /** * Query the given anchor using a cps path expression. * diff --git a/cps-service/src/main/java/org/onap/cps/impl/CpsFacadeImpl.java b/cps-service/src/main/java/org/onap/cps/impl/CpsFacadeImpl.java index 35a03685b6..09c24f4acf 100644 --- a/cps-service/src/main/java/org/onap/cps/impl/CpsFacadeImpl.java +++ b/cps-service/src/main/java/org/onap/cps/impl/CpsFacadeImpl.java @@ -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 getDataNodesByAnchorV3(final String dataspaceName, + final String anchorName, + final String xpath, + final FetchDescendantsOption fetchDescendantsOption) { + final Collection dataNodes = cpsDataService.getDataNodes(dataspaceName, anchorName, xpath, + fetchDescendantsOption); + return dataMapper.toDataMapForApiV3(dataspaceName, anchorName, dataNodes); + } + @Override public List> executeAnchorQuery(final String dataspaceName, final String anchorName, diff --git a/cps-service/src/main/java/org/onap/cps/utils/DataMapUtils.java b/cps-service/src/main/java/org/onap/cps/utils/DataMapUtils.java index 52cb0eae52..fabae6dc51 100644 --- a/cps-service/src/main/java/org/onap/cps/utils/DataMapUtils.java +++ b/cps-service/src/main/java/org/onap/cps/utils/DataMapUtils.java @@ -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); } diff --git a/cps-service/src/main/java/org/onap/cps/utils/DataMapper.java b/cps-service/src/main/java/org/onap/cps/utils/DataMapper.java index 29d61ffcc4..b686a1dcac 100644 --- a/cps-service/src/main/java/org/onap/cps/utils/DataMapper.java +++ b/cps-service/src/main/java/org/onap/cps/utils/DataMapper.java @@ -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 toDataMapForApiV3(final String dataspaceName, final String anchorName, + final Collection 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 diff --git a/cps-service/src/main/java/org/onap/cps/utils/XmlFileUtils.java b/cps-service/src/main/java/org/onap/cps/utils/XmlFileUtils.java index c9c1ece2c5..01093d7d74 100644 --- a/cps-service/src/main/java/org/onap/cps/utils/XmlFileUtils.java +++ b/cps-service/src/main/java/org/onap/cps/utils/XmlFileUtils.java @@ -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> 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 dataMap : dataMaps) { - createXmlElements(document, documentFragment, dataMap); + if (dataMaps instanceof Map) { + createXmlElements(document, documentFragment, (Map) dataMaps); + } else if (dataMaps instanceof List) { + for (final Map dataMap : (List>) dataMaps) { + createXmlElements(document, documentFragment, dataMap); + } + } else { + throw new IllegalArgumentException("Unsupported data type for XML conversion"); } return transformFragmentToString(documentFragment); } catch (final DOMException | NullPointerException | ParserConfigurationException | TransformerException diff --git a/cps-service/src/test/groovy/org/onap/cps/impl/CpsFacadeImplSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/impl/CpsFacadeImplSpec.groovy index 36cee6d627..03348f8c97 100644 --- a/cps-service/src/test/groovy/org/onap/cps/impl/CpsFacadeImplSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/impl/CpsFacadeImplSpec.groovy @@ -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'] diff --git a/cps-service/src/test/groovy/org/onap/cps/utils/XmlFileUtilsSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/utils/XmlFileUtilsSpec.groovy index 268bdc7d35..5f9db83734 100644 --- a/cps-service/src/test/groovy/org/onap/cps/utils/XmlFileUtilsSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/utils/XmlFileUtilsSpec.groovy @@ -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']]]]] || 'LeftSmallSparrowOwl' - 'nested XML branch' | [['test-tree': [branch: [name: 'Left', nest: [name: 'Small', birds: 'Sparrow']]]]] || 'LeftSmallSparrow' - 'list of branch within a test tree' | [['test-tree': [branch: [[name: 'Left', nest: [name: 'Small', birds: 'Sparrow']], [name: 'Right', nest: [name: 'Big', birds: 'Owl']]]]]] || 'LeftSmallSparrowRightBigOwl' - 'list of birds under a nest' | [['nest': ['name': 'Small', 'birds': ['Sparrow']]]] || 'SmallSparrow' + scenario | dataMaps || expectedXmlOutput + 'single XML branch' | ['branch': ['name': 'Left']] || 'Left' + 'nested XML branch' | [['test-tree': [branch: [name: 'Left', nest: [name: 'Small', birds: 'Sparrow']]]]] || 'LeftSmallSparrow' + 'list of branch within a test tree' | [['test-tree': [branch: [[name: 'Left', nest: [name: 'Small', birds: 'Sparrow']], [name: 'Right', nest: [name: 'Big', birds: 'Owl']]]]]] || 'LeftSmallSparrowRightBigOwl' + 'list of birds under a nest' | [['nest': ['name': 'Small', 'birds': ['Sparrow']]]] || 'SmallSparrow' } 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]]]]] || 'LeftSmallSparrow' } + 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] diff --git a/docs/api/swagger/cps/openapi.yaml b/docs/api/swagger/cps/openapi.yaml index ccfddaced4..f944f34730 100644 --- a/docs/api/swagger/cps/openapi.yaml +++ b/docs/api/swagger/cps/openapi.yaml @@ -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: Chapters 1 SciFi - 2 kids + Book 1 N/A + 11 2009 + Book 2 German 39 2007 + 2013 2021 + 2 kids Book 3 + English 15 2010 + + 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: + Chapters 1 SciFi + Book 1 N/A 11 2009 + Book 2 German 39 + 2007 2013 2021 + 2 kids + Book 3 English 15 2010 + deltaReportSample: value: - action: create @@ -3198,7 +3366,3 @@ components: required: - json type: object - securitySchemes: - basicAuth: - scheme: basic - type: http diff --git a/docs/api/swagger/ncmp/openapi-inventory.yaml b/docs/api/swagger/ncmp/openapi-inventory.yaml index 069239b4ff..c1ca0ae9ac 100644 --- a/docs/api/swagger/ncmp/openapi-inventory.yaml +++ b/docs/api/swagger/ncmp/openapi-inventory.yaml @@ -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 diff --git a/docs/api/swagger/ncmp/openapi.yaml b/docs/api/swagger/ncmp/openapi.yaml index ee9c46b5c8..aa72e4b420 100644 --- a/docs/api/swagger/ncmp/openapi.yaml +++ b/docs/api/swagger/ncmp/openapi.yaml @@ -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 diff --git a/docs/test_ScrapeMetrics.py b/docs/test_ScrapeMetrics.py index f59e4fc062..f222901e48 100644 --- a/docs/test_ScrapeMetrics.py +++ b/docs/test_ScrapeMetrics.py @@ -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.") -- 2.16.6