Bug 6895 - Implement Query parameters - depth 01/46601/26
authorIvan Hrasko <ivan.hrasko@pantheon.tech>
Thu, 6 Oct 2016 09:19:59 +0000 (11:19 +0200)
committerIvan Hrasko <ivan.hrasko@pantheon.tech>
Fri, 4 Nov 2016 11:18:47 +0000 (11:18 +0000)
- depth URI query parameter for new Restconf
- parse and verify all GET parameters in one method
- added tests for depth parameter writer
- added unit tests for content parameter

Change-Id: If9fece9c995dd52bb8ad00d29b6addb66fa4e32b
Signed-off-by: Ivan Hrasko <ivan.hrasko@pantheon.tech>
restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/restconf/impl/WriterParameters.java
restconf/sal-rest-connector/src/main/java/org/opendaylight/restconf/restful/services/impl/RestconfDataServiceImpl.java
restconf/sal-rest-connector/src/main/java/org/opendaylight/restconf/restful/utils/ParametersUtil.java [new file with mode: 0644]
restconf/sal-rest-connector/src/main/java/org/opendaylight/restconf/restful/utils/ReadDataTransactionUtil.java
restconf/sal-rest-connector/src/main/java/org/opendaylight/restconf/restful/utils/RestconfDataServiceConstant.java
restconf/sal-rest-connector/src/test/java/org/opendaylight/netconf/sal/rest/impl/DepthAwareNormalizedNodeWriterTest.java [new file with mode: 0644]
restconf/sal-rest-connector/src/test/java/org/opendaylight/restconf/restful/services/impl/RestconfDataServiceImplTest.java
restconf/sal-rest-connector/src/test/java/org/opendaylight/restconf/restful/utils/ParametersUtilTest.java [new file with mode: 0644]
restconf/sal-rest-connector/src/test/java/org/opendaylight/restconf/restful/utils/ReadDataTransactionUtilTest.java

index 11227d0a042e7acbd66332185a83e8598df466fb..07978083dd5e1ed001e15f7ba1143d67244df3c0 100644 (file)
@@ -11,12 +11,18 @@ package org.opendaylight.netconf.sal.restconf.impl;
 import com.google.common.base.Optional;
 
 public class WriterParameters {
+    private final String content;
     private final Optional<Integer> depth;
     private final boolean prettyPrint;
 
     private WriterParameters(final WriterParametersBuilder builder) {
-        this.prettyPrint = builder.prettyPrint;
+        this.content = builder.content;
         this.depth = builder.depth;
+        this.prettyPrint = builder.prettyPrint;
+    }
+
+    public String getContent() {
+        return content;
     }
 
     public Optional<Integer> getDepth() {
@@ -28,14 +34,15 @@ public class WriterParameters {
     }
 
     public static class WriterParametersBuilder {
+        private String content;
         private Optional<Integer> depth = Optional.absent();
         private boolean prettyPrint;
 
-        public WriterParametersBuilder() {
-        }
+        public WriterParametersBuilder() {}
 
-        public Optional<Integer> getDepth() {
-            return depth;
+        public WriterParametersBuilder setContent(final String content) {
+            this.content = content;
+            return this;
         }
 
         public WriterParametersBuilder setDepth(final int depth) {
@@ -43,10 +50,6 @@ public class WriterParameters {
             return this;
         }
 
-        public boolean isPrettyPrint() {
-            return prettyPrint;
-        }
-
         public WriterParametersBuilder setPrettyPrint(final boolean prettyPrint) {
             this.prettyPrint = prettyPrint;
             return this;
@@ -57,4 +60,3 @@ public class WriterParameters {
         }
     }
 }
-
index cb17434392c1870ce602082d4719e8366a671abf..e75e78371b8db5c5a2dd200fb73d72769ad7bee8 100644 (file)
@@ -24,6 +24,7 @@ import org.opendaylight.netconf.sal.restconf.impl.PATCHContext;
 import org.opendaylight.netconf.sal.restconf.impl.PATCHStatusContext;
 import org.opendaylight.netconf.sal.restconf.impl.RestconfDocumentedException;
 import org.opendaylight.netconf.sal.restconf.impl.RestconfError;
+import org.opendaylight.netconf.sal.restconf.impl.WriterParameters;
 import org.opendaylight.restconf.RestConnectorProvider;
 import org.opendaylight.restconf.common.references.SchemaContextRef;
 import org.opendaylight.restconf.handlers.DOMMountPointServiceHandler;
@@ -65,12 +66,13 @@ public class RestconfDataServiceImpl implements RestconfDataService {
     @Override
     public Response readData(final String identifier, final UriInfo uriInfo) {
         Preconditions.checkNotNull(identifier);
+
+        final WriterParameters parameters = ReadDataTransactionUtil.parseUriParameters(uriInfo);
         final SchemaContextRef schemaContextRef = new SchemaContextRef(this.schemaContextHandler.get());
 
         final InstanceIdentifierContext<?> instanceIdentifier = ParserIdentifier.toInstanceIdentifier(
                 identifier, schemaContextRef.get(), Optional.of(this.mountPointServiceHandler.get()));
         final DOMMountPoint mountPoint = instanceIdentifier.getMountPoint();
-        final String value = uriInfo.getQueryParameters().getFirst(RestconfDataServiceConstant.CONTENT);
 
         final DOMTransactionChain transactionChain;
         if (mountPoint == null) {
@@ -79,9 +81,9 @@ public class RestconfDataServiceImpl implements RestconfDataService {
             transactionChain = transactionChainOfMountPoint(mountPoint);
         }
 
-        final TransactionVarsWrapper transactionNode = new TransactionVarsWrapper(instanceIdentifier, mountPoint,
-                transactionChain);
-        final NormalizedNode<?, ?> node = ReadDataTransactionUtil.readData(value, transactionNode);
+        final TransactionVarsWrapper transactionNode = new TransactionVarsWrapper(
+                instanceIdentifier, mountPoint, transactionChain);
+        final NormalizedNode<?, ?> node = ReadDataTransactionUtil.readData(parameters.getContent(), transactionNode);
         if (node == null) {
             throw new RestconfDocumentedException(
                     "Request could not be completed because the relevant data model content does not exist",
@@ -94,12 +96,19 @@ public class RestconfDataServiceImpl implements RestconfDataService {
                 + node.getNodeType().getLocalName() + '"';
         final Response resp;
 
-        if ((value == null) || value.contains(RestconfDataServiceConstant.ReadData.CONFIG)) {
-            resp = Response.status(200).entity(new NormalizedNodeContext(instanceIdentifier, node)).header("ETag", etag)
-                    .header("Last-Modified", dateFormatGmt.format(new Date())).build();
+        if ((parameters.getContent().equals(RestconfDataServiceConstant.ReadData.ALL))
+                    || parameters.getContent().equals(RestconfDataServiceConstant.ReadData.CONFIG)) {
+            resp = Response.status(200)
+                    .entity(new NormalizedNodeContext(instanceIdentifier, node, parameters))
+                    .header("ETag", etag)
+                    .header("Last-Modified", dateFormatGmt.format(new Date()))
+                    .build();
         } else {
-            resp = Response.status(200).entity(new NormalizedNodeContext(instanceIdentifier, node)).build();
+            resp = Response.status(200)
+                    .entity(new NormalizedNodeContext(instanceIdentifier, node, parameters))
+                    .build();
         }
+
         return resp;
     }
 
diff --git a/restconf/sal-rest-connector/src/main/java/org/opendaylight/restconf/restful/utils/ParametersUtil.java b/restconf/sal-rest-connector/src/main/java/org/opendaylight/restconf/restful/utils/ParametersUtil.java
new file mode 100644 (file)
index 0000000..bd85e54
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2016 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.restconf.restful.utils;
+
+import com.google.common.collect.Sets;
+import java.util.List;
+import java.util.Set;
+import javax.annotation.Nonnull;
+import org.opendaylight.netconf.sal.restconf.impl.RestconfDocumentedException;
+import org.opendaylight.netconf.sal.restconf.impl.RestconfError;
+import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorTag;
+import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorType;
+
+class ParametersUtil {
+
+    private ParametersUtil() {
+        throw new UnsupportedOperationException("Util class.");
+    }
+
+    /**
+     * Check if URI does not contain not allowed parameters for specified operation
+     *
+     * @param operationType
+     *            - type of operation (READ, POST, PUT, DELETE...)
+     * @param usedParameters
+     *            - parameters used in URI request
+     * @param allowedParameters
+     *            - allowed parameters for operation
+     */
+    static void checkParametersTypes(@Nonnull final String operationType,
+                                     @Nonnull final Set<String> usedParameters,
+                                     @Nonnull final String... allowedParameters) {
+        final Set<String> notAllowedParameters = Sets.newHashSet(usedParameters);
+        notAllowedParameters.removeAll(Sets.newHashSet(allowedParameters));
+
+        if (!notAllowedParameters.isEmpty()) {
+            throw new RestconfDocumentedException(
+                    "Not allowed parameters for " + operationType + " operation: " + notAllowedParameters,
+                    RestconfError.ErrorType.PROTOCOL,
+                    RestconfError.ErrorTag.INVALID_VALUE);
+        }
+    }
+
+    /**
+     * Check if URI does not contain value for the same parameter more than once
+     *
+     * @param parameterValues
+     *            - URI parameter values
+     * @param parameterName
+     *            - URI parameter name
+     */
+    static void checkParameterCount(@Nonnull final List<String> parameterValues, @Nonnull final String parameterName) {
+        if (parameterValues.size() > 1) {
+            throw new RestconfDocumentedException(
+                    "Parameter " + parameterName + " can appear at most once in request URI",
+                    ErrorType.PROTOCOL,
+                    ErrorTag.INVALID_VALUE);
+        }
+    }
+}
index fe840990ed1f4db44d5eb076c9e3dc9aea81170b..cf4ced08b15437d8269f908555438453a8622cec 100644 (file)
@@ -8,18 +8,23 @@
 package org.opendaylight.restconf.restful.utils;
 
 import com.google.common.base.Optional;
+import com.google.common.primitives.Ints;
 import com.google.common.util.concurrent.CheckedFuture;
 import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
 import java.util.Map;
 import java.util.function.Function;
 import java.util.stream.Collectors;
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
+import javax.ws.rs.core.UriInfo;
 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
 import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException;
 import org.opendaylight.netconf.sal.restconf.impl.RestconfDocumentedException;
-import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorTag;
-import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorType;
+import org.opendaylight.netconf.sal.restconf.impl.RestconfError;
+import org.opendaylight.netconf.sal.restconf.impl.WriterParameters;
+import org.opendaylight.netconf.sal.restconf.impl.WriterParameters.WriterParametersBuilder;
 import org.opendaylight.restconf.restful.transaction.TransactionVarsWrapper;
 import org.opendaylight.yangtools.yang.common.QNameModule;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
@@ -56,6 +61,72 @@ public final class ReadDataTransactionUtil {
         throw new UnsupportedOperationException("Util class.");
     }
 
+    /**
+     * Parse parameters from URI request and check their types and values.
+     *
+     * @param uriInfo
+     *            - URI info
+     * @return {@link WriterParameters}
+     */
+    @Nonnull public static WriterParameters parseUriParameters(@Nullable final UriInfo uriInfo) {
+        final WriterParametersBuilder builder = new WriterParametersBuilder();
+
+        if (uriInfo == null) {
+            return builder.build();
+        }
+
+        // check only allowed parameters
+        ParametersUtil.checkParametersTypes(
+                RestconfDataServiceConstant.ReadData.READ_TYPE_TX,
+                uriInfo.getQueryParameters().keySet(),
+                RestconfDataServiceConstant.ReadData.CONTENT,
+                RestconfDataServiceConstant.ReadData.DEPTH);
+
+        // read parameters from URI or set default values
+        final List<String> content = uriInfo.getQueryParameters().getOrDefault(
+                RestconfDataServiceConstant.ReadData.CONTENT,
+                Collections.singletonList(RestconfDataServiceConstant.ReadData.ALL));
+        final List<String> depth = uriInfo.getQueryParameters().getOrDefault(
+                RestconfDataServiceConstant.ReadData.DEPTH,
+                Collections.singletonList(RestconfDataServiceConstant.ReadData.UNBOUNDED));
+
+        // parameter can be in URI at most once
+        ParametersUtil.checkParameterCount(content, RestconfDataServiceConstant.ReadData.CONTENT);
+        ParametersUtil.checkParameterCount(depth, RestconfDataServiceConstant.ReadData.DEPTH);
+
+        // check and set content
+        final String contentValue = content.get(0);
+        if (!contentValue.equals(RestconfDataServiceConstant.ReadData.ALL)) {
+            if (!contentValue.equals(RestconfDataServiceConstant.ReadData.CONFIG)
+                    && !contentValue.equals(RestconfDataServiceConstant.ReadData.NONCONFIG)) {
+                throw new RestconfDocumentedException(
+                        new RestconfError(RestconfError.ErrorType.PROTOCOL, RestconfError.ErrorTag.INVALID_VALUE,
+                                "Invalid content parameter: " + contentValue, null,
+                                "The content parameter value must be either config, nonconfig or all (default)"));
+            }
+        }
+
+        builder.setContent(content.get(0));
+
+        // check and set depth
+        if (!depth.get(0).equals(RestconfDataServiceConstant.ReadData.UNBOUNDED)) {
+            final Integer value = Ints.tryParse(depth.get(0));
+
+            if (value == null
+                    || (!(value >= RestconfDataServiceConstant.ReadData.MIN_DEPTH
+                        && value <= RestconfDataServiceConstant.ReadData.MAX_DEPTH))) {
+                throw new RestconfDocumentedException(
+                        new RestconfError(RestconfError.ErrorType.PROTOCOL, RestconfError.ErrorTag.INVALID_VALUE,
+                                "Invalid depth parameter: " + depth, null,
+                                "The depth parameter must be an integer between 1 and 65535 or \"unbounded\""));
+            } else {
+                builder.setDepth(value);
+            }
+        }
+
+        return builder.build();
+    }
+
     /**
      * Read specific type of data from data store via transaction.
      *
@@ -65,31 +136,26 @@ public final class ReadDataTransactionUtil {
      *            - {@link TransactionVarsWrapper} - wrapper for variables
      * @return {@link NormalizedNode}
      */
-    public static @Nullable NormalizedNode<?, ?> readData(@Nullable final String valueOfContent,
+    public static @Nullable NormalizedNode<?, ?> readData(@Nonnull final String valueOfContent,
                                                           @Nonnull final TransactionVarsWrapper transactionNode) {
-        final NormalizedNode<?, ?> data;
-        if (valueOfContent != null) {
-            switch (valueOfContent) {
-                case RestconfDataServiceConstant.ReadData.CONFIG:
-                    transactionNode.setLogicalDatastoreType(LogicalDatastoreType.CONFIGURATION);
-                    data = readDataViaTransaction(transactionNode);
-                    break;
-                case RestconfDataServiceConstant.ReadData.NONCONFIG:
-                    transactionNode.setLogicalDatastoreType(LogicalDatastoreType.OPERATIONAL);
-                    data = readDataViaTransaction(transactionNode);
-                    break;
-                case RestconfDataServiceConstant.ReadData.ALL:
-                    data = readAllData(transactionNode);
-                    break;
-                default:
-                    throw new RestconfDocumentedException("Bad query parameter for content.", ErrorType.APPLICATION,
-                            ErrorTag.INVALID_VALUE);
-            }
-        } else {
-            data = readAllData(transactionNode);
+        switch (valueOfContent) {
+            case RestconfDataServiceConstant.ReadData.CONFIG:
+                transactionNode.setLogicalDatastoreType(LogicalDatastoreType.CONFIGURATION);
+                return readDataViaTransaction(transactionNode);
+
+            case RestconfDataServiceConstant.ReadData.NONCONFIG:
+                transactionNode.setLogicalDatastoreType(LogicalDatastoreType.OPERATIONAL);
+                return readDataViaTransaction(transactionNode);
+
+            case RestconfDataServiceConstant.ReadData.ALL:
+                return readAllData(transactionNode);
+
+            default:
+                throw new RestconfDocumentedException(
+                        new RestconfError(RestconfError.ErrorType.PROTOCOL, RestconfError.ErrorTag.INVALID_VALUE,
+                                "Invalid content parameter: " + valueOfContent, null,
+                                "The content parameter value must be either config, nonconfig or all (default)"));
         }
-
-        return data;
     }
 
     /**
index bc9f4d64633708db728cd9edc5844df8fbd1ad78..fd43a4a83473b09df6dd9e16d61622c55e693622 100644 (file)
@@ -21,7 +21,6 @@ import org.opendaylight.yangtools.yang.common.QNameModule;
  */
 public final class RestconfDataServiceConstant {
 
-    public static final String CONTENT = "content";
     public static final QName NETCONF_BASE_QNAME;
     static {
         try {
@@ -43,10 +42,20 @@ public final class RestconfDataServiceConstant {
      *
      */
     public final class ReadData {
+        // URI parameters
+        public static final String CONTENT = "content";
+        public static final String DEPTH = "depth";
 
+        // content values
         public static final String CONFIG = "config";
-        public static final String NONCONFIG = "nonconfig";
         public static final String ALL = "all";
+        public static final String NONCONFIG = "nonconfig";
+
+        // depth values
+        public static final String UNBOUNDED = "unbounded";
+        public static final int MIN_DEPTH = 1;
+        public static final int MAX_DEPTH = 65535;
+
         public static final String READ_TYPE_TX = "READ";
 
         private ReadData() {
diff --git a/restconf/sal-rest-connector/src/test/java/org/opendaylight/netconf/sal/rest/impl/DepthAwareNormalizedNodeWriterTest.java b/restconf/sal-rest-connector/src/test/java/org/opendaylight/netconf/sal/rest/impl/DepthAwareNormalizedNodeWriterTest.java
new file mode 100644 (file)
index 0000000..ba8aee8
--- /dev/null
@@ -0,0 +1,327 @@
+/*
+ * Copyright (c) 2016 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.netconf.sal.rest.impl;
+
+import com.google.common.base.Optional;
+import com.google.common.collect.Sets;
+import java.util.Collection;
+import java.util.Collections;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
+import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
+import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
+import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
+import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode;
+import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode;
+import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
+import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
+
+public class DepthAwareNormalizedNodeWriterTest {
+
+    @Mock
+    private NormalizedNodeStreamWriter writer;
+    @Mock
+    private ContainerNode containerNodeData;
+    @Mock
+    private MapNode mapNodeData;
+    @Mock
+    private MapEntryNode mapEntryNodeData;
+    @Mock
+    private LeafSetNode<String> leafSetNodeData;
+    @Mock
+    private LeafSetEntryNode<String> leafSetEntryNodeData;
+    @Mock
+    private LeafNode<String> keyLeafNodeData;
+    @Mock
+    private LeafNode<String> anotherLeafNodeData;
+
+    private NodeIdentifier containerNodeIdentifier;
+    private NodeIdentifier mapNodeIdentifier;
+    private NodeIdentifierWithPredicates mapEntryNodeIdentifier;
+    private NodeIdentifier leafSetNodeIdentifier;
+    private NodeWithValue<?> leafSetEntryNodeIdentifier;
+    private NodeIdentifier keyLeafNodeIdentifier;
+    private NodeIdentifier anotherLeafNodeIdentifier;
+
+    private Collection<DataContainerChild<?, ?>> containerNodeValue;
+    private Collection<MapEntryNode> mapNodeValue;
+    private Collection<DataContainerChild<?, ?>> mapEntryNodeValue;
+    private Collection<LeafSetEntryNode<String>> leafSetNodeValue;
+    private String leafSetEntryNodeValue;
+    private String keyLeafNodeValue;
+    private String anotherLeafNodeValue;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        // identifiers
+        containerNodeIdentifier = NodeIdentifier.create(QName.create("namespace", "container"));
+        Mockito.when(containerNodeData.getIdentifier()).thenReturn(containerNodeIdentifier);
+
+        mapNodeIdentifier = NodeIdentifier.create(QName.create("namespace", "list"));
+        Mockito.when(mapNodeData.getIdentifier()).thenReturn(mapNodeIdentifier);
+
+        final QName leafSetEntryNodeQName = QName.create("namespace", "leaf-set-entry");
+        leafSetEntryNodeValue = "leaf-set-value";
+        leafSetEntryNodeIdentifier = new NodeWithValue<>(leafSetEntryNodeQName, leafSetEntryNodeValue);
+        Mockito.when(leafSetEntryNodeData.getIdentifier()).thenReturn(leafSetEntryNodeIdentifier);
+        Mockito.when(leafSetEntryNodeData.getNodeType()).thenReturn(leafSetEntryNodeQName);
+
+        leafSetNodeIdentifier = NodeIdentifier.create(QName.create("namespace", "leaf-set"));
+        Mockito.when(leafSetNodeData.getIdentifier()).thenReturn(leafSetNodeIdentifier);
+
+        final QName mapEntryNodeKey = QName.create("namespace", "key-field");
+        keyLeafNodeIdentifier = NodeIdentifier.create(mapEntryNodeKey);
+        keyLeafNodeValue = "key-value";
+
+        mapEntryNodeIdentifier = new YangInstanceIdentifier.NodeIdentifierWithPredicates(
+                QName.create("namespace", "list-entry"), Collections.singletonMap(mapEntryNodeKey, keyLeafNodeValue));
+        Mockito.when(mapEntryNodeData.getIdentifier()).thenReturn(mapEntryNodeIdentifier);
+        Mockito.when(mapEntryNodeData.getChild(keyLeafNodeIdentifier)).thenReturn(Optional.of(keyLeafNodeData));
+
+        Mockito.when(keyLeafNodeData.getValue()).thenReturn(keyLeafNodeValue);
+        Mockito.when(keyLeafNodeData.getIdentifier()).thenReturn(keyLeafNodeIdentifier);
+
+        anotherLeafNodeIdentifier = NodeIdentifier.create(QName.create("namespace", "another-field"));
+        anotherLeafNodeValue = "another-value";
+
+        Mockito.when(anotherLeafNodeData.getValue()).thenReturn(anotherLeafNodeValue);
+        Mockito.when(anotherLeafNodeData.getIdentifier()).thenReturn(anotherLeafNodeIdentifier);
+
+        // values
+        Mockito.when(leafSetEntryNodeData.getValue()).thenReturn(leafSetEntryNodeValue);
+
+        leafSetNodeValue = Collections.singletonList(leafSetEntryNodeData);
+        Mockito.when(leafSetNodeData.getValue()).thenReturn(leafSetNodeValue);
+
+        containerNodeValue = Collections.singleton(leafSetNodeData);
+        Mockito.when(containerNodeData.getValue()).thenReturn(containerNodeValue);
+
+        mapEntryNodeValue = Sets.newHashSet(keyLeafNodeData, anotherLeafNodeData);
+        Mockito.when(mapEntryNodeData.getValue()).thenReturn(mapEntryNodeValue);
+
+        mapNodeValue = Collections.singleton(mapEntryNodeData);
+        Mockito.when(mapNodeData.getValue()).thenReturn(mapNodeValue);
+    }
+
+    /**
+     * Test write {@link ContainerNode} with children but write data only to depth 1 (children will not be written).
+     */
+    @Test
+    public void writeContainerWithoutChildrenTest() throws Exception {
+        final DepthAwareNormalizedNodeWriter depthWriter = DepthAwareNormalizedNodeWriter.forStreamWriter(writer, 1);
+
+        depthWriter.write(containerNodeData);
+
+        final InOrder inOrder = Mockito.inOrder(writer);
+        inOrder.verify(writer, Mockito.times(1)).startContainerNode(containerNodeIdentifier, containerNodeValue.size());
+        inOrder.verify(writer, Mockito.times(1)).endNode();
+        Mockito.verifyNoMoreInteractions(writer);
+    }
+
+    /**
+     * Test write {@link ContainerNode} with children and write also all its children.
+     */
+    @Test
+    public void writeContainerWithChildrenTest() throws Exception {
+        final DepthAwareNormalizedNodeWriter depthWriter = DepthAwareNormalizedNodeWriter.forStreamWriter(
+                writer, Integer.MAX_VALUE);
+
+        depthWriter.write(containerNodeData);
+
+        final InOrder inOrder = Mockito.inOrder(writer);
+        inOrder.verify(writer, Mockito.times(1)).startContainerNode(containerNodeIdentifier, containerNodeValue.size());
+        inOrder.verify(writer, Mockito.times(1)).startLeafSet(leafSetNodeIdentifier, leafSetNodeValue.size());
+        inOrder.verify(writer, Mockito.times(1)).leafSetEntryNode(
+                leafSetEntryNodeIdentifier.getNodeType(), leafSetEntryNodeValue);
+        inOrder.verify(writer, Mockito.times(2)).endNode();
+        Mockito.verifyNoMoreInteractions(writer);
+    }
+
+    /**
+     * Test write with {@link MapNode} with children but write data only to depth 1 (children will not be written).
+     */
+    @Test
+    public void writeMapNodeWithoutChildrenTest() throws Exception {
+        final DepthAwareNormalizedNodeWriter depthWriter = DepthAwareNormalizedNodeWriter.forStreamWriter(writer, 1);
+
+        depthWriter.write(mapNodeData);
+
+        final InOrder inOrder = Mockito.inOrder(writer);
+        inOrder.verify(writer, Mockito.times(1)).startMapNode(mapNodeIdentifier, mapNodeValue.size());
+        inOrder.verify(writer, Mockito.times(1)).startMapEntryNode(mapEntryNodeIdentifier, mapEntryNodeValue.size());
+        inOrder.verify(writer, Mockito.times(1)).leafNode(keyLeafNodeIdentifier, keyLeafNodeValue);
+        inOrder.verify(writer, Mockito.times(2)).endNode();
+        Mockito.verifyNoMoreInteractions(writer);
+    }
+
+    /**
+     * Test write {@link MapNode} with children and write also all its children.
+     *
+     * FIXME
+     * Although ordered writer is used leaves are not written in expected order.
+     *
+     */
+    @Ignore
+    @Test
+    public void writeMapNodeWithChildrenTest() throws Exception {
+        final DepthAwareNormalizedNodeWriter depthWriter = DepthAwareNormalizedNodeWriter.forStreamWriter(
+                writer, Integer.MAX_VALUE);
+
+        depthWriter.write(mapNodeData);
+
+        final InOrder inOrder = Mockito.inOrder(writer);
+        inOrder.verify(writer, Mockito.times(1)).startMapNode(mapNodeIdentifier, mapNodeValue.size());
+        inOrder.verify(writer, Mockito.times(1)).startMapEntryNode(mapEntryNodeIdentifier, mapEntryNodeValue.size());
+        inOrder.verify(writer, Mockito.times(2)).leafNode(keyLeafNodeIdentifier, keyLeafNodeValue);
+        // FIXME this assertion is not working because leaves are not written in expected order
+        inOrder.verify(writer, Mockito.times(1)).leafNode(anotherLeafNodeIdentifier, anotherLeafNodeValue);
+        inOrder.verify(writer, Mockito.times(2)).endNode();
+        Mockito.verifyNoMoreInteractions(writer);
+    }
+
+    /**
+     * Test write with {@link LeafSetNode} with depth 1 (children will not be written).
+     */
+    @Test
+    public void writeLeafSetNodeWithoutChildrenTest() throws Exception {
+        final DepthAwareNormalizedNodeWriter depthWriter = DepthAwareNormalizedNodeWriter.forStreamWriter(
+                writer, 1);
+
+        depthWriter.write(leafSetNodeData);
+
+        final InOrder inOrder = Mockito.inOrder(writer);
+        inOrder.verify(writer, Mockito.times(1)).startLeafSet(leafSetNodeIdentifier, leafSetNodeValue.size());
+        inOrder.verify(writer, Mockito.times(1)).endNode();
+        Mockito.verifyNoMoreInteractions(writer);
+    }
+
+    /**
+     * Test write with {@link LeafSetNode} when all its children will be written.
+     */
+    @Test
+    public void writeLeafSetNodeWithChildrenTest() throws Exception {
+        final DepthAwareNormalizedNodeWriter depthWriter = DepthAwareNormalizedNodeWriter.forStreamWriter(
+                writer, Integer.MAX_VALUE);
+
+        depthWriter.write(leafSetNodeData);
+
+        final InOrder inOrder = Mockito.inOrder(writer);
+        inOrder.verify(writer, Mockito.times(1)).startLeafSet(leafSetNodeIdentifier, leafSetNodeValue.size());
+        inOrder.verify(writer, Mockito.times(1)).leafSetEntryNode(
+                leafSetEntryNodeIdentifier.getNodeType(), leafSetEntryNodeValue);
+        inOrder.verify(writer, Mockito.times(1)).endNode();
+        Mockito.verifyNoMoreInteractions(writer);
+    }
+
+    /**
+     * Test write with {@link LeafSetEntryNode}.
+     */
+    @Test
+    public void writeLeafSetEntryNodeTest() throws Exception {
+        final DepthAwareNormalizedNodeWriter depthWriter = DepthAwareNormalizedNodeWriter.forStreamWriter(
+                writer, Integer.MAX_VALUE);
+
+        depthWriter.write(leafSetEntryNodeData);
+
+        final InOrder inOrder = Mockito.inOrder(writer);
+        inOrder.verify(writer, Mockito.times(1)).leafSetEntryNode(
+                leafSetEntryNodeIdentifier.getNodeType(), leafSetEntryNodeValue);
+        Mockito.verifyNoMoreInteractions(writer);
+    }
+
+    /**
+     * Test write with {@link MapEntryNode} unordered to depth 1 to write only keys.
+     */
+    @Test
+    public void writeMapEntryNodeUnorderedOnlyKeysTest() throws Exception {
+        final DepthAwareNormalizedNodeWriter depthWriter = DepthAwareNormalizedNodeWriter.forStreamWriter(
+                writer, false, 1);
+
+        depthWriter.write(mapEntryNodeData);
+
+        final InOrder inOrder = Mockito.inOrder(writer);
+        inOrder.verify(writer, Mockito.times(1)).startMapEntryNode(mapEntryNodeIdentifier, mapEntryNodeValue.size());
+        // write only the key
+        inOrder.verify(writer, Mockito.times(1)).leafNode(keyLeafNodeIdentifier, keyLeafNodeValue);
+        inOrder.verify(writer, Mockito.times(1)).endNode();
+        Mockito.verifyNoMoreInteractions(writer);
+    }
+
+    /**
+     * Test write with {@link MapEntryNode} unordered with full depth.
+     */
+    @Test
+    public void writeMapEntryNodeUnorderedTest() throws Exception {
+        final DepthAwareNormalizedNodeWriter depthWriter = DepthAwareNormalizedNodeWriter.forStreamWriter(
+                writer, false, Integer.MAX_VALUE);
+
+        depthWriter.write(mapEntryNodeData);
+
+        // unordered
+        Mockito.verify(writer, Mockito.times(1)).startMapEntryNode(mapEntryNodeIdentifier, mapEntryNodeValue.size());
+        Mockito.verify(writer, Mockito.times(1)).leafNode(keyLeafNodeIdentifier, keyLeafNodeValue);
+        Mockito.verify(writer, Mockito.times(1)).leafNode(anotherLeafNodeIdentifier, anotherLeafNodeValue);
+        Mockito.verify(writer, Mockito.times(1)).endNode();
+        Mockito.verifyNoMoreInteractions(writer);
+    }
+
+    /**
+     * Test write with {@link MapEntryNode} ordered with depth 1 (children will not be written).
+     */
+    @Test
+    public void writeMapEntryNodeOrderedWithoutChildrenTest() throws Exception {
+        final DepthAwareNormalizedNodeWriter depthWriter = DepthAwareNormalizedNodeWriter.forStreamWriter(
+                writer, true, 1);
+
+        depthWriter.write(mapEntryNodeData);
+
+        final InOrder inOrder = Mockito.inOrder(writer);
+        inOrder.verify(writer, Mockito.times(1)).startMapEntryNode(mapEntryNodeIdentifier, mapEntryNodeValue.size());
+        inOrder.verify(writer, Mockito.times(1)).leafNode(keyLeafNodeIdentifier, keyLeafNodeValue);
+        inOrder.verify(writer, Mockito.times(1)).endNode();
+        Mockito.verifyNoMoreInteractions(writer);
+    }
+
+    /**
+     * Test write with {@link MapEntryNode} ordered and write also all its children.
+     *
+     * FIXME
+     * Although ordered writer is used leaves are not written in expected order.
+     *
+     */
+    @Ignore
+    @Test
+    public void writeMapEntryNodeOrderedTest() throws Exception {
+        final DepthAwareNormalizedNodeWriter depthWriter = DepthAwareNormalizedNodeWriter.forStreamWriter(
+                writer, true, Integer.MAX_VALUE);
+
+        depthWriter.write(mapEntryNodeData);
+
+        final InOrder inOrder = Mockito.inOrder(writer);
+        inOrder.verify(writer, Mockito.times(1)).startMapEntryNode(mapEntryNodeIdentifier, mapEntryNodeValue.size());
+        inOrder.verify(writer, Mockito.times(2)).leafNode(keyLeafNodeIdentifier, keyLeafNodeValue);
+        // FIXME this assertion is not working because leaves are not written in expected order
+        inOrder.verify(writer, Mockito.times(1)).leafNode(anotherLeafNodeIdentifier, anotherLeafNodeValue);
+        inOrder.verify(writer, Mockito.times(1)).endNode();
+        Mockito.verifyNoMoreInteractions(writer);
+    }
+}
\ No newline at end of file
index 485b522cf26e6c08ec3f9b631dce48578a2e4fb3..5248586943642eb580f4b032de9075c6b672504e 100644 (file)
@@ -22,6 +22,7 @@ import com.google.common.base.Optional;
 import com.google.common.util.concurrent.Futures;
 import java.lang.reflect.Field;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import javax.ws.rs.core.MultivaluedHashMap;
 import javax.ws.rs.core.Response;
@@ -227,6 +228,66 @@ public class RestconfDataServiceImplTest {
         dataService.readData("example-jukebox:jukebox", uriInfo);
     }
 
+    /**
+     * Read data from config datastore according to content parameter
+     */
+    @Test
+    public void testReadDataConfigTest() {
+        final MultivaluedHashMap<String, String> parameters = new MultivaluedHashMap<>();
+        parameters.put("content", Collections.singletonList("config"));
+
+        doReturn(parameters).when(uriInfo).getQueryParameters();
+        doReturn(Futures.immediateCheckedFuture(Optional.of(buildBaseContConfig))).when(read)
+                .read(LogicalDatastoreType.CONFIGURATION, iidBase);
+        doReturn(Futures.immediateCheckedFuture(Optional.of(buildBaseContOperational))).when(read)
+                .read(LogicalDatastoreType.OPERATIONAL, iidBase);
+
+        final Response response = dataService.readData("example-jukebox:jukebox", uriInfo);
+
+        assertNotNull(response);
+        assertEquals(200, response.getStatus());
+
+        // response must contain only config data
+        final NormalizedNode<?, ?> data = ((NormalizedNodeContext) response.getEntity()).getData();
+
+        // config data present
+        assertTrue(((ContainerNode) data).getChild(buildPlayerCont.getIdentifier()).isPresent());
+        assertTrue(((ContainerNode) data).getChild(buildLibraryCont.getIdentifier()).isPresent());
+
+        // state data absent
+        assertFalse(((ContainerNode) data).getChild(buildPlaylistList.getIdentifier()).isPresent());
+    }
+
+    /**
+     * Read data from operational datastore according to content parameter
+     */
+    @Test
+    public void testReadDataOperationalTest() {
+        final MultivaluedHashMap<String, String> parameters = new MultivaluedHashMap<>();
+        parameters.put("content", Collections.singletonList("nonconfig"));
+
+        doReturn(parameters).when(uriInfo).getQueryParameters();
+        doReturn(Futures.immediateCheckedFuture(Optional.of(buildBaseContConfig))).when(read)
+                .read(LogicalDatastoreType.CONFIGURATION, iidBase);
+        doReturn(Futures.immediateCheckedFuture(Optional.of(buildBaseContOperational))).when(read)
+                .read(LogicalDatastoreType.OPERATIONAL, iidBase);
+
+        final Response response = dataService.readData("example-jukebox:jukebox", uriInfo);
+
+        assertNotNull(response);
+        assertEquals(200, response.getStatus());
+
+        // response must contain only operational data
+        final NormalizedNode<?, ?> data = ((NormalizedNodeContext) response.getEntity()).getData();
+
+        // state data present
+        assertTrue(((ContainerNode) data).getChild(buildPlayerCont.getIdentifier()).isPresent());
+        assertTrue(((ContainerNode) data).getChild(buildPlaylistList.getIdentifier()).isPresent());
+
+        // config data absent
+        assertFalse(((ContainerNode) data).getChild(buildLibraryCont.getIdentifier()).isPresent());
+    }
+
     @Test
     public void testPutData() {
         final InstanceIdentifierContext<DataSchemaNode> iidContext = new InstanceIdentifierContext<>(iidBase, schemaNode, null, contextRef.get());
diff --git a/restconf/sal-rest-connector/src/test/java/org/opendaylight/restconf/restful/utils/ParametersUtilTest.java b/restconf/sal-rest-connector/src/test/java/org/opendaylight/restconf/restful/utils/ParametersUtilTest.java
new file mode 100644 (file)
index 0000000..b55ffd9
--- /dev/null
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2016 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.restconf.restful.utils;
+
+import static org.junit.Assert.assertEquals;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import org.junit.Assert;
+import org.junit.Test;
+import org.opendaylight.netconf.sal.restconf.impl.RestconfDocumentedException;
+import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorTag;
+import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorType;
+
+/**
+ * Unit test for {@link ParametersUtil}
+ */
+public class ParametersUtilTest {
+
+    /**
+     * Test when all parameters are allowed
+     */
+    @Test
+    public void checkParametersTypesTest() {
+        ParametersUtil.checkParametersTypes(
+                RestconfDataServiceConstant.ReadData.READ_TYPE_TX,
+                Sets.newHashSet("content"),
+                RestconfDataServiceConstant.ReadData.CONTENT, RestconfDataServiceConstant.ReadData.DEPTH);
+    }
+
+    /**
+     * Test when not allowed parameter type is used
+     */
+    @Test
+    public void checkParametersTypesNegativeTest() {
+        try {
+            ParametersUtil.checkParametersTypes(
+                    RestconfDataServiceConstant.ReadData.READ_TYPE_TX,
+                    Sets.newHashSet("not-allowed-parameter"),
+                    RestconfDataServiceConstant.ReadData.CONTENT, RestconfDataServiceConstant.ReadData.DEPTH);
+
+            Assert.fail("Test expected to fail due to not allowed parameter used with operation");
+        } catch (final RestconfDocumentedException e) {
+            assertEquals("Error type is not correct", ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType());
+            assertEquals("Error tag is not correct", ErrorTag.INVALID_VALUE, e.getErrors().get(0).getErrorTag());
+            assertEquals("Error status code is not correct", 400, e.getErrors().get(0).getErrorTag().getStatusCode());
+        }
+    }
+
+    /**
+     * Test when parameter is present at most once
+     */
+    @Test
+    public void checkParameterCountTest() {
+        ParametersUtil.checkParameterCount(Lists.newArrayList("all"), RestconfDataServiceConstant.ReadData.CONTENT);
+    }
+
+    /**
+     * Test when parameter is present more than once
+     */
+    @Test
+    public void checkParameterCountNegativeTest() {
+        try {
+            ParametersUtil.checkParameterCount(Lists.newArrayList("config", "nonconfig", "all"),
+                    RestconfDataServiceConstant.ReadData.CONTENT);
+
+            Assert.fail("Test expected to fail due to multiple values of the same parameter");
+        } catch (final RestconfDocumentedException e) {
+            assertEquals("Error type is not correct", ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType());
+            assertEquals("Error tag is not correct", ErrorTag.INVALID_VALUE, e.getErrors().get(0).getErrorTag());
+            assertEquals("Error status code is not correct", 400, e.getErrors().get(0).getErrorTag().getStatusCode());
+        }
+    }
+}
\ No newline at end of file
index c06cc4532f3a1ddb4d965c831caf18787c0578df..acc543b24e7d8559388f93dd44bd70048b2c9e7a 100644 (file)
@@ -9,20 +9,31 @@
 package org.opendaylight.restconf.restful.utils;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.when;
 
 import com.google.common.base.Optional;
 import com.google.common.util.concurrent.Futures;
+import java.util.Collections;
+import javax.ws.rs.core.MultivaluedHashMap;
+import javax.ws.rs.core.UriInfo;
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
 import org.opendaylight.controller.md.sal.dom.api.DOMDataReadOnlyTransaction;
 import org.opendaylight.controller.md.sal.dom.api.DOMTransactionChain;
 import org.opendaylight.netconf.sal.restconf.impl.InstanceIdentifierContext;
 import org.opendaylight.netconf.sal.restconf.impl.RestconfDocumentedException;
+import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorTag;
+import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorType;
+import org.opendaylight.netconf.sal.restconf.impl.WriterParameters;
 import org.opendaylight.restconf.restful.transaction.TransactionVarsWrapper;
 import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
@@ -122,7 +133,8 @@ public class ReadDataTransactionUtilTest {
         doReturn(Futures.immediateCheckedFuture(Optional.of(data.data4))).when(read)
                 .read(LogicalDatastoreType.OPERATIONAL, data.path);
         doReturn(data.path).when(context).getInstanceIdentifier();
-        final NormalizedNode<?, ?> normalizedNode = ReadDataTransactionUtil.readData(null, wrapper);
+        final NormalizedNode<?, ?> normalizedNode = ReadDataTransactionUtil.readData(
+                RestconfDataServiceConstant.ReadData.ALL, wrapper);
         final ContainerNode checkingData = Builders
                 .containerBuilder()
                 .withNodeIdentifier(nodeIdentifier)
@@ -165,4 +177,142 @@ public class ReadDataTransactionUtilTest {
         final NormalizedNode<?, ?> normalizedNode = ReadDataTransactionUtil.readData(valueOfContent, null);
         assertNull(normalizedNode);
     }
+
+    /**
+     * Test of parsing default parameters from URI request
+     */
+    @Test
+    public void parseUriParametersDefaultTest() {
+        final UriInfo uriInfo = Mockito.mock(UriInfo.class);
+        final MultivaluedHashMap<String, String> parameters = new MultivaluedHashMap<>();
+
+        // no parameters, default values should be used
+        when(uriInfo.getQueryParameters()).thenReturn(parameters);
+
+        final WriterParameters parsedParameters = ReadDataTransactionUtil.parseUriParameters(uriInfo);
+
+        assertEquals("Not correctly parsed URI parameter",
+                RestconfDataServiceConstant.ReadData.ALL, parsedParameters.getContent());
+        assertFalse("Not correctly parsed URI parameter",
+                parsedParameters.getDepth().isPresent());
+    }
+
+    /**
+     * Test of parsing user defined parameters from URI request
+     */
+    @Test
+    public void parseUriParametersUserDefinedTest() {
+        final UriInfo uriInfo = Mockito.mock(UriInfo.class);
+        final MultivaluedHashMap<String, String> parameters = new MultivaluedHashMap<>();
+
+        final String content = "config";
+        final String depth = "10";
+
+        parameters.put("content", Collections.singletonList(content));
+        parameters.put("depth", Collections.singletonList(depth));
+
+        when(uriInfo.getQueryParameters()).thenReturn(parameters);
+
+        final WriterParameters parsedParameters = ReadDataTransactionUtil.parseUriParameters(uriInfo);
+
+        assertEquals("Not correctly parsed URI parameter",
+                content, parsedParameters.getContent());
+        assertTrue("Not correctly parsed URI parameter",
+                parsedParameters.getDepth().isPresent());
+        assertEquals("Not correctly parsed URI parameter",
+                depth, parsedParameters.getDepth().get().toString());
+    }
+
+    /**
+     * Negative test of parsing request URI parameters when content parameter has not allowed value.
+     */
+    @Test
+    public void parseUriParametersContentParameterNegativeTest() {
+        final UriInfo uriInfo = Mockito.mock(UriInfo.class);
+        final MultivaluedHashMap<String, String> parameters = new MultivaluedHashMap<>();
+
+        parameters.put("content", Collections.singletonList("not-allowed-parameter-value"));
+        when(uriInfo.getQueryParameters()).thenReturn(parameters);
+
+        try {
+            ReadDataTransactionUtil.parseUriParameters(uriInfo);
+            fail("Test expected to fail due to not allowed parameter value");
+        } catch (final RestconfDocumentedException e) {
+            // Bad request
+            assertEquals("Error type is not correct", ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType());
+            assertEquals("Error tag is not correct", ErrorTag.INVALID_VALUE, e.getErrors().get(0).getErrorTag());
+            assertEquals("Error status code is not correct", 400, e.getErrors().get(0).getErrorTag().getStatusCode());
+        }
+    }
+
+    /**
+     * Negative test of parsing request URI parameters when depth parameter has not allowed value.
+     */
+    @Test
+    public void parseUriParametersDepthParameterNegativeTest() {
+        final UriInfo uriInfo = Mockito.mock(UriInfo.class);
+        final MultivaluedHashMap<String, String> parameters = new MultivaluedHashMap<>();
+
+        // inserted value is not allowed
+        parameters.put("depth", Collections.singletonList("bounded"));
+        when(uriInfo.getQueryParameters()).thenReturn(parameters);
+
+        try {
+            ReadDataTransactionUtil.parseUriParameters(uriInfo);
+            fail("Test expected to fail due to not allowed parameter value");
+        } catch (final RestconfDocumentedException e) {
+            // Bad request
+            assertEquals("Error type is not correct", ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType());
+            assertEquals("Error tag is not correct", ErrorTag.INVALID_VALUE, e.getErrors().get(0).getErrorTag());
+            assertEquals("Error status code is not correct", 400, e.getErrors().get(0).getErrorTag().getStatusCode());
+        }
+    }
+
+    /**
+     * Negative test of parsing request URI parameters when depth parameter has not allowed value (less than minimum).
+     */
+    @Test
+    public void parseUriParametersDepthMinimalParameterNegativeTest() {
+        final UriInfo uriInfo = Mockito.mock(UriInfo.class);
+        final MultivaluedHashMap<String, String> parameters = new MultivaluedHashMap<>();
+
+        // inserted value is too low
+        parameters.put(
+                "depth", Collections.singletonList(String.valueOf(RestconfDataServiceConstant.ReadData.MIN_DEPTH - 1)));
+        when(uriInfo.getQueryParameters()).thenReturn(parameters);
+
+        try {
+            ReadDataTransactionUtil.parseUriParameters(uriInfo);
+            fail("Test expected to fail due to not allowed parameter value");
+        } catch (final RestconfDocumentedException e) {
+            // Bad request
+            assertEquals("Error type is not correct", ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType());
+            assertEquals("Error tag is not correct", ErrorTag.INVALID_VALUE, e.getErrors().get(0).getErrorTag());
+            assertEquals("Error status code is not correct", 400, e.getErrors().get(0).getErrorTag().getStatusCode());
+        }
+    }
+
+    /**
+     * Negative test of parsing request URI parameters when depth parameter has not allowed value (more than maximum).
+     */
+    @Test
+    public void parseUriParametersDepthMaximalParameterNegativeTest() {
+        final UriInfo uriInfo = Mockito.mock(UriInfo.class);
+        final MultivaluedHashMap<String, String> parameters = new MultivaluedHashMap<>();
+
+        // inserted value is too high
+        parameters.put(
+                "depth", Collections.singletonList(String.valueOf(RestconfDataServiceConstant.ReadData.MAX_DEPTH + 1)));
+        when(uriInfo.getQueryParameters()).thenReturn(parameters);
+
+        try {
+            ReadDataTransactionUtil.parseUriParameters(uriInfo);
+            fail("Test expected to fail due to not allowed parameter value");
+        } catch (final RestconfDocumentedException e) {
+            // Bad request
+            assertEquals("Error type is not correct", ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType());
+            assertEquals("Error tag is not correct", ErrorTag.INVALID_VALUE, e.getErrors().get(0).getErrorTag());
+            assertEquals("Error status code is not correct", 400, e.getErrors().get(0).getErrorTag().getStatusCode());
+        }
+    }
 }