diff options
10 files changed, 192 insertions, 54 deletions
diff --git a/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java b/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java index ded234bb48..4dbbfd2fe5 100644 --- a/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java +++ b/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java @@ -48,6 +48,8 @@ import org.onap.cps.spi.FetchDescendantsOption; import org.onap.cps.spi.entities.AnchorEntity; import org.onap.cps.spi.entities.DataspaceEntity; import org.onap.cps.spi.entities.FragmentEntity; +import org.onap.cps.spi.entities.SchemaSetEntity; +import org.onap.cps.spi.entities.YangResourceEntity; import org.onap.cps.spi.exceptions.AlreadyDefinedException; import org.onap.cps.spi.exceptions.ConcurrencyException; import org.onap.cps.spi.exceptions.CpsAdminException; @@ -60,6 +62,8 @@ import org.onap.cps.spi.repository.DataspaceRepository; import org.onap.cps.spi.repository.FragmentRepository; import org.onap.cps.spi.utils.SessionManager; import org.onap.cps.utils.JsonObjectMapper; +import org.onap.cps.yang.YangTextSchemaSourceSetBuilder; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.stereotype.Service; @@ -242,16 +246,27 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService } private DataNode toDataNode(final FragmentEntity fragmentEntity, - final FetchDescendantsOption fetchDescendantsOption) { + final FetchDescendantsOption fetchDescendantsOption) { final List<DataNode> childDataNodes = getChildDataNodes(fragmentEntity, fetchDescendantsOption); Map<String, Object> leaves = new HashMap<>(); if (fragmentEntity.getAttributes() != null) { leaves = jsonObjectMapper.convertJsonString(fragmentEntity.getAttributes(), Map.class); } return new DataNodeBuilder() - .withXpath(fragmentEntity.getXpath()) - .withLeaves(leaves) - .withChildDataNodes(childDataNodes).build(); + .withModuleNamePrefix(getFirstModuleName(fragmentEntity)) + .withXpath(fragmentEntity.getXpath()) + .withLeaves(leaves) + .withChildDataNodes(childDataNodes).build(); + } + + private String getFirstModuleName(final FragmentEntity fragmentEntity) { + final SchemaSetEntity schemaSetEntity = fragmentEntity.getAnchor().getSchemaSet(); + final Map<String, String> yangResourceNameToContent = + schemaSetEntity.getYangResources().stream().collect( + Collectors.toMap(YangResourceEntity::getName, YangResourceEntity::getContent)); + final SchemaContext schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent) + .getSchemaContext(); + return schemaContext.getModules().iterator().next().getName(); } private List<DataNode> getChildDataNodes(final FragmentEntity fragmentEntity, diff --git a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceSpec.groovy b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceSpec.groovy index a96b6aff9b..bb80199d09 100644 --- a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceSpec.groovy +++ b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceSpec.groovy @@ -22,7 +22,11 @@ package org.onap.cps.spi.impl import com.fasterxml.jackson.databind.ObjectMapper import org.hibernate.StaleStateException import org.onap.cps.spi.FetchDescendantsOption +import org.onap.cps.spi.entities.AnchorEntity +import org.onap.cps.spi.entities.DataspaceEntity import org.onap.cps.spi.entities.FragmentEntity +import org.onap.cps.spi.entities.SchemaSetEntity +import org.onap.cps.spi.entities.YangResourceEntity import org.onap.cps.spi.exceptions.ConcurrencyException import org.onap.cps.spi.exceptions.DataValidationException import org.onap.cps.spi.model.DataNodeBuilder @@ -31,6 +35,10 @@ import org.onap.cps.spi.repository.DataspaceRepository import org.onap.cps.spi.repository.FragmentRepository import org.onap.cps.spi.utils.SessionManager import org.onap.cps.utils.JsonObjectMapper +import org.onap.cps.yang.YangTextSchemaSourceSet +import org.onap.cps.yang.YangTextSchemaSourceSetBuilder +import org.opendaylight.yangtools.yang.model.api.SchemaContext +import spock.lang.Shared import spock.lang.Specification class CpsDataPersistenceServiceSpec extends Specification { @@ -44,6 +52,25 @@ class CpsDataPersistenceServiceSpec extends Specification { def objectUnderTest = new CpsDataPersistenceServiceImpl( mockDataspaceRepository, mockAnchorRepository, mockFragmentRepository, jsonObjectMapper,mockSessionManager) + @Shared + def NEW_RESOURCE_CONTENT = 'module stores {\n' + + ' yang-version 1.1;\n' + + ' namespace "org:onap:ccsdk:sample";\n' + + '\n' + + ' prefix book-store;\n' + + '\n' + + ' revision "2020-09-15" {\n' + + ' description\n' + + ' "Sample Model";\n' + + ' }' + + '}' + + @Shared + def yangResourceSet = [new YangResourceEntity(moduleName: 'moduleName', content: NEW_RESOURCE_CONTENT, + name: 'sampleYangResource' + )] as Set + + def 'Handling of StaleStateException (caused by concurrent updates) during data node tree update.'() { def parentXpath = '/parent-01' @@ -51,67 +78,68 @@ class CpsDataPersistenceServiceSpec extends Specification { def myAnchorName = 'my-anchor' given: 'data node object' - def submittedDataNode = new DataNodeBuilder() - .withXpath(parentXpath) - .withLeaves(['leaf-name': 'leaf-value']) - .build() + def submittedDataNode = new DataNodeBuilder() + .withXpath(parentXpath) + .withLeaves(['leaf-name': 'leaf-value']) + .build() and: 'fragment to be updated' - mockFragmentRepository.getByDataspaceAndAnchorAndXpath(_, _, _) >> { - def fragmentEntity = new FragmentEntity() - fragmentEntity.setXpath(parentXpath) - fragmentEntity.setChildFragments(Collections.emptySet()) - return fragmentEntity - } + mockFragmentRepository.getByDataspaceAndAnchorAndXpath(_, _, _) >> { + def fragmentEntity = new FragmentEntity() + fragmentEntity.setXpath(parentXpath) + fragmentEntity.setChildFragments(Collections.emptySet()) + return fragmentEntity + } and: 'data node is concurrently updated by another transaction' - mockFragmentRepository.save(_) >> { throw new StaleStateException("concurrent updates") } + mockFragmentRepository.save(_) >> { throw new StaleStateException("concurrent updates") } when: 'attempt to update data node' - objectUnderTest.replaceDataNodeTree(myDataspaceName, myAnchorName, submittedDataNode) + objectUnderTest.replaceDataNodeTree(myDataspaceName, myAnchorName, submittedDataNode) then: 'concurrency exception is thrown' - def concurrencyException = thrown(ConcurrencyException) - assert concurrencyException.getDetails().contains(myDataspaceName) - assert concurrencyException.getDetails().contains(myAnchorName) - assert concurrencyException.getDetails().contains(parentXpath) + def concurrencyException = thrown(ConcurrencyException) + assert concurrencyException.getDetails().contains(myDataspaceName) + assert concurrencyException.getDetails().contains(myAnchorName) + assert concurrencyException.getDetails().contains(parentXpath) } def 'Retrieving a data node with a property JSON value of #scenario'() { given: 'a fragment with a property JSON value of #scenario' mockFragmentRepository.getByDataspaceAndAnchorAndXpath(_, _, _) >> { new FragmentEntity(childFragments: Collections.emptySet(), - attributes: "{\"some attribute\": ${dataString}}") + attributes: "{\"some attribute\": ${dataString}}", + anchor: new AnchorEntity(schemaSet: new SchemaSetEntity(yangResources: yangResourceSet ))) } when: 'getting the data node represented by this fragment' - def dataNode = objectUnderTest.getDataNode('my-dataspace', 'my-anchor', - '/parent-01', FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) + def dataNode = objectUnderTest.getDataNode('my-dataspace', 'my-anchor', + '/parent-01', FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) then: 'the leaf is of the correct value and data type' - def attributeValue = dataNode.leaves.get('some attribute') - assert attributeValue == expectedValue - assert attributeValue.class == expectedDataClass + def attributeValue = dataNode.leaves.get('some attribute') + assert attributeValue == expectedValue + assert attributeValue.class == expectedDataClass where: 'the following Data Type is passed' - scenario | dataString || expectedValue | expectedDataClass - 'just numbers' | '15174' || 15174 | Integer - 'number with dot' | '15174.32' || 15174.32 | Double - 'number with 0 value after dot' | '15174.0' || 15174.0 | Double - 'number with 0 value before dot' | '0.32' || 0.32 | Double - 'number higher than max int' | '2147483648' || 2147483648 | Long - 'just text' | '"Test"' || 'Test' | String - 'number with exponent' | '1.2345e5' || 1.2345e5 | Double - 'number higher than max int with dot' | '123456789101112.0' || 123456789101112.0 | Double - 'text and numbers' | '"String = \'1234\'"' || "String = '1234'" | String - 'number as String' | '"12345"' || '12345' | String + scenario | dataString || expectedValue | expectedDataClass + 'just numbers' | '15174' || 15174 | Integer + 'number with dot' | '15174.32' || 15174.32 | Double + 'number with 0 value after dot' | '15174.0' || 15174.0 | Double + 'number with 0 value before dot' | '0.32' || 0.32 | Double + 'number higher than max int' | '2147483648' || 2147483648 | Long + 'just text' | '"Test"' || 'Test' | String + 'number with exponent' | '1.2345e5' || 1.2345e5 | Double + 'number higher than max int with dot' | '123456789101112.0' || 123456789101112.0 | Double + 'text and numbers' | '"String = \'1234\'"' || "String = '1234'" | String + 'number as String' | '"12345"' || '12345' | String } def 'Retrieving a data node with invalid JSON'() { given: 'a fragment with invalid JSON' - mockFragmentRepository.getByDataspaceAndAnchorAndXpath(_, _, _) >> { - new FragmentEntity(childFragments: Collections.emptySet(), attributes: '{invalid json') - } + mockFragmentRepository.getByDataspaceAndAnchorAndXpath(_, _, _) >> { + new FragmentEntity(childFragments: Collections.emptySet(), attributes: '{invalid json') + } when: 'getting the data node represented by this fragment' - def dataNode = objectUnderTest.getDataNode('my-dataspace', 'my-anchor', - '/parent-01', FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) + def dataNode = objectUnderTest.getDataNode('my-dataspace', 'my-anchor', + '/parent-01', FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) then: 'a data validation exception is thrown' - thrown(DataValidationException) + thrown(DataValidationException) } def 'start session'() { diff --git a/cps-ri/src/test/resources/data/cps-path-query.sql b/cps-ri/src/test/resources/data/cps-path-query.sql index d1a62209eb..c406203c8c 100644 --- a/cps-ri/src/test/resources/data/cps-path-query.sql +++ b/cps-ri/src/test/resources/data/cps-path-query.sql @@ -25,6 +25,28 @@ INSERT INTO DATASPACE (ID, NAME) VALUES INSERT INTO SCHEMA_SET (ID, NAME, DATASPACE_ID) VALUES (2001, 'SCHEMA-SET-001', 1001); +INSERT INTO YANG_RESOURCE (ID, NAME, CONTENT, CHECKSUM, MODULE_NAME, REVISION) VALUES + (4001, 'TEST','', 'SAMPLECHECKSUM','TESTMODULENAME', 'SAMPLEREVISION'); + +UPDATE YANG_RESOURCE SET +content = 'module stores { + yang-version 1.1; + namespace "org:onap:ccsdk:sample"; + + prefix book-store; + + revision "2020-09-15" { + description + "Sample Model"; + } + } +' +where ID = 4001; + + +INSERT INTO SCHEMA_SET_YANG_RESOURCES (SCHEMA_SET_ID, YANG_RESOURCE_ID) VALUES + (2001, 4001); + INSERT INTO ANCHOR (ID, NAME, DATASPACE_ID, SCHEMA_SET_ID) VALUES (1003, 'ANCHOR-004', 1001, 2001); diff --git a/cps-ri/src/test/resources/data/fragment.sql b/cps-ri/src/test/resources/data/fragment.sql index 4106541061..fd05900e28 100755 --- a/cps-ri/src/test/resources/data/fragment.sql +++ b/cps-ri/src/test/resources/data/fragment.sql @@ -27,6 +27,27 @@ INSERT INTO DATASPACE (ID, NAME) VALUES INSERT INTO SCHEMA_SET (ID, NAME, DATASPACE_ID) VALUES (2001, 'SCHEMA-SET-001', 1001); +INSERT INTO YANG_RESOURCE (ID, NAME, CONTENT, CHECKSUM, MODULE_NAME, REVISION) VALUES + (4001, 'TEST','', 'SAMPLECHECKSUM','TESTMODULENAME', 'SAMPLEREVISION'); + +UPDATE YANG_RESOURCE SET +content = 'module stores { + yang-version 1.1; + namespace "org:onap:ccsdk:sample"; + + prefix book-store; + + revision "2020-09-15" { + description + "Sample Model"; + } + } +' +where ID = 4001; + +INSERT INTO SCHEMA_SET_YANG_RESOURCES (SCHEMA_SET_ID, YANG_RESOURCE_ID) VALUES + (2001, 4001); + INSERT INTO ANCHOR (ID, NAME, DATASPACE_ID, SCHEMA_SET_ID) VALUES (3001, 'ANCHOR-001', 1001, 2001), (3003, 'ANCHOR-003', 1001, 2001), diff --git a/cps-service/src/main/java/org/onap/cps/spi/model/DataNode.java b/cps-service/src/main/java/org/onap/cps/spi/model/DataNode.java index 43aa06b81b..d80306bae8 100644 --- a/cps-service/src/main/java/org/onap/cps/spi/model/DataNode.java +++ b/cps-service/src/main/java/org/onap/cps/spi/model/DataNode.java @@ -42,6 +42,7 @@ public class DataNode { private String anchorName; private ModuleReference moduleReference; private String xpath; + private String moduleNamePrefix; private Map<String, Object> leaves = Collections.emptyMap(); private Collection<String> xpathsChildren; private Collection<DataNode> childDataNodes = Collections.emptySet(); diff --git a/cps-service/src/main/java/org/onap/cps/spi/model/DataNodeBuilder.java b/cps-service/src/main/java/org/onap/cps/spi/model/DataNodeBuilder.java index 4a9957deb4..f2bde03a01 100644 --- a/cps-service/src/main/java/org/onap/cps/spi/model/DataNodeBuilder.java +++ b/cps-service/src/main/java/org/onap/cps/spi/model/DataNodeBuilder.java @@ -2,6 +2,7 @@ * ============LICENSE_START======================================================= * Copyright (C) 2021 Bell Canada. All rights reserved. * Modifications Copyright (C) 2021 Pantheon.tech + * Modifications Copyright (C) 2022 Nordix Foundation. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -45,6 +46,7 @@ public class DataNodeBuilder { private NormalizedNode<?, ?> normalizedNodeTree; private String xpath; + private String moduleNamePrefix; private String parentNodeXpath = ""; private Map<String, Object> leaves = Collections.emptyMap(); private Collection<DataNode> childDataNodes = Collections.emptySet(); @@ -84,6 +86,17 @@ public class DataNodeBuilder { } /** + * To use module name for prefix for creating {@link DataNode}. + * + * @param moduleNamePrefix module name as prefix + * @return DataNodeBuilder + */ + public DataNodeBuilder withModuleNamePrefix(final String moduleNamePrefix) { + this.moduleNamePrefix = moduleNamePrefix; + return this; + } + + /** * To use attributes for creating {@link DataNode}. * * @param leaves for the data node @@ -136,6 +149,7 @@ public class DataNodeBuilder { private DataNode buildFromAttributes() { final var dataNode = new DataNode(); dataNode.setXpath(xpath); + dataNode.setModuleNamePrefix(moduleNamePrefix); dataNode.setLeaves(leaves); dataNode.setChildDataNodes(childDataNodes); return dataNode; 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 42719d9b3c..ff5204ff6c 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 @@ -1,7 +1,7 @@ /* * ============LICENSE_START======================================================= * Copyright (C) 2021 Pantheon.tech - * Modifications (C) 2021 Nordix Foundation + * Modifications (C) 2021-2022 Nordix Foundation * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -44,7 +44,7 @@ public class DataMapUtils { */ public static Map<String, Object> toDataMapWithIdentifier(final DataNode dataNode) { return ImmutableMap.<String, Object>builder() - .put(getNodeIdentifier(dataNode.getXpath()), toDataMap(dataNode)) + .put(getNodeIdentifierWithPrefix(dataNode.getXpath(), dataNode.getModuleNamePrefix()), toDataMap(dataNode)) .build(); } @@ -96,6 +96,13 @@ public class DataMapUtils { return toIndex > 0 ? xpath.substring(fromIndex, toIndex) : xpath.substring(fromIndex); } + private static String getNodeIdentifierWithPrefix(final String xpath, final String moduleNamePrefix) { + if (moduleNamePrefix != null) { + return moduleNamePrefix + ":" + getNodeIdentifier(xpath); + } + return getNodeIdentifier(xpath); + } + private static boolean isContainerNode(final String xpath) { return !isListElement(xpath); } diff --git a/cps-service/src/test/groovy/org/onap/cps/spi/model/DataNodeBuilderSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/spi/model/DataNodeBuilderSpec.groovy index ce54ead2a0..16d4efc273 100644 --- a/cps-service/src/test/groovy/org/onap/cps/spi/model/DataNodeBuilderSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/spi/model/DataNodeBuilderSpec.groovy @@ -22,6 +22,7 @@ package org.onap.cps.spi.model import org.onap.cps.TestUtils import org.onap.cps.spi.model.DataNodeBuilder +import org.onap.cps.utils.DataMapUtils import org.onap.cps.utils.YangUtils import org.onap.cps.yang.YangTextSchemaSourceSetBuilder import org.opendaylight.yangtools.yang.common.QName @@ -172,6 +173,22 @@ class DataNodeBuilderSpec extends Specification { 'NormalizedNode is an unsupported type' | 'not supported' | Mock(NormalizedNode) | 0 | [ ] } + def 'Use of adding the module name prefix attribute of data node.'() { + when: 'data node is built with a prefix' + def testDataNode = new DataNodeBuilder() + .withModuleNamePrefix('sampleModuleNamePrefix') + .withXpath(xPath) + .withLeaves(sampleLeaves) + .build() + then: 'the result when node request is a #scenario includes the correct prefix' + def result = new DataMapUtils().toDataMapWithIdentifier(testDataNode) + result.toString() == expectedResult + where: 'the following parameters are used' + scenario | xPath | sampleLeaves | expectedResult + 'list attribute' | '/test-tree/branch[@name=\'Right\']/nest' | [name: 'Big', birds: ['Owl']] | '{sampleModuleNamePrefix:nest={name=Big, birds=[Owl]}}' + 'container xpath' | '/test-tree/branch[@name=\'Left\']' | [name: 'Left'] | '{sampleModuleNamePrefix:branch={name=Left}}' + } + def static assertLeavesMaps(actualLeavesMap, expectedLeavesMap) { expectedLeavesMap.each { key, value -> { diff --git a/cps-service/src/test/groovy/org/onap/cps/utils/DataMapUtilsSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/utils/DataMapUtilsSpec.groovy index 90563c0c16..24e8061b53 100644 --- a/cps-service/src/test/groovy/org/onap/cps/utils/DataMapUtilsSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/utils/DataMapUtilsSpec.groovy @@ -1,7 +1,7 @@ /* * ============LICENSE_START======================================================= * Copyright (C) 2021 Pantheon.tech - * Modifications Copyright (C) 2020 Nordix Foundation + * Modifications Copyright (C) 2020-2022 Nordix Foundation * Modifications Copyright (C) 2022 Bell Canada. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); @@ -29,13 +29,13 @@ class DataMapUtilsSpec extends Specification { def noChildren = [] def dataNode = buildDataNode( - "/parent",[parentLeaf:'parentLeafValue', parentLeafList:['parentLeafListEntry1','parentLeafListEntry2']],[ - buildDataNode('/parent/child-list[@id=1]',[listElementLeaf:'listElement1leafValue'],noChildren), - buildDataNode('/parent/child-list[@id=2]',[listElementLeaf:'listElement2leafValue'],noChildren), - buildDataNode('/parent/child-object',[childLeaf:'childLeafValue'], - [buildDataNode('/parent/child-object/grand-child-object',[grandChildLeaf:'grandChildLeafValue'],noChildren)] - ), - ]) + "/parent",[parentLeaf:'parentLeafValue', parentLeafList:['parentLeafListEntry1','parentLeafListEntry2']],[ + buildDataNode('/parent/child-list[@id=1]',[listElementLeaf:'listElement1leafValue'],noChildren), + buildDataNode('/parent/child-list[@id=2]',[listElementLeaf:'listElement2leafValue'],noChildren), + buildDataNode('/parent/child-object',[childLeaf:'childLeafValue'], + [buildDataNode('/parent/child-object/grand-child-object',[grandChildLeaf:'grandChildLeafValue'],noChildren)] + ), + ]) static def buildDataNode(xpath, leaves, children) { return new DataNodeBuilder().withXpath(xpath).withLeaves(leaves).withChildDataNodes(children).build() @@ -81,4 +81,16 @@ class DataMapUtilsSpec extends Specification { and: 'leaves for grandchild element is populated under its node identifier' parentNode.'child-object'.'grand-child-object'.grandChildLeaf == 'grandChildLeafValue' } + + def 'Adding prefix to data node identifier.'() { + when: 'a valid xPath is passed to the addPrefixToXpath method' + def result = new DataMapUtils().getNodeIdentifierWithPrefix(xPath,'sampleModuleName') + then: 'the correct modified node identifier is given' + assert result == expectedNodeIdentifier + where: 'the following parameters are used' + scenario | xPath | expectedNodeIdentifier + 'container xpath' | '/bookstore' | 'sampleModuleName:bookstore' + 'xpath contains list attribute' | '/bookstore/categories[@code=1]' | 'sampleModuleName:categories' + } } + diff --git a/csit/tests/cps-data/cps-data.robot b/csit/tests/cps-data/cps-data.robot index 55667c3c12..844b5d53f6 100644 --- a/csit/tests/cps-data/cps-data.robot +++ b/csit/tests/cps-data/cps-data.robot @@ -1,6 +1,7 @@ # ============LICENSE_START======================================================= # Copyright (c) 2021 Pantheon.tech. # Modifications Copyright (C) 2022 Bell Canada. +# Modifications Copyright (C) 2022 Nordix Foundation. # ================================================================================ # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -46,7 +47,7 @@ Get Data Node by XPath ${params}= Create Dictionary xpath=/test-tree/branch[@name='Left']/nest ${headers}= Create Dictionary Authorization=${auth} ${response}= Get On Session CPS_URL ${uri} params=${params} headers=${headers} expected_status=200 - ${responseJson}= Set Variable ${response.json()['nest']} + ${responseJson}= Set Variable ${response.json()['test-tree:nest']} Should Be Equal As Strings ${responseJson['name']} Small |