Merge "Bug 1125: Added regression test"
[controller.git] / opendaylight / md-sal / sal-rest-connector / src / test / java / org / opendaylight / controller / sal / restconf / impl / test / RestGetOperationTest.java
index 1e01020e78925f6b8b33606da8f43967a584ff12..536d140cf1b78e05b7a3e77d38e43a32d8a1f8f4 100644 (file)
@@ -8,47 +8,79 @@
 package org.opendaylight.controller.sal.restconf.impl.test;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
-import static org.opendaylight.controller.sal.restconf.impl.test.RestOperationUtils.createUri;
 
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
 import java.io.FileNotFoundException;
+import java.io.InputStream;
 import java.io.UnsupportedEncodingException;
 import java.net.URI;
 import java.net.URISyntaxException;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
 import java.util.ArrayList;
+import java.util.Date;
 import java.util.List;
+import java.util.Map;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
-
 import javax.ws.rs.core.Application;
 import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedHashMap;
+import javax.ws.rs.core.MultivaluedMap;
 import javax.ws.rs.core.Response;
-
+import javax.ws.rs.core.UriInfo;
 import org.glassfish.jersey.server.ResourceConfig;
 import org.glassfish.jersey.test.JerseyTest;
 import org.junit.BeforeClass;
 import org.junit.Test;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
 import org.opendaylight.controller.sal.core.api.mount.MountInstance;
 import org.opendaylight.controller.sal.core.api.mount.MountService;
 import org.opendaylight.controller.sal.rest.impl.JsonToCompositeNodeProvider;
+import org.opendaylight.controller.sal.rest.impl.RestconfDocumentedExceptionMapper;
 import org.opendaylight.controller.sal.rest.impl.StructuredDataToJsonProvider;
 import org.opendaylight.controller.sal.rest.impl.StructuredDataToXmlProvider;
 import org.opendaylight.controller.sal.rest.impl.XmlToCompositeNodeProvider;
 import org.opendaylight.controller.sal.restconf.impl.BrokerFacade;
 import org.opendaylight.controller.sal.restconf.impl.CompositeNodeWrapper;
 import org.opendaylight.controller.sal.restconf.impl.ControllerContext;
+import org.opendaylight.controller.sal.restconf.impl.RestconfDocumentedException;
 import org.opendaylight.controller.sal.restconf.impl.RestconfImpl;
 import org.opendaylight.controller.sal.restconf.impl.SimpleNodeWrapper;
+import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.data.api.CompositeNode;
 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument;
 import org.opendaylight.yangtools.yang.data.api.Node;
+import org.opendaylight.yangtools.yang.data.impl.ImmutableCompositeNode;
+import org.opendaylight.yangtools.yang.data.impl.util.CompositeNodeBuilder;
 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
 
 public class RestGetOperationTest extends JerseyTest {
 
+    static class NodeData {
+        Object key;
+        Object data; // List for a CompositeNode, value Object for a SimpleNode
+
+        NodeData(final Object key, final Object data) {
+            this.key = key;
+            this.data = data;
+        }
+    }
+
     private static BrokerFacade brokerFacade;
     private static RestconfImpl restconfImpl;
     private static SchemaContext schemaContextYangsIetf;
@@ -85,6 +117,7 @@ public class RestGetOperationTest extends JerseyTest {
         resourceConfig = resourceConfig.registerInstances(restconfImpl, StructuredDataToXmlProvider.INSTANCE,
                 StructuredDataToJsonProvider.INSTANCE, XmlToCompositeNodeProvider.INSTANCE,
                 JsonToCompositeNodeProvider.INSTANCE);
+        resourceConfig.registerClasses(RestconfDocumentedExceptionMapper.class);
         return resourceConfig;
     }
 
@@ -94,10 +127,10 @@ public class RestGetOperationTest extends JerseyTest {
     @Test
     public void getOperationalStatusCodes() throws UnsupportedEncodingException {
         mockReadOperationalDataMethod();
-        String uri = createUri("/operational/", "ietf-interfaces:interfaces/interface/eth0");
+        String uri = "/operational/ietf-interfaces:interfaces/interface/eth0";
         assertEquals(200, get(uri, MediaType.APPLICATION_XML));
 
-        uri = createUri("/operational/", "wrong-module:interfaces/interface/eth0");
+        uri = "/operational/wrong-module:interfaces/interface/eth0";
         assertEquals(400, get(uri, MediaType.APPLICATION_XML));
     }
 
@@ -107,10 +140,10 @@ public class RestGetOperationTest extends JerseyTest {
     @Test
     public void getConfigStatusCodes() throws UnsupportedEncodingException {
         mockReadConfigurationDataMethod();
-        String uri = createUri("/config/", "ietf-interfaces:interfaces/interface/eth0");
+        String uri = "/config/ietf-interfaces:interfaces/interface/eth0";
         assertEquals(200, get(uri, MediaType.APPLICATION_XML));
 
-        uri = createUri("/config/", "wrong-module:interfaces/interface/eth0");
+        uri = "/config/wrong-module:interfaces/interface/eth0";
         assertEquals(400, get(uri, MediaType.APPLICATION_XML));
     }
 
@@ -129,14 +162,58 @@ public class RestGetOperationTest extends JerseyTest {
 
         ControllerContext.getInstance().setMountService(mockMountService);
 
-        String uri = createUri("/config/",
-                "ietf-interfaces:interfaces/interface/0/yang-ext:mount/test-module:cont/cont1");
+        String uri = "/config/ietf-interfaces:interfaces/interface/0/yang-ext:mount/test-module:cont/cont1";
         assertEquals(200, get(uri, MediaType.APPLICATION_XML));
 
-        uri = createUri("/config/", "ietf-interfaces:interfaces/yang-ext:mount/test-module:cont/cont1");
+        uri = "/config/ietf-interfaces:interfaces/yang-ext:mount/test-module:cont/cont1";
         assertEquals(200, get(uri, MediaType.APPLICATION_XML));
     }
 
+    /**
+     * MountPoint test. URI represents mount point.
+     *
+     * Slashes in URI behind mount point. lst1 element with key GigabitEthernet0%2F0%2F0%2F0 (GigabitEthernet0/0/0/0) is
+     * requested via GET HTTP operation. It is tested whether %2F character is replaced with simple / in
+     * InstanceIdentifier parameter in method
+     * {@link BrokerFacade#readConfigurationDataBehindMountPoint(MountInstance, InstanceIdentifier)} which is called in
+     * method {@link RestconfImpl#readConfigurationData}
+     *
+     *
+     * @throws ParseException
+     */
+    @Test
+    public void getDataWithSlashesBehindMountPoint() throws UnsupportedEncodingException, URISyntaxException,
+            ParseException {
+        InstanceIdentifier awaitedInstanceIdentifier = prepareInstanceIdentifierForList();
+        when(
+                brokerFacade.readConfigurationDataBehindMountPoint(any(MountInstance.class),
+                        eq(awaitedInstanceIdentifier))).thenReturn(prepareCnDataForMountPointTest());
+        MountInstance mountInstance = mock(MountInstance.class);
+        when(mountInstance.getSchemaContext()).thenReturn(schemaContextTestModule);
+        MountService mockMountService = mock(MountService.class);
+        when(mockMountService.getMountPoint(any(InstanceIdentifier.class))).thenReturn(mountInstance);
+
+        ControllerContext.getInstance().setMountService(mockMountService);
+
+        String uri = "/config/ietf-interfaces:interfaces/interface/0/yang-ext:mount/test-module:cont/lst1/GigabitEthernet0%2F0%2F0%2F0";
+        assertEquals(200, get(uri, MediaType.APPLICATION_XML));
+    }
+
+    private InstanceIdentifier prepareInstanceIdentifierForList() throws URISyntaxException, ParseException {
+        List<PathArgument> parameters = new ArrayList<>();
+
+        Date revision = new SimpleDateFormat("yyyy-MM-dd").parse("2014-01-09");
+        URI uri = new URI("test:module");
+        QName qNameCont = QName.create(uri, revision, "cont");
+        QName qNameList = QName.create(uri, revision, "lst1");
+        QName qNameKeyList = QName.create(uri, revision, "lf11");
+
+        parameters.add(new InstanceIdentifier.NodeIdentifier(qNameCont));
+        parameters.add(new InstanceIdentifier.NodeIdentifierWithPredicates(qNameList, qNameKeyList,
+                "GigabitEthernet0/0/0/0"));
+        return InstanceIdentifier.create(parameters);
+    }
+
     @Test
     public void getDataMountPointIntoHighestElement() throws UnsupportedEncodingException, URISyntaxException {
         when(
@@ -149,7 +226,7 @@ public class RestGetOperationTest extends JerseyTest {
 
         ControllerContext.getInstance().setMountService(mockMountService);
 
-        String uri = createUri("/config/", "ietf-interfaces:interfaces/interface/0/yang-ext:mount/");
+        String uri = "/config/ietf-interfaces:interfaces/interface/0/yang-ext:mount/";
         assertEquals(200, get(uri, MediaType.APPLICATION_XML));
     }
 
@@ -158,7 +235,7 @@ public class RestGetOperationTest extends JerseyTest {
     public void getModulesTest() throws UnsupportedEncodingException, FileNotFoundException {
         ControllerContext.getInstance().setGlobalSchema(schemaContextModules);
 
-        String uri = createUri("/modules", "");
+        String uri = "/modules";
 
         Response response = target(uri).request("application/yang.api+json").get();
         validateModulesResponseJson(response);
@@ -167,12 +244,30 @@ public class RestGetOperationTest extends JerseyTest {
         validateModulesResponseXml(response);
     }
 
+    // /streams/
+    @Test
+    public void getStreamsTest() throws UnsupportedEncodingException, FileNotFoundException {
+        ControllerContext.getInstance().setGlobalSchema(schemaContextModules);
+
+        String uri = "/streams";
+
+        Response response = target(uri).request("application/yang.api+json").get();
+        String responseBody = response.readEntity(String.class);
+        assertNotNull(responseBody);
+        assertTrue(responseBody.contains("streams"));
+
+        response = target(uri).request("application/yang.api+xml").get();
+        responseBody = response.readEntity(String.class);
+        assertNotNull(responseBody);
+        assertTrue(responseBody.contains("<streams xmlns=\"urn:ietf:params:xml:ns:yang:ietf-restconf\""));
+    }
+
     // /modules/module
     @Test
     public void getModuleTest() throws FileNotFoundException, UnsupportedEncodingException {
         ControllerContext.getInstance().setGlobalSchema(schemaContextModules);
 
-        String uri = createUri("/modules/module/module2/2014-01-02", "");
+        String uri = "/modules/module/module2/2014-01-02";
 
         Response response = target(uri).request("application/yang.api+xml").get();
         assertEquals(200, response.getStatus());
@@ -180,7 +275,7 @@ public class RestGetOperationTest extends JerseyTest {
         assertTrue("Module2 in xml wasn't found", prepareXmlRegex("module2", "2014-01-02", "module:2", responseBody)
                 .find());
         String[] split = responseBody.split("<module");
-        assertEquals("<module element is returned more then once",2,split.length);
+        assertEquals("<module element is returned more then once", 2, split.length);
 
         response = target(uri).request("application/yang.api+json").get();
         assertEquals(200, response.getStatus());
@@ -188,7 +283,7 @@ public class RestGetOperationTest extends JerseyTest {
         assertTrue("Module2 in json wasn't found", prepareJsonRegex("module2", "2014-01-02", "module:2", responseBody)
                 .find());
         split = responseBody.split("\"module\"");
-        assertEquals("\"module\" element is returned more then once",2,split.length);
+        assertEquals("\"module\" element is returned more then once", 2, split.length);
 
     }
 
@@ -197,7 +292,7 @@ public class RestGetOperationTest extends JerseyTest {
     public void getOperationsTest() throws FileNotFoundException, UnsupportedEncodingException {
         ControllerContext.getInstance().setGlobalSchema(schemaContextModules);
 
-        String uri = createUri("/operations", "");
+        String uri = "/operations";
 
         Response response = target(uri).request("application/yang.api+xml").get();
         assertEquals(200, response.getStatus());
@@ -238,7 +333,7 @@ public class RestGetOperationTest extends JerseyTest {
 
         controllerContext.setMountService(mockMountService);
 
-        String uri = createUri("/operations/", "ietf-interfaces:interfaces/interface/0/yang-ext:mount/");
+        String uri = "/operations/ietf-interfaces:interfaces/interface/0/yang-ext:mount/";
 
         Response response = target(uri).request("application/yang.api+xml").get();
         assertEquals(200, response.getStatus());
@@ -258,7 +353,7 @@ public class RestGetOperationTest extends JerseyTest {
 
     }
 
-    private Matcher validateOperationsResponseJson(String searchIn, String rpcName, String moduleName) {
+    private Matcher validateOperationsResponseJson(final String searchIn, final String rpcName, final String moduleName) {
         StringBuilder regex = new StringBuilder();
         regex.append("^");
 
@@ -292,7 +387,7 @@ public class RestGetOperationTest extends JerseyTest {
 
     }
 
-    private Matcher validateOperationsResponseXml(String searchIn, String rpcName, String namespace) {
+    private Matcher validateOperationsResponseXml(final String searchIn, final String rpcName, final String namespace) {
         StringBuilder regex = new StringBuilder();
 
         regex.append("^");
@@ -329,7 +424,7 @@ public class RestGetOperationTest extends JerseyTest {
 
         controllerContext.setMountService(mockMountService);
 
-        String uri = createUri("/modules/", "ietf-interfaces:interfaces/interface/0/yang-ext:mount/");
+        String uri = "/modules/ietf-interfaces:interfaces/interface/0/yang-ext:mount/";
 
         Response response = target(uri).request("application/yang.api+json").get();
         assertEquals(200, response.getStatus());
@@ -371,8 +466,7 @@ public class RestGetOperationTest extends JerseyTest {
 
         controllerContext.setMountService(mockMountService);
 
-        String uri = createUri("/modules/module/",
-                "ietf-interfaces:interfaces/interface/0/yang-ext:mount/module1-behind-mount-point/2014-02-03");
+        String uri = "/modules/module/ietf-interfaces:interfaces/interface/0/yang-ext:mount/module1-behind-mount-point/2014-02-03";
 
         Response response = target(uri).request("application/yang.api+json").get();
         assertEquals(200, response.getStatus());
@@ -383,8 +477,7 @@ public class RestGetOperationTest extends JerseyTest {
                 prepareJsonRegex("module1-behind-mount-point", "2014-02-03", "module:1:behind:mount:point",
                         responseBody).find());
         String[] split = responseBody.split("\"module\"");
-        assertEquals("\"module\" element is returned more then once",2,split.length);
-
+        assertEquals("\"module\" element is returned more then once", 2, split.length);
 
         response = target(uri).request("application/yang.api+xml").get();
         assertEquals(200, response.getStatus());
@@ -394,14 +487,11 @@ public class RestGetOperationTest extends JerseyTest {
                 prepareXmlRegex("module1-behind-mount-point", "2014-02-03", "module:1:behind:mount:point", responseBody)
                         .find());
         split = responseBody.split("<module");
-        assertEquals("<module element is returned more then once",2,split.length);
-
-
-
+        assertEquals("<module element is returned more then once", 2, split.length);
 
     }
 
-    private void validateModulesResponseXml(Response response) {
+    private void validateModulesResponseXml(final Response response) {
         assertEquals(200, response.getStatus());
         String responseBody = response.readEntity(String.class);
 
@@ -413,7 +503,7 @@ public class RestGetOperationTest extends JerseyTest {
                 .find());
     }
 
-    private void validateModulesResponseJson(Response response) {
+    private void validateModulesResponseJson(final Response response) {
         assertEquals(200, response.getStatus());
         String responseBody = response.readEntity(String.class);
 
@@ -425,7 +515,8 @@ public class RestGetOperationTest extends JerseyTest {
                 .find());
     }
 
-    private Matcher prepareJsonRegex(String module, String revision, String namespace, String searchIn) {
+    private Matcher prepareJsonRegex(final String module, final String revision, final String namespace,
+            final String searchIn) {
         StringBuilder regex = new StringBuilder();
         regex.append("^");
 
@@ -451,7 +542,8 @@ public class RestGetOperationTest extends JerseyTest {
 
     }
 
-    private Matcher prepareXmlRegex(String module, String revision, String namespace, String searchIn) {
+    private Matcher prepareXmlRegex(final String module, final String revision, final String namespace,
+            final String searchIn) {
         StringBuilder regex = new StringBuilder();
         regex.append("^");
 
@@ -479,13 +571,14 @@ public class RestGetOperationTest extends JerseyTest {
         return ptrn.matcher(searchIn);
     }
 
-    private void prepareMockForModulesTest(ControllerContext mockedControllerContext) throws FileNotFoundException {
+    private void prepareMockForModulesTest(final ControllerContext mockedControllerContext)
+            throws FileNotFoundException {
         SchemaContext schemaContext = TestUtils.loadSchemaContext("/modules");
         mockedControllerContext.setGlobalSchema(schemaContext);
         // when(mockedControllerContext.getGlobalSchema()).thenReturn(schemaContext);
     }
 
-    private int get(String uri, String mediaType) {
+    private int get(final String uri, final String mediaType) {
         return target(uri).request(mediaType).get().getStatus();
     }
 
@@ -522,4 +615,325 @@ public class RestGetOperationTest extends JerseyTest {
         return null;
     }
 
+    /**
+     * If includeWhiteChars URI parameter is set to false then no white characters can be included in returned output
+     *
+     * @throws UnsupportedEncodingException
+     */
+    @Test
+    public void getDataWithUriIncludeWhiteCharsParameterTest() throws UnsupportedEncodingException {
+        getDataWithUriIncludeWhiteCharsParameter("config");
+        getDataWithUriIncludeWhiteCharsParameter("operational");
+    }
+
+    private void getDataWithUriIncludeWhiteCharsParameter(String target) throws UnsupportedEncodingException {
+        mockReadConfigurationDataMethod();
+        String uri = "/" + target + "/ietf-interfaces:interfaces/interface/eth0";
+        Response response = target(uri).queryParam("prettyPrint", "false").request("application/xml").get();
+        String xmlData = response.readEntity(String.class);
+
+        Pattern pattern = Pattern.compile(".*(>\\s+|\\s+<).*", Pattern.DOTALL);
+        Matcher matcher = pattern.matcher(xmlData);
+        // XML element can't surrounded with white character (e.g ">    " or
+        // "    <")
+        assertFalse(matcher.matches());
+
+        response = target(uri).queryParam("prettyPrint", "false").request("application/json").get();
+        String jsonData = response.readEntity(String.class);
+        pattern = Pattern.compile(".*(\\}\\s+|\\s+\\{|\\]\\s+|\\s+\\[|\\s+:|:\\s+).*", Pattern.DOTALL);
+        matcher = pattern.matcher(jsonData);
+        // JSON element can't surrounded with white character (e.g "} ", " {",
+        // "] ", " [", " :" or ": ")
+        assertFalse(matcher.matches());
+    }
+
+    @Test
+    public void getDataWithUriDepthParameterTest() throws UnsupportedEncodingException {
+
+        ControllerContext.getInstance().setGlobalSchema(schemaContextModules);
+
+        CompositeNode depth1Cont = toCompositeNode(toCompositeNodeData(
+                toNestedQName("depth1-cont"),
+                toCompositeNodeData(
+                        toNestedQName("depth2-cont1"),
+                        toCompositeNodeData(
+                                toNestedQName("depth3-cont1"),
+                                toCompositeNodeData(toNestedQName("depth4-cont1"),
+                                        toSimpleNodeData(toNestedQName("depth5-leaf1"), "depth5-leaf1-value")),
+                                toSimpleNodeData(toNestedQName("depth4-leaf1"), "depth4-leaf1-value")),
+                        toSimpleNodeData(toNestedQName("depth3-leaf1"), "depth3-leaf1-value")),
+                toCompositeNodeData(
+                        toNestedQName("depth2-cont2"),
+                        toCompositeNodeData(
+                                toNestedQName("depth3-cont2"),
+                                toCompositeNodeData(toNestedQName("depth4-cont2"),
+                                        toSimpleNodeData(toNestedQName("depth5-leaf2"), "depth5-leaf2-value")),
+                                toSimpleNodeData(toNestedQName("depth4-leaf2"), "depth4-leaf2-value")),
+                        toSimpleNodeData(toNestedQName("depth3-leaf2"), "depth3-leaf2-value")),
+                toSimpleNodeData(toNestedQName("depth2-leaf1"), "depth2-leaf1-value")));
+
+        when(brokerFacade.readConfigurationData(any(InstanceIdentifier.class))).thenReturn(depth1Cont);
+
+        // Test config with depth 1
+
+        Response response = target("/config/nested-module:depth1-cont").queryParam("depth", "1")
+                .request("application/xml").get();
+
+        verifyXMLResponse(response, expectEmptyContainer("depth1-cont"));
+
+        // Test config with depth 2
+
+        response = target("/config/nested-module:depth1-cont").queryParam("depth", "2").request("application/xml")
+                .get();
+
+        // String
+        // xml="<depth1-cont><depth2-cont1/><depth2-cont2/><depth2-leaf1>depth2-leaf1-value</depth2-leaf1></depth1-cont>";
+        // Response mr=mock(Response.class);
+        // when(mr.getEntity()).thenReturn( new
+        // java.io.StringBufferInputStream(xml) );
+
+        verifyXMLResponse(
+                response,
+                expectContainer("depth1-cont", expectEmptyContainer("depth2-cont1"),
+                        expectEmptyContainer("depth2-cont2"), expectLeaf("depth2-leaf1", "depth2-leaf1-value")));
+
+        // Test config with depth 3
+
+        response = target("/config/nested-module:depth1-cont").queryParam("depth", "3").request("application/xml")
+                .get();
+
+        verifyXMLResponse(
+                response,
+                expectContainer(
+                        "depth1-cont",
+                        expectContainer("depth2-cont1", expectEmptyContainer("depth3-cont1"),
+                                expectLeaf("depth3-leaf1", "depth3-leaf1-value")),
+                        expectContainer("depth2-cont2", expectEmptyContainer("depth3-cont2"),
+                                expectLeaf("depth3-leaf2", "depth3-leaf2-value")),
+                        expectLeaf("depth2-leaf1", "depth2-leaf1-value")));
+
+        // Test config with depth 4
+
+        response = target("/config/nested-module:depth1-cont").queryParam("depth", "4").request("application/xml")
+                .get();
+
+        verifyXMLResponse(
+                response,
+                expectContainer(
+                        "depth1-cont",
+                        expectContainer(
+                                "depth2-cont1",
+                                expectContainer("depth3-cont1", expectEmptyContainer("depth4-cont1"),
+                                        expectLeaf("depth4-leaf1", "depth4-leaf1-value")),
+                                expectLeaf("depth3-leaf1", "depth3-leaf1-value")),
+                        expectContainer(
+                                "depth2-cont2",
+                                expectContainer("depth3-cont2", expectEmptyContainer("depth4-cont2"),
+                                        expectLeaf("depth4-leaf2", "depth4-leaf2-value")),
+                                expectLeaf("depth3-leaf2", "depth3-leaf2-value")),
+                        expectLeaf("depth2-leaf1", "depth2-leaf1-value")));
+
+        // Test config with depth 5
+
+        response = target("/config/nested-module:depth1-cont").queryParam("depth", "5").request("application/xml")
+                .get();
+
+        verifyXMLResponse(
+                response,
+                expectContainer(
+                        "depth1-cont",
+                        expectContainer(
+                                "depth2-cont1",
+                                expectContainer(
+                                        "depth3-cont1",
+                                        expectContainer("depth4-cont1",
+                                                expectLeaf("depth5-leaf1", "depth5-leaf1-value")),
+                                        expectLeaf("depth4-leaf1", "depth4-leaf1-value")),
+                                expectLeaf("depth3-leaf1", "depth3-leaf1-value")),
+                        expectContainer(
+                                "depth2-cont2",
+                                expectContainer(
+                                        "depth3-cont2",
+                                        expectContainer("depth4-cont2",
+                                                expectLeaf("depth5-leaf2", "depth5-leaf2-value")),
+                                        expectLeaf("depth4-leaf2", "depth4-leaf2-value")),
+                                expectLeaf("depth3-leaf2", "depth3-leaf2-value")),
+                        expectLeaf("depth2-leaf1", "depth2-leaf1-value")));
+
+        // Test config with depth unbounded
+
+        response = target("/config/nested-module:depth1-cont").queryParam("depth", "unbounded")
+                .request("application/xml").get();
+
+        verifyXMLResponse(
+                response,
+                expectContainer(
+                        "depth1-cont",
+                        expectContainer(
+                                "depth2-cont1",
+                                expectContainer(
+                                        "depth3-cont1",
+                                        expectContainer("depth4-cont1",
+                                                expectLeaf("depth5-leaf1", "depth5-leaf1-value")),
+                                        expectLeaf("depth4-leaf1", "depth4-leaf1-value")),
+                                expectLeaf("depth3-leaf1", "depth3-leaf1-value")),
+                        expectContainer(
+                                "depth2-cont2",
+                                expectContainer(
+                                        "depth3-cont2",
+                                        expectContainer("depth4-cont2",
+                                                expectLeaf("depth5-leaf2", "depth5-leaf2-value")),
+                                        expectLeaf("depth4-leaf2", "depth4-leaf2-value")),
+                                expectLeaf("depth3-leaf2", "depth3-leaf2-value")),
+                        expectLeaf("depth2-leaf1", "depth2-leaf1-value")));
+
+        // Test operational
+
+        CompositeNode depth2Cont1 = toCompositeNode(toCompositeNodeData(
+                toNestedQName("depth2-cont1"),
+                toCompositeNodeData(
+                        toNestedQName("depth3-cont1"),
+                        toCompositeNodeData(toNestedQName("depth4-cont1"),
+                                toSimpleNodeData(toNestedQName("depth5-leaf1"), "depth5-leaf1-value")),
+                        toSimpleNodeData(toNestedQName("depth4-leaf1"), "depth4-leaf1-value")),
+                toSimpleNodeData(toNestedQName("depth3-leaf1"), "depth3-leaf1-value")));
+
+        when(brokerFacade.readOperationalData(any(InstanceIdentifier.class))).thenReturn(depth2Cont1);
+
+        response = target("/operational/nested-module:depth1-cont/depth2-cont1").queryParam("depth", "3")
+                .request("application/xml").get();
+
+        verifyXMLResponse(
+                response,
+                expectContainer(
+                        "depth2-cont1",
+                        expectContainer("depth3-cont1", expectEmptyContainer("depth4-cont1"),
+                                expectLeaf("depth4-leaf1", "depth4-leaf1-value")),
+                        expectLeaf("depth3-leaf1", "depth3-leaf1-value")));
+    }
+
+    @Test
+    public void getDataWithInvalidDepthParameterTest() {
+
+        ControllerContext.getInstance().setGlobalSchema(schemaContextModules);
+
+        final MultivaluedMap<String, String> paramMap = new MultivaluedHashMap<>();
+        paramMap.putSingle("depth", "1o");
+        UriInfo mockInfo = mock(UriInfo.class);
+        when(mockInfo.getQueryParameters(false)).thenAnswer(new Answer<MultivaluedMap<String, String>>() {
+            @Override
+            public MultivaluedMap<String, String> answer(final InvocationOnMock invocation) {
+                return paramMap;
+            }
+        });
+
+        getDataWithInvalidDepthParameterTest(mockInfo);
+
+        paramMap.putSingle("depth", "0");
+        getDataWithInvalidDepthParameterTest(mockInfo);
+
+        paramMap.putSingle("depth", "-1");
+        getDataWithInvalidDepthParameterTest(mockInfo);
+    }
+
+    private void getDataWithInvalidDepthParameterTest(final UriInfo uriInfo) {
+        try {
+            restconfImpl.readConfigurationData("nested-module:depth1-cont", uriInfo);
+            fail("Expected RestconfDocumentedException");
+        } catch (RestconfDocumentedException e) {
+            assertTrue("Unexpected error message: " + e.getErrors().get(0).getErrorMessage(), e.getErrors().get(0)
+                    .getErrorMessage().contains("depth"));
+        }
+    }
+
+    private void verifyXMLResponse(final Response response, final NodeData nodeData) {
+
+        Document doc = TestUtils.loadDocumentFrom((InputStream) response.getEntity());
+        assertNotNull("Could not parse XML document", doc);
+
+        // System.out.println(TestUtils.getDocumentInPrintableForm( doc ));
+
+        verifyContainerElement(doc.getDocumentElement(), nodeData);
+    }
+
+    @SuppressWarnings("unchecked")
+    private void verifyContainerElement(final Element element, final NodeData nodeData) {
+
+        assertEquals("Element local name", nodeData.key, element.getNodeName());
+
+        NodeList childNodes = element.getChildNodes();
+        if (nodeData.data == null) { // empty container
+            assertTrue("Expected no child elements for \"" + element.getNodeName() + "\"", childNodes.getLength() == 0);
+            return;
+        }
+
+        Map<String, NodeData> expChildMap = Maps.newHashMap();
+        for (NodeData expChild : (List<NodeData>) nodeData.data) {
+            expChildMap.put(expChild.key.toString(), expChild);
+        }
+
+        for (int i = 0; i < childNodes.getLength(); i++) {
+            org.w3c.dom.Node actualChild = childNodes.item(i);
+            if (!(actualChild instanceof Element)) {
+                continue;
+            }
+
+            Element actualElement = (Element) actualChild;
+            NodeData expChild = expChildMap.remove(actualElement.getNodeName());
+            assertNotNull(
+                    "Unexpected child element for parent \"" + element.getNodeName() + "\": "
+                            + actualElement.getNodeName(), expChild);
+
+            if (expChild.data == null || expChild.data instanceof List) {
+                verifyContainerElement(actualElement, expChild);
+            } else {
+                assertEquals("Text content for element: " + actualElement.getNodeName(), expChild.data,
+                        actualElement.getTextContent());
+            }
+        }
+
+        if (!expChildMap.isEmpty()) {
+            fail("Missing elements for parent \"" + element.getNodeName() + "\": " + expChildMap.keySet());
+        }
+    }
+
+    private NodeData expectContainer(final String name, final NodeData... childData) {
+        return new NodeData(name, Lists.newArrayList(childData));
+    }
+
+    private NodeData expectEmptyContainer(final String name) {
+        return new NodeData(name, null);
+    }
+
+    private NodeData expectLeaf(final String name, final Object value) {
+        return new NodeData(name, value);
+    }
+
+    private QName toNestedQName(final String localName) {
+        return QName.create("urn:nested:module", "2014-06-3", localName);
+    }
+
+    @SuppressWarnings("unchecked")
+    private CompositeNode toCompositeNode(final NodeData nodeData) {
+        CompositeNodeBuilder<ImmutableCompositeNode> builder = ImmutableCompositeNode.builder();
+        builder.setQName((QName) nodeData.key);
+
+        for (NodeData child : (List<NodeData>) nodeData.data) {
+            if (child.data instanceof List) {
+                builder.add(toCompositeNode(child));
+            } else {
+                builder.addLeaf((QName) child.key, child.data);
+            }
+        }
+
+        return builder.toInstance();
+    }
+
+    private NodeData toCompositeNodeData(final QName key, final NodeData... childData) {
+        return new NodeData(key, Lists.newArrayList(childData));
+    }
+
+    private NodeData toSimpleNodeData(final QName key, final Object value) {
+        return new NodeData(key, value);
+    }
 }