OpenApi add POST request to device root
[netconf.git] / restconf / restconf-nb / src / test / java / org / opendaylight / restconf / nb / jaxrs / RestconfDataPostTest.java
index 2314377cbfab957f21b675ed99b2b7c1e55b3e1a..f2932bcac83a475f8f95097e0847464596381f1f 100644 (file)
@@ -8,11 +8,20 @@
 package org.opendaylight.restconf.nb.jaxrs;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
 import static org.opendaylight.yangtools.util.concurrent.FluentFutures.immediateFalseFluentFuture;
+import static org.opendaylight.yangtools.util.concurrent.FluentFutures.immediateFluentFuture;
+import static org.opendaylight.yangtools.util.concurrent.FluentFutures.immediateTrueFluentFuture;
 
 import java.net.URI;
+import java.util.List;
+import java.util.Optional;
+import javax.ws.rs.container.AsyncResponse;
 import javax.ws.rs.core.MultivaluedHashMap;
 import javax.ws.rs.core.UriBuilder;
 import org.junit.jupiter.api.BeforeEach;
@@ -22,18 +31,30 @@ import org.mockito.Mock;
 import org.mockito.junit.jupiter.MockitoExtension;
 import org.opendaylight.mdsal.common.api.CommitInfo;
 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
+import org.opendaylight.mdsal.dom.api.DOMDataTreeReadTransaction;
 import org.opendaylight.mdsal.dom.api.DOMDataTreeReadWriteTransaction;
+import org.opendaylight.yangtools.yang.common.ErrorTag;
+import org.opendaylight.yangtools.yang.common.ErrorType;
+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.impl.schema.Builders;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.spi.node.ImmutableNodes;
 
 @ExtendWith(MockitoExtension.class)
 class RestconfDataPostTest extends AbstractRestconfTest {
+    private static final String BASE_URI = "http://localhost:8181/rests/";
+    private static final String INSERT = "insert";
+    private static final String POINT = "point";
     @Mock
     private DOMDataTreeReadWriteTransaction tx;
+    @Mock
+    private DOMDataTreeReadTransaction readTx;
+    @Mock
+    private AsyncResponse asyncResponse;
 
     @BeforeEach
     void beforeEach() {
-        doReturn(CommitInfo.emptyFluentFuture()).when(tx).commit();
         doReturn(tx).when(dataBroker).newReadWriteTransaction();
     }
 
@@ -42,39 +63,198 @@ class RestconfDataPostTest extends AbstractRestconfTest {
         doReturn(new MultivaluedHashMap<>()).when(uriInfo).getQueryParameters();
         doReturn(immediateFalseFluentFuture()).when(tx).exists(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
         doNothing().when(tx).put(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID,
-            Builders.containerBuilder().withNodeIdentifier(new NodeIdentifier(JUKEBOX_QNAME)).build());
-        doReturn(UriBuilder.fromUri("http://localhost:8181/rests/")).when(uriInfo).getBaseUriBuilder();
-
-        doReturn(true).when(asyncResponse).resume(responseCaptor.capture());
-        restconf.postDataJSON(stringInputStream("""
-            {
-              "example-jukebox:jukebox" : {
-              }
-            }"""), uriInfo, asyncResponse);
-        final var response = responseCaptor.getValue();
-        assertEquals(201, response.getStatus());
-        assertEquals(URI.create("http://localhost:8181/rests/data/example-jukebox:jukebox"), response.getLocation());
+            ImmutableNodes.newContainerBuilder().withNodeIdentifier(new NodeIdentifier(JUKEBOX_QNAME)).build());
+        doReturn(UriBuilder.fromUri(BASE_URI)).when(uriInfo).getBaseUriBuilder();
+        doReturn(CommitInfo.emptyFluentFuture()).when(tx).commit();
+
+        assertEquals(URI.create("http://localhost:8181/rests/data/example-jukebox:jukebox"),
+            assertResponse(201, ar -> restconf.postDataJSON(stringInputStream("""
+                {
+                  "example-jukebox:jukebox" : {
+                  }
+                }"""), uriInfo, ar)).getLocation());
     }
 
     @Test
-    public void testPostMapEntryData() {
+    void testPostMapEntryData() {
         doReturn(new MultivaluedHashMap<>()).when(uriInfo).getQueryParameters();
         final var node = PLAYLIST_IID.node(BAND_ENTRY.name());
         doReturn(immediateFalseFluentFuture()).when(tx).exists(LogicalDatastoreType.CONFIGURATION, node);
         doNothing().when(tx).put(LogicalDatastoreType.CONFIGURATION, node, BAND_ENTRY);
-        doReturn(UriBuilder.fromUri("http://localhost:8181/rests/")).when(uriInfo).getBaseUriBuilder();
-
-        doReturn(true).when(asyncResponse).resume(responseCaptor.capture());
-        restconf.postDataJSON("example-jukebox:jukebox", stringInputStream("""
-            {
-              "example-jukebox:playlist" : {
-                "name" : "name of band",
-                "description" : "band description"
-              }
-            }"""), uriInfo, asyncResponse);
-        final var response = responseCaptor.getValue();
-        assertEquals(201, response.getStatus());
+        doReturn(UriBuilder.fromUri(BASE_URI)).when(uriInfo).getBaseUriBuilder();
+        doReturn(CommitInfo.emptyFluentFuture()).when(tx).commit();
+
         assertEquals(URI.create("http://localhost:8181/rests/data/example-jukebox:jukebox/playlist=name%20of%20band"),
-            response.getLocation());
+            assertResponse(201, ar -> restconf.postDataJSON(JUKEBOX_API_PATH, stringInputStream("""
+                {
+                  "example-jukebox:playlist" : {
+                    "name" : "name of band",
+                    "description" : "band description"
+                  }
+                }"""), uriInfo, ar)).getLocation());
+    }
+
+    @Test
+    void testPostExistingData() {
+        doReturn(new MultivaluedHashMap<>()).when(uriInfo).getQueryParameters();
+        doReturn(immediateTrueFluentFuture())
+            .when(tx).exists(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
+
+        final var error = assertError(ar -> restconf.postDataJSON(stringInputStream("""
+                {
+                  "example-jukebox:jukebox" : {
+                  }
+                }"""),
+            uriInfo, ar));
+        assertEquals(ErrorType.PROTOCOL, error.getErrorType());
+        assertEquals(ErrorTag.DATA_EXISTS, error.getErrorTag());
+    }
+
+    @Test
+    void testPostExistingListsDataErrorPath() {
+        doReturn(new MultivaluedHashMap<>()).when(uriInfo).getQueryParameters();
+        final var node = PLAYLIST_IID.node(BAND_ENTRY.name());
+        doReturn(immediateTrueFluentFuture()).when(tx).exists(LogicalDatastoreType.CONFIGURATION, node);
+        doNothing().when(tx).put(LogicalDatastoreType.CONFIGURATION, node, BAND_ENTRY);
+
+        final var error = assertError(ar -> restconf.postDataJSON(JUKEBOX_API_PATH,
+            stringInputStream("""
+                {
+                  "example-jukebox:playlist" : {
+                    "name" : "name of band",
+                    "description" : "band description"
+                  }
+                }"""),
+                uriInfo, ar));
+        final var actualPath = error.getErrorPath();
+        final var expectedPath = YangInstanceIdentifier.builder(PLAYLIST_IID)
+            .nodeWithKey(PLAYLIST_QNAME, QName.create(JUKEBOX_QNAME, "name"), "name of band")
+            .build();
+        assertEquals(expectedPath, actualPath);
+    }
+
+    @Test
+    void testPostDataWithInsertLast() {
+        // Mocking the query parameters to include 'insert=last'
+        final var queryParams = new MultivaluedHashMap<String, String>();
+        queryParams.put(INSERT, List.of("last"));
+        doReturn(queryParams).when(uriInfo).getQueryParameters();
+
+        doReturn(immediateFalseFluentFuture()).when(tx)
+            .exists(eq(LogicalDatastoreType.CONFIGURATION), any(YangInstanceIdentifier.class));
+
+        doNothing().when(tx).put(eq(LogicalDatastoreType.CONFIGURATION), any(YangInstanceIdentifier.class),
+            any(NormalizedNode.class));
+        doReturn(UriBuilder.fromUri(BASE_URI)).when(uriInfo).getBaseUriBuilder();
+        doReturn(CommitInfo.emptyFluentFuture()).when(tx).commit();
+
+        assertEquals(URI.create("http://localhost:8181/rests/data/example-jukebox:jukebox/playlist=0/song=3"),
+            assertResponse(201, ar -> restconf.postDataJSON(
+                apiPath("example-jukebox:jukebox/playlist=0"), stringInputStream("""
+                    {
+                      "example-jukebox:song" : [
+                        {
+                           "index": "3"
+                        }
+                      ]
+                    }"""), uriInfo, ar)).getLocation());
+        verify(tx, times(1)).put(any(), any(), any());
+    }
+
+    @Test
+    void testPostDataWithInsertFirst() {
+        // Mocking the query parameters to include 'insert=first'
+        final var queryParams = new MultivaluedHashMap<String, String>();
+        queryParams.put(INSERT, List.of("first"));
+        doReturn(queryParams).when(uriInfo).getQueryParameters();
+        doReturn(readTx).when(dataBroker).newReadOnlyTransaction();
+
+        doReturn(immediateFalseFluentFuture()).when(readTx)
+            .exists(eq(LogicalDatastoreType.CONFIGURATION), any(YangInstanceIdentifier.class));
+        // Mocking existed playlist with two songs in DS
+        doReturn(immediateFluentFuture(Optional.of(PLAYLIST_WITH_SONGS))).when(tx)
+            .read(eq(LogicalDatastoreType.CONFIGURATION), any(YangInstanceIdentifier.class));
+
+        doNothing().when(tx).put(eq(LogicalDatastoreType.CONFIGURATION), any(YangInstanceIdentifier.class),
+                any(NormalizedNode.class));
+        doReturn(UriBuilder.fromUri(BASE_URI)).when(uriInfo).getBaseUriBuilder();
+        doReturn(CommitInfo.emptyFluentFuture()).when(tx).commit();
+
+        assertEquals(URI.create("http://localhost:8181/rests/data/example-jukebox:jukebox/playlist=0/song=3"),
+            assertResponse(201, ar -> restconf.postDataJSON(
+                apiPath("example-jukebox:jukebox/playlist=0"), stringInputStream("""
+                    {
+                      "example-jukebox:song" : [
+                        {
+                           "index": "3"
+                        }
+                      ]
+                    }"""), uriInfo, ar)).getLocation());
+        verify(tx, times(3)).put(any(), any(), any());
+    }
+
+    @Test
+    void testPostDataWithInsertBefore() {
+        // Mocking the query parameters to include 'insert=before' and 'point=example-jukebox:jukebox/playlist=0/song=2'
+        final var queryParams = new MultivaluedHashMap<String, String>();
+        queryParams.put(INSERT, List.of("before"));
+        queryParams.put(POINT, List.of("example-jukebox:jukebox/playlist=0/song=2"));
+        doReturn(queryParams).when(uriInfo).getQueryParameters();
+        doReturn(readTx).when(dataBroker).newReadOnlyTransaction();
+
+        doReturn(immediateFalseFluentFuture()).when(readTx)
+            .exists(eq(LogicalDatastoreType.CONFIGURATION), any(YangInstanceIdentifier.class));
+        // Mocking existed playlist with two songs in DS
+        doReturn(immediateFluentFuture(Optional.of(PLAYLIST_WITH_SONGS))).when(tx)
+            .read(eq(LogicalDatastoreType.CONFIGURATION), any(YangInstanceIdentifier.class));
+
+        doReturn(UriBuilder.fromUri(BASE_URI)).when(uriInfo).getBaseUriBuilder();
+        doReturn(CommitInfo.emptyFluentFuture()).when(tx).commit();
+
+        assertEquals(URI.create("http://localhost:8181/rests/data/example-jukebox:jukebox/playlist=0/song=3"),
+            assertResponse(201, ar -> restconf.postDataJSON(
+                apiPath("example-jukebox:jukebox/playlist=0"), stringInputStream("""
+                    {
+                      "example-jukebox:song" : [
+                        {
+                           "index": "3",
+                           "id" = "C"
+                        }
+                      ]
+                    }"""), uriInfo, ar)).getLocation());
+
+        verify(tx, times(3)).put(any(), any(), any());
+    }
+
+    @Test
+    void testPostDataWithInsertAfter() {
+        // Mocking the query parameters to include 'insert=after' and 'point=example-jukebox:jukebox/playlist=0/song=1'
+        final var queryParams = new MultivaluedHashMap<String, String>();
+        queryParams.put(INSERT, List.of("after"));
+        queryParams.put(POINT, List.of("example-jukebox:jukebox/playlist=0/song=1"));
+        doReturn(queryParams).when(uriInfo).getQueryParameters();
+        doReturn(readTx).when(dataBroker).newReadOnlyTransaction();
+
+        doReturn(immediateFalseFluentFuture()).when(readTx)
+            .exists(eq(LogicalDatastoreType.CONFIGURATION), any(YangInstanceIdentifier.class));
+        doReturn(immediateFluentFuture(Optional.of(PLAYLIST_WITH_SONGS))).when(tx)
+            .read(eq(LogicalDatastoreType.CONFIGURATION), any(YangInstanceIdentifier.class));
+
+        doReturn(UriBuilder.fromUri(BASE_URI)).when(uriInfo).getBaseUriBuilder();
+        doReturn(CommitInfo.emptyFluentFuture()).when(tx).commit();
+
+        assertEquals(URI.create("http://localhost:8181/rests/data/example-jukebox:jukebox/playlist=0/song=3"),
+            assertResponse(201, ar -> restconf.postDataJSON(
+                apiPath("example-jukebox:jukebox/playlist=0"), stringInputStream("""
+                    {
+                      "example-jukebox:song" : [
+                        {
+                           "index": "3",
+                           "id" = "C"
+                        }
+                      ]
+                    }"""), uriInfo, ar)).getLocation());
+
+        verify(tx, times(3)).put(any(), any(), any());
     }
 }