Fix PUT/POST requests for lists
[netconf.git] / restconf / restconf-nb / src / test / java / org / opendaylight / restconf / nb / rfc8040 / rests / transactions / NetconfRestconfStrategyTest.java
index 973fe9e525d2b0236c2514a0a5387f9071490461..4b45f707cce07169bc1769b8311bdc7ca57d94af 100644 (file)
@@ -13,11 +13,15 @@ import static org.junit.Assert.assertNull;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.opendaylight.yangtools.util.concurrent.FluentFutures.immediateFailedFluentFuture;
 import static org.opendaylight.yangtools.util.concurrent.FluentFutures.immediateFluentFuture;
 
 import com.google.common.util.concurrent.Futures;
+import java.text.ParseException;
+import java.util.HashMap;
 import java.util.Optional;
 import org.junit.Before;
 import org.junit.Test;
@@ -29,15 +33,20 @@ import org.opendaylight.mdsal.common.api.TransactionCommitFailedException;
 import org.opendaylight.mdsal.dom.spi.DefaultDOMRpcResult;
 import org.opendaylight.netconf.api.NetconfDocumentedException;
 import org.opendaylight.netconf.dom.api.NetconfDataTreeService;
+import org.opendaylight.restconf.api.ApiPath;
 import org.opendaylight.restconf.common.patch.PatchStatusContext;
 import org.opendaylight.restconf.common.patch.PatchStatusEntity;
+import org.opendaylight.restconf.nb.rfc8040.databind.JsonDataPostBody;
+import org.opendaylight.restconf.nb.rfc8040.databind.JsonResourceBody;
 import org.opendaylight.restconf.server.api.DatabindContext;
 import org.opendaylight.yangtools.yang.common.ErrorSeverity;
 import org.opendaylight.yangtools.yang.common.ErrorTag;
 import org.opendaylight.yangtools.yang.common.ErrorType;
+import org.opendaylight.yangtools.yang.common.Uint32;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
+import org.opendaylight.yangtools.yang.data.spi.node.ImmutableNodes;
 import org.w3c.dom.DOMException;
 
 @RunWith(MockitoJUnitRunner.StrictStubs.class)
@@ -193,6 +202,103 @@ public final class NetconfRestconfStrategyTest extends AbstractRestconfStrategyT
             Optional.empty());
     }
 
+    /**
+     * Test for put with insert=after option for last element of the list. Here we're trying to insert new element of
+     * the ordered list after last existing element. Test uses list with two items and add third one at the end. After
+     * this we check how many times replace transaction was called to know how many items was inserted.
+     * @throws ParseException if ApiPath string cannot be parsed
+     */
+    @Test
+    public void testPutDataWithInsertAfterLast() throws ParseException {
+        // Spy of jukeboxStrategy will be used later to count how many items was inserted
+        final var spyStrategy = spy(jukeboxStrategy());
+        final var spyTx = spy(jukeboxStrategy().prepareWriteExecution());
+        doReturn(spyTx).when(spyStrategy).prepareWriteExecution();
+        doReturn(immediateFluentFuture(Optional.empty())).when(netconfService).getConfig(any());
+
+        // For this test we are using
+        final var songsList = ImmutableNodes.newUserMapBuilder()
+            .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(SONG_QNAME))
+            .withChild(ImmutableNodes.newMapEntryBuilder()
+                .withNodeIdentifier(YangInstanceIdentifier.NodeIdentifierWithPredicates.of(SONG_QNAME, SONG_INDEX_QNAME,
+                    Uint32.valueOf(1)))
+                .withChild(ImmutableNodes.leafNode(SONG_ID_QNAME, "A"))
+                .build())
+            .withChild(ImmutableNodes.newMapEntryBuilder()
+                .withNodeIdentifier(YangInstanceIdentifier.NodeIdentifierWithPredicates.of(SONG_QNAME, SONG_INDEX_QNAME,
+                    Uint32.valueOf(2)))
+                .withChild(ImmutableNodes.leafNode(SONG_ID_QNAME, "B"))
+                .build())
+            .build();
+        doReturn(songsList).when(spyTx).readList(any(YangInstanceIdentifier.class));
+
+        // Creating query params to insert new item after last existing item in list
+        final var queryParams = new HashMap<String, String>();
+        queryParams.put("insert", "after");
+        queryParams.put("point", "example-jukebox:jukebox/playlist=0/song=2");
+
+        // Inserting new song at 3rd position (aka as last element)
+        spyStrategy.dataPUT(ApiPath.parse("example-jukebox:jukebox/playlist=0/song=3"),
+            new JsonResourceBody(stringInputStream("""
+                {
+                  "example-jukebox:song" : [
+                    {
+                       "index": "3",
+                       "id" = "C"
+                    }
+                  ]
+                }""")), queryParams);
+
+        // Counting how many times we insert items in list
+        verify(spyTx, times(3)).replace(any(), any());
+    }
+
+    /**
+     * Test for post with insert=after option for last element of the list. Here we're trying to insert new element of
+     * the ordered list after last existing element. Test uses list with two items and add third one at the end. After
+     * this we check how many times replace transaction was called to know how many items was inserted.
+     * @throws ParseException if ApiPath string cannot be parsed
+     */
+    @Test
+    public void testPostDataWithInsertAfterLast() throws ParseException {
+        // Spy of jukeboxStrategy will be used later to count how many items was inserted
+        final var spyStrategy = spy(jukeboxStrategy());
+        final var spyTx = spy(jukeboxStrategy().prepareWriteExecution());
+        doReturn(spyTx).when(spyStrategy).prepareWriteExecution();
+        doReturn(immediateFluentFuture(Optional.empty())).when(netconfService).getConfig(any());
+
+        // For this test we are using
+        final var songsList = ImmutableNodes.newUserMapBuilder()
+            .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(SONG_QNAME))
+                .withChild(ImmutableNodes.newMapEntryBuilder()
+                    .withNodeIdentifier(YangInstanceIdentifier.NodeIdentifierWithPredicates.of(SONG_QNAME,
+                        SONG_INDEX_QNAME, Uint32.valueOf(1)))
+                    .withChild(ImmutableNodes.leafNode(SONG_ID_QNAME, "A"))
+                    .build())
+                .withChild(ImmutableNodes.newMapEntryBuilder()
+                    .withNodeIdentifier(YangInstanceIdentifier.NodeIdentifierWithPredicates.of(SONG_QNAME,
+                        SONG_INDEX_QNAME, Uint32.valueOf(2)))
+                    .withChild(ImmutableNodes.leafNode(SONG_ID_QNAME, "B"))
+                    .build())
+            .build();
+        doReturn(songsList).when(spyTx).readList(any(YangInstanceIdentifier.class));
+
+        // Creating query params to insert new item after last existing item in list
+        final var queryParams = new HashMap<String, String>();
+        queryParams.put("insert", "after");
+        queryParams.put("point", "example-jukebox:jukebox/playlist=0/song=2");
+
+        // Inserting new song at 3rd position (aka as last element)
+        spyStrategy.dataPOST(ApiPath.parse("example-jukebox:jukebox/playlist=0/song=3"),
+            new JsonDataPostBody(stringInputStream("""
+            {
+              "id" = "C"
+            }""")), queryParams);
+
+        // Counting how many times we insert items in list
+        verify(spyTx, times(3)).replace(any(), any());
+    }
+
     @Test
     public void testPutReplaceListData() {
         doReturn(immediateFluentFuture(Optional.of(mock(ContainerNode.class)))).when(netconfService)