Introduce restconf.server.{api,spi,mdsal}
[netconf.git] / restconf / restconf-nb / src / test / java / org / opendaylight / restconf / nb / rfc8040 / rests / services / impl / RestconfDataServiceImplTest.java
index cbb2bb342d256683c67ac0b78b4b6b9fe3f36e23..be6b7e861afc21644f05bbb9480f9475790f8c82 100644 (file)
@@ -7,28 +7,30 @@
  */
 package org.opendaylight.restconf.nb.rfc8040.rests.services.impl;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertThrows;
-import static org.junit.Assert.assertTrue;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
-import static org.opendaylight.restconf.common.patch.PatchEditOperation.CREATE;
-import static org.opendaylight.restconf.common.patch.PatchEditOperation.DELETE;
-import static org.opendaylight.restconf.common.patch.PatchEditOperation.REMOVE;
-import static org.opendaylight.restconf.common.patch.PatchEditOperation.REPLACE;
 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.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
 import java.util.Collection;
 import java.util.List;
 import java.util.Optional;
 import java.util.Set;
+import javax.ws.rs.container.AsyncResponse;
 import javax.ws.rs.core.MultivaluedHashMap;
 import javax.ws.rs.core.MultivaluedMap;
 import javax.ws.rs.core.Response;
@@ -37,6 +39,8 @@ import javax.ws.rs.core.UriInfo;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
 import org.mockito.Mock;
 import org.mockito.junit.MockitoJUnitRunner;
 import org.opendaylight.mdsal.common.api.CommitInfo;
@@ -45,58 +49,49 @@ import org.opendaylight.mdsal.dom.api.DOMActionService;
 import org.opendaylight.mdsal.dom.api.DOMDataBroker;
 import org.opendaylight.mdsal.dom.api.DOMDataTreeReadTransaction;
 import org.opendaylight.mdsal.dom.api.DOMDataTreeReadWriteTransaction;
-import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteTransaction;
 import org.opendaylight.mdsal.dom.api.DOMMountPoint;
 import org.opendaylight.mdsal.dom.api.DOMMountPointService;
+import org.opendaylight.mdsal.dom.api.DOMRpcService;
 import org.opendaylight.mdsal.dom.api.DOMSchemaService;
-import org.opendaylight.mdsal.dom.api.DOMTransactionChain;
 import org.opendaylight.mdsal.dom.spi.FixedDOMSchemaService;
 import org.opendaylight.netconf.dom.api.NetconfDataTreeService;
-import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
 import org.opendaylight.restconf.common.patch.PatchContext;
 import org.opendaylight.restconf.common.patch.PatchEntity;
 import org.opendaylight.restconf.common.patch.PatchStatusContext;
 import org.opendaylight.restconf.nb.rfc8040.AbstractJukeboxTest;
 import org.opendaylight.restconf.nb.rfc8040.databind.DatabindContext;
+import org.opendaylight.restconf.nb.rfc8040.databind.DatabindProvider;
 import org.opendaylight.restconf.nb.rfc8040.legacy.NormalizedNodePayload;
-import org.opendaylight.restconf.nb.rfc8040.rests.services.api.RestconfStreamsSubscriptionService;
-import org.opendaylight.restconf.nb.rfc8040.rests.transactions.MdsalRestconfStrategy;
-import org.opendaylight.restconf.nb.rfc8040.rests.transactions.NetconfRestconfStrategy;
-import org.opendaylight.restconf.nb.rfc8040.rests.transactions.RestconfStrategy;
-import org.opendaylight.restconf.nb.rfc8040.streams.StreamsConfiguration;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.patch.rev170222.yang.patch.yang.patch.Edit.Operation;
 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.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
 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.MapEntryNode;
-import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
 import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
-import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
 
 @RunWith(MockitoJUnitRunner.StrictStubs.class)
 public class RestconfDataServiceImplTest extends AbstractJukeboxTest {
-    private ContainerNode buildBaseCont;
-    private ContainerNode buildBaseContConfig;
-    private ContainerNode buildBaseContOperational;
-    private YangInstanceIdentifier iidBase;
-    private RestconfDataServiceImpl dataService;
-    private QName baseQName;
-    private QName containerPlayerQname;
-    private QName leafQname;
-    private ContainerNode buildPlayerCont;
-    private ContainerNode buildLibraryCont;
-    private MapNode buildPlaylistList;
+    private static final NodeIdentifier PLAYLIST_NID = new NodeIdentifier(PLAYLIST_QNAME);
+    private static final NodeIdentifier LIBRARY_NID = new NodeIdentifier(LIBRARY_QNAME);
+
+    // config contains one child the same as in operational and one additional
+    private static final ContainerNode CONFIG_JUKEBOX = Builders.containerBuilder()
+            .withNodeIdentifier(new NodeIdentifier(JUKEBOX_QNAME))
+            .withChild(CONT_PLAYER)
+            .withChild(Builders.containerBuilder().withNodeIdentifier(LIBRARY_NID).build())
+            .build();
+    // operational contains one child the same as in config and one additional
+    private static final ContainerNode OPER_JUKEBOX = Builders.containerBuilder()
+            .withNodeIdentifier(new NodeIdentifier(JUKEBOX_QNAME))
+            .withChild(CONT_PLAYER)
+            .withChild(Builders.mapBuilder().withNodeIdentifier(PLAYLIST_NID).build())
+            .build();
 
-    @Mock
-    private DOMTransactionChain domTransactionChain;
     @Mock
     private UriInfo uriInfo;
     @Mock
@@ -104,8 +99,6 @@ public class RestconfDataServiceImplTest extends AbstractJukeboxTest {
     @Mock
     private DOMDataTreeReadTransaction read;
     @Mock
-    private DOMDataTreeWriteTransaction write;
-    @Mock
     private DOMMountPointService mountPointService;
     @Mock
     private DOMMountPoint mountPoint;
@@ -116,76 +109,36 @@ public class RestconfDataServiceImplTest extends AbstractJukeboxTest {
     @Mock
     private DOMActionService actionService;
     @Mock
-    private RestconfStreamsSubscriptionService delegRestconfSubscrService;
+    private DOMRpcService rpcService;
     @Mock
     private MultivaluedMap<String, String> queryParamenters;
+    @Mock
+    private AsyncResponse asyncResponse;
+    @Captor
+    private ArgumentCaptor<Response> responseCaptor;
+
+    private RestconfDataServiceImpl dataService;
 
     @Before
     public void setUp() throws Exception {
         doReturn(Set.of()).when(queryParamenters).entrySet();
         doReturn(queryParamenters).when(uriInfo).getQueryParameters();
 
-        baseQName = QName.create("http://example.com/ns/example-jukebox", "2015-04-04", "jukebox");
-        containerPlayerQname = QName.create(baseQName, "player");
-        leafQname = QName.create(baseQName, "gap");
-
-        final QName containerLibraryQName = QName.create(baseQName, "library");
-        final QName listPlaylistQName = QName.create(baseQName, "playlist");
-
-        final LeafNode<?> buildLeaf = Builders.leafBuilder()
-                .withNodeIdentifier(new NodeIdentifier(leafQname))
-                .withValue(0.2)
-                .build();
-
-        buildPlayerCont = Builders.containerBuilder()
-                .withNodeIdentifier(new NodeIdentifier(containerPlayerQname))
-                .withChild(buildLeaf)
-                .build();
-
-        buildLibraryCont = Builders.containerBuilder()
-                .withNodeIdentifier(new NodeIdentifier(containerLibraryQName))
-                .build();
-
-        buildPlaylistList = Builders.mapBuilder()
-                .withNodeIdentifier(new NodeIdentifier(listPlaylistQName))
-                .build();
-
-        buildBaseCont = Builders.containerBuilder()
-                .withNodeIdentifier(new NodeIdentifier(baseQName))
-                .withChild(buildPlayerCont)
-                .build();
-
-        // config contains one child the same as in operational and one additional
-        buildBaseContConfig = Builders.containerBuilder()
-                .withNodeIdentifier(new NodeIdentifier(baseQName))
-                .withChild(buildPlayerCont)
-                .withChild(buildLibraryCont)
-                .build();
-
-        // operational contains one child the same as in config and one additional
-        buildBaseContOperational = Builders.containerBuilder()
-                .withNodeIdentifier(new NodeIdentifier(baseQName))
-                .withChild(buildPlayerCont)
-                .withChild(buildPlaylistList)
-                .build();
-
-        iidBase = YangInstanceIdentifier.builder()
-                .node(baseQName)
-                .build();
-
         doReturn(CommitInfo.emptyFluentFuture()).when(readWrite).commit();
 
-        DOMDataBroker mockDataBroker = mock(DOMDataBroker.class);
-        doReturn(read).when(mockDataBroker).newReadOnlyTransaction();
-        doReturn(readWrite).when(mockDataBroker).newReadWriteTransaction();
+        final var dataBroker = mock(DOMDataBroker.class);
+        doReturn(read).when(dataBroker).newReadOnlyTransaction();
+        doReturn(readWrite).when(dataBroker).newReadWriteTransaction();
 
-        dataService = new RestconfDataServiceImpl(() -> DatabindContext.ofModel(JUKEBOX_SCHEMA), mockDataBroker,
-                mountPointService, delegRestconfSubscrService, actionService, new StreamsConfiguration(0, 1, 0, false));
+        final DatabindProvider databindProvider = () -> DatabindContext.ofModel(JUKEBOX_SCHEMA);
+        dataService = new RestconfDataServiceImpl(databindProvider,
+            new MdsalRestconfServer(databindProvider, dataBroker, rpcService, mountPointService), actionService);
         doReturn(Optional.of(mountPoint)).when(mountPointService)
                 .getMountPoint(any(YangInstanceIdentifier.class));
         doReturn(Optional.of(FixedDOMSchemaService.of(JUKEBOX_SCHEMA))).when(mountPoint)
                 .getService(DOMSchemaService.class);
         doReturn(Optional.of(mountDataBroker)).when(mountPoint).getService(DOMDataBroker.class);
+        doReturn(Optional.of(rpcService)).when(mountPoint).getService(DOMRpcService.class);
         doReturn(Optional.empty()).when(mountPoint).getService(NetconfDataTreeService.class);
         doReturn(read).when(mountDataBroker).newReadOnlyTransaction();
         doReturn(readWrite).when(mountDataBroker).newReadWriteTransaction();
@@ -194,30 +147,30 @@ public class RestconfDataServiceImplTest extends AbstractJukeboxTest {
     @Test
     public void testReadData() {
         doReturn(new MultivaluedHashMap<>()).when(uriInfo).getQueryParameters();
-        doReturn(immediateFluentFuture(Optional.of(buildBaseCont))).when(read)
-                .read(LogicalDatastoreType.CONFIGURATION, iidBase);
+        doReturn(immediateFluentFuture(Optional.of(EMPTY_JUKEBOX))).when(read)
+                .read(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
         doReturn(immediateFluentFuture(Optional.empty()))
-                .when(read).read(LogicalDatastoreType.OPERATIONAL, iidBase);
+                .when(read).read(LogicalDatastoreType.OPERATIONAL, JUKEBOX_IID);
         final Response response = dataService.readData("example-jukebox:jukebox", uriInfo);
         assertNotNull(response);
         assertEquals(200, response.getStatus());
-        assertEquals(buildBaseCont, ((NormalizedNodePayload) response.getEntity()).getData());
+        assertEquals(EMPTY_JUKEBOX, ((NormalizedNodePayload) response.getEntity()).data());
     }
 
     @Test
     public void testReadRootData() {
         doReturn(new MultivaluedHashMap<>()).when(uriInfo).getQueryParameters();
-        doReturn(immediateFluentFuture(Optional.of(wrapNodeByDataRootContainer(buildBaseContConfig))))
+        doReturn(immediateFluentFuture(Optional.of(wrapNodeByDataRootContainer(CONFIG_JUKEBOX))))
                 .when(read)
                 .read(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.of());
-        doReturn(immediateFluentFuture(Optional.of(wrapNodeByDataRootContainer(buildBaseContOperational))))
+        doReturn(immediateFluentFuture(Optional.of(wrapNodeByDataRootContainer(OPER_JUKEBOX))))
                 .when(read)
                 .read(LogicalDatastoreType.OPERATIONAL, YangInstanceIdentifier.of());
         final Response response = dataService.readData(uriInfo);
         assertNotNull(response);
         assertEquals(200, response.getStatus());
 
-        final NormalizedNode data = ((NormalizedNodePayload) response.getEntity()).getData();
+        final NormalizedNode data = ((NormalizedNodePayload) response.getEntity()).data();
         assertTrue(data instanceof ContainerNode);
         final Collection<DataContainerChild> rootNodes = ((ContainerNode) data).body();
         assertEquals(1, rootNodes.size());
@@ -239,10 +192,10 @@ public class RestconfDataServiceImplTest extends AbstractJukeboxTest {
     @Test
     public void testReadDataMountPoint() {
         doReturn(new MultivaluedHashMap<>()).when(uriInfo).getQueryParameters();
-        doReturn(immediateFluentFuture(Optional.of(buildBaseContConfig))).when(read)
-                .read(LogicalDatastoreType.CONFIGURATION, iidBase);
-        doReturn(immediateFluentFuture(Optional.of(buildBaseContOperational))).when(read)
-                .read(LogicalDatastoreType.OPERATIONAL, iidBase);
+        doReturn(immediateFluentFuture(Optional.of(CONFIG_JUKEBOX))).when(read)
+                .read(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
+        doReturn(immediateFluentFuture(Optional.of(OPER_JUKEBOX))).when(read)
+                .read(LogicalDatastoreType.OPERATIONAL, JUKEBOX_IID);
 
         final Response response = dataService.readData(
                 "example-jukebox:jukebox/yang-ext:mount/example-jukebox:jukebox", uriInfo);
@@ -251,21 +204,21 @@ public class RestconfDataServiceImplTest extends AbstractJukeboxTest {
         assertEquals(200, response.getStatus());
 
         // response must contain all child nodes from config and operational containers merged in one container
-        final NormalizedNode data = ((NormalizedNodePayload) response.getEntity()).getData();
+        final NormalizedNode data = ((NormalizedNodePayload) response.getEntity()).data();
         assertTrue(data instanceof ContainerNode);
         assertEquals(3, ((ContainerNode) data).size());
-        assertNotNull(((ContainerNode) data).childByArg(buildPlayerCont.name()));
-        assertNotNull(((ContainerNode) data).childByArg(buildLibraryCont.name()));
-        assertNotNull(((ContainerNode) data).childByArg(buildPlaylistList.name()));
+        assertNotNull(((ContainerNode) data).childByArg(CONT_PLAYER.name()));
+        assertNotNull(((ContainerNode) data).childByArg(LIBRARY_NID));
+        assertNotNull(((ContainerNode) data).childByArg(PLAYLIST_NID));
     }
 
     @Test
     public void testReadDataNoData() {
         doReturn(new MultivaluedHashMap<>()).when(uriInfo).getQueryParameters();
         doReturn(immediateFluentFuture(Optional.empty()))
-                .when(read).read(LogicalDatastoreType.CONFIGURATION, iidBase);
+                .when(read).read(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
         doReturn(immediateFluentFuture(Optional.empty()))
-                .when(read).read(LogicalDatastoreType.OPERATIONAL, iidBase);
+                .when(read).read(LogicalDatastoreType.OPERATIONAL, JUKEBOX_IID);
 
         final var errors = assertThrows(RestconfDocumentedException.class,
             () -> dataService.readData("example-jukebox:jukebox", uriInfo)).getErrors();
@@ -286,8 +239,8 @@ public class RestconfDataServiceImplTest extends AbstractJukeboxTest {
         parameters.put("content", List.of("config"));
 
         doReturn(parameters).when(uriInfo).getQueryParameters();
-        doReturn(immediateFluentFuture(Optional.of(buildBaseContConfig))).when(read)
-                .read(LogicalDatastoreType.CONFIGURATION, iidBase);
+        doReturn(immediateFluentFuture(Optional.of(CONFIG_JUKEBOX))).when(read)
+                .read(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
 
         final Response response = dataService.readData("example-jukebox:jukebox", uriInfo);
 
@@ -295,14 +248,14 @@ public class RestconfDataServiceImplTest extends AbstractJukeboxTest {
         assertEquals(200, response.getStatus());
 
         // response must contain only config data
-        final NormalizedNode data = ((NormalizedNodePayload) response.getEntity()).getData();
+        final NormalizedNode data = ((NormalizedNodePayload) response.getEntity()).data();
 
         // config data present
-        assertNotNull(((ContainerNode) data).childByArg(buildPlayerCont.name()));
-        assertNotNull(((ContainerNode) data).childByArg(buildLibraryCont.name()));
+        assertNotNull(((ContainerNode) data).childByArg(CONT_PLAYER.name()));
+        assertNotNull(((ContainerNode) data).childByArg(LIBRARY_NID));
 
         // state data absent
-        assertNull(((ContainerNode) data).childByArg(buildPlaylistList.name()));
+        assertNull(((ContainerNode) data).childByArg(PLAYLIST_NID));
     }
 
     /**
@@ -314,8 +267,8 @@ public class RestconfDataServiceImplTest extends AbstractJukeboxTest {
         parameters.put("content", List.of("nonconfig"));
 
         doReturn(parameters).when(uriInfo).getQueryParameters();
-        doReturn(immediateFluentFuture(Optional.of(buildBaseContOperational))).when(read)
-                .read(LogicalDatastoreType.OPERATIONAL, iidBase);
+        doReturn(immediateFluentFuture(Optional.of(OPER_JUKEBOX))).when(read)
+                .read(LogicalDatastoreType.OPERATIONAL, JUKEBOX_IID);
 
         final Response response = dataService.readData("example-jukebox:jukebox", uriInfo);
 
@@ -323,82 +276,125 @@ public class RestconfDataServiceImplTest extends AbstractJukeboxTest {
         assertEquals(200, response.getStatus());
 
         // response must contain only operational data
-        final NormalizedNode data = ((NormalizedNodePayload) response.getEntity()).getData();
+        final NormalizedNode data = ((NormalizedNodePayload) response.getEntity()).data();
 
         // state data present
-        assertNotNull(((ContainerNode) data).childByArg(buildPlayerCont.name()));
-        assertNotNull(((ContainerNode) data).childByArg(buildPlaylistList.name()));
+        assertNotNull(((ContainerNode) data).childByArg(CONT_PLAYER.name()));
+        assertNotNull(((ContainerNode) data).childByArg(PLAYLIST_NID));
 
         // config data absent
-        assertNull(((ContainerNode) data).childByArg(buildLibraryCont.name()));
+        assertNull(((ContainerNode) data).childByArg(LIBRARY_NID));
     }
 
     @Test
     public void testPutData() {
-        final InstanceIdentifierContext iidContext = InstanceIdentifierContext.ofLocalPath(JUKEBOX_SCHEMA, iidBase);
-        final NormalizedNodePayload payload = NormalizedNodePayload.of(iidContext, buildBaseCont);
-
         doReturn(immediateTrueFluentFuture()).when(read)
-                .exists(LogicalDatastoreType.CONFIGURATION, iidBase);
-        doNothing().when(readWrite).put(LogicalDatastoreType.CONFIGURATION, iidBase, payload.getData());
-        final Response response = dataService.putData(null, payload, uriInfo);
-        assertNotNull(response);
+                .exists(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
+        doNothing().when(readWrite).put(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID, EMPTY_JUKEBOX);
+
+        doReturn(true).when(asyncResponse).resume(responseCaptor.capture());
+        dataService.putDataJSON("example-jukebox:jukebox", uriInfo, stringInputStream("""
+            {
+              "example-jukebox:jukebox" : {
+                 "player": {
+                   "gap": "0.2"
+                 }
+              }
+            }"""), asyncResponse);
+        final var response = responseCaptor.getValue();
         assertEquals(Response.Status.NO_CONTENT.getStatusCode(), response.getStatus());
     }
 
     @Test
     public void testPutDataWithMountPoint() {
-        final InstanceIdentifierContext iidContext =
-            InstanceIdentifierContext.ofMountPointPath(mountPoint, JUKEBOX_SCHEMA, iidBase);
-        final NormalizedNodePayload payload = NormalizedNodePayload.of(iidContext, buildBaseCont);
-
         doReturn(immediateTrueFluentFuture()).when(read)
-                .exists(LogicalDatastoreType.CONFIGURATION, iidBase);
-        doNothing().when(readWrite).put(LogicalDatastoreType.CONFIGURATION, iidBase, payload.getData());
-        final Response response = dataService.putData(null, payload, uriInfo);
-        assertNotNull(response);
+                .exists(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
+        doNothing().when(readWrite).put(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID, EMPTY_JUKEBOX);
+
+        doReturn(true).when(asyncResponse).resume(responseCaptor.capture());
+        dataService.putDataXML("example-jukebox:jukebox/yang-ext:mount/example-jukebox:jukebox",
+            uriInfo, stringInputStream("""
+                <jukebox xmlns="http://example.com/ns/example-jukebox">
+                  <player>
+                    <gap>0.2</gap>
+                  </player>
+                </jukebox>"""), asyncResponse);
+        final var response = responseCaptor.getValue();
         assertEquals(Response.Status.NO_CONTENT.getStatusCode(), response.getStatus());
     }
 
+    private static InputStream stringInputStream(final String str) {
+        return new ByteArrayInputStream(str.getBytes(StandardCharsets.UTF_8));
+    }
+
     @Test
     public void testPostData() {
-        final QName listQname = QName.create(baseQName, "playlist");
-        final QName listKeyQname = QName.create(baseQName, "name");
-        final NodeIdentifierWithPredicates nodeWithKey =
-                NodeIdentifierWithPredicates.of(listQname, listKeyQname, "name of band");
+        doReturn(new MultivaluedHashMap<>()).when(uriInfo).getQueryParameters();
+        doReturn(immediateFalseFluentFuture()).when(readWrite).exists(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
+        doNothing().when(readWrite).put(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID,
+            Builders.containerBuilder().withNodeIdentifier(new NodeIdentifier(JUKEBOX_QNAME)).build());
+        doReturn(UriBuilder.fromUri("http://localhost:8181/rests/")).when(uriInfo).getBaseUriBuilder();
+
+        final var captor = ArgumentCaptor.forClass(Response.class);
+        doReturn(true).when(asyncResponse).resume(captor.capture());
+        dataService.postDataJSON(stringInputStream("""
+            {
+              "example-jukebox:jukebox" : {
+              }
+            }"""), uriInfo, asyncResponse);
+        final var response = captor.getValue();
+        assertEquals(201, response.getStatus());
+        assertEquals(URI.create("http://localhost:8181/rests/data/example-jukebox:jukebox"), response.getLocation());
+    }
 
+    @Test
+    public void testPostMapEntryData() {
         doReturn(new MultivaluedHashMap<>()).when(uriInfo).getQueryParameters();
-        final InstanceIdentifierContext iidContext = InstanceIdentifierContext.ofLocalPath(JUKEBOX_SCHEMA, iidBase);
-        final NormalizedNodePayload payload = NormalizedNodePayload.of(iidContext, Builders.mapBuilder()
-            .withNodeIdentifier(new NodeIdentifier(listQname))
-            .withChild(Builders.mapEntryBuilder()
-                .withNodeIdentifier(nodeWithKey)
-                .withChild(ImmutableNodes.leafNode(QName.create(baseQName, "name"), "name of band"))
-                .withChild(ImmutableNodes.leafNode(QName.create(baseQName, "description"), "band description"))
-                .build())
-            .build());
-        final MapNode data = (MapNode) payload.getData();
-        final MapEntryNode entryNode = data.body().iterator().next();
-        final NodeIdentifierWithPredicates identifier = entryNode.name();
-        final YangInstanceIdentifier node =
-                payload.getInstanceIdentifierContext().getInstanceIdentifier().node(identifier);
-        doReturn(immediateFalseFluentFuture())
-                .when(readWrite).exists(LogicalDatastoreType.CONFIGURATION, node);
-        doNothing().when(readWrite).put(LogicalDatastoreType.CONFIGURATION, node, entryNode);
+        final var node = PLAYLIST_IID.node(BAND_ENTRY.name());
+        doReturn(immediateFalseFluentFuture()).when(readWrite).exists(LogicalDatastoreType.CONFIGURATION, node);
+        doNothing().when(readWrite).put(LogicalDatastoreType.CONFIGURATION, node, BAND_ENTRY);
         doReturn(UriBuilder.fromUri("http://localhost:8181/rests/")).when(uriInfo).getBaseUriBuilder();
 
-        final Response response = dataService.postData(null, payload, uriInfo);
+        final var captor = ArgumentCaptor.forClass(Response.class);
+        doReturn(true).when(asyncResponse).resume(captor.capture());
+        dataService.postDataJSON("example-jukebox:jukebox", stringInputStream("""
+            {
+              "example-jukebox:playlist" : {
+                "name" : "name of band",
+                "description" : "band description"
+              }
+            }"""), uriInfo, asyncResponse);
+        final var response = captor.getValue();
         assertEquals(201, response.getStatus());
+        assertEquals(URI.create("http://localhost:8181/rests/data/example-jukebox:jukebox/playlist=name%20of%20band"),
+            response.getLocation());
     }
 
     @Test
     public void testDeleteData() {
-        doNothing().when(readWrite).delete(LogicalDatastoreType.CONFIGURATION, iidBase);
+        doNothing().when(readWrite).delete(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
         doReturn(immediateTrueFluentFuture())
-                .when(readWrite).exists(LogicalDatastoreType.CONFIGURATION, iidBase);
-        final Response response = dataService.deleteData("example-jukebox:jukebox");
-        assertNotNull(response);
-        assertEquals(Response.Status.NO_CONTENT.getStatusCode(), response.getStatus());
+                .when(readWrite).exists(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
+        final var captor = ArgumentCaptor.forClass(Response.class);
+        doReturn(true).when(asyncResponse).resume(captor.capture());
+        dataService.deleteData("example-jukebox:jukebox", asyncResponse);
+
+        assertEquals(204, captor.getValue().getStatus());
+    }
+
+    @Test
+    public void testDeleteDataNotExisting() {
+        doReturn(immediateFalseFluentFuture())
+                .when(readWrite).exists(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
+        final var captor = ArgumentCaptor.forClass(RestconfDocumentedException.class);
+        doReturn(true).when(asyncResponse).resume(captor.capture());
+        dataService.deleteData("example-jukebox:jukebox", asyncResponse);
+
+        final var errors = captor.getValue().getErrors();
+        assertEquals(1, errors.size());
+        final var error = errors.get(0);
+        assertEquals(ErrorType.PROTOCOL, error.getErrorType());
+        assertEquals(ErrorTag.DATA_MISSING, error.getErrorTag());
     }
 
     /**
@@ -406,99 +402,89 @@ public class RestconfDataServiceImplTest extends AbstractJukeboxTest {
      */
     @Test
     public void testDeleteDataMountPoint() {
-        doNothing().when(readWrite).delete(LogicalDatastoreType.CONFIGURATION, iidBase);
+        doNothing().when(readWrite).delete(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
         doReturn(immediateTrueFluentFuture())
-                .when(readWrite).exists(LogicalDatastoreType.CONFIGURATION, iidBase);
-        final Response response =
-                dataService.deleteData("example-jukebox:jukebox/yang-ext:mount/example-jukebox:jukebox");
-        assertNotNull(response);
-        assertEquals(Response.Status.NO_CONTENT.getStatusCode(), response.getStatus());
+                .when(readWrite).exists(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
+        final var captor = ArgumentCaptor.forClass(Response.class);
+        doReturn(true).when(asyncResponse).resume(captor.capture());
+        dataService.deleteData("example-jukebox:jukebox/yang-ext:mount/example-jukebox:jukebox", asyncResponse);
+
+        assertEquals(204, captor.getValue().getStatus());
     }
 
     @Test
     public void testPatchData() {
-        final InstanceIdentifierContext iidContext = InstanceIdentifierContext.ofLocalPath(JUKEBOX_SCHEMA, iidBase);
-        final YangInstanceIdentifier iidleaf = YangInstanceIdentifier.builder(iidBase)
-                .node(containerPlayerQname)
-                .node(leafQname)
-                .build();
-        final PatchContext patch = new PatchContext(iidContext, List.of(
-            new PatchEntity("create data", CREATE, iidBase, buildBaseCont),
-            new PatchEntity("replace data", REPLACE, iidBase, buildBaseCont),
-            new PatchEntity("delete data", DELETE, iidleaf)), "test patch id");
-
-        doNothing().when(readWrite).delete(LogicalDatastoreType.CONFIGURATION, iidleaf);
+        final var patch = new PatchContext("test patch id", List.of(
+            new PatchEntity("create data", Operation.Create, JUKEBOX_IID, EMPTY_JUKEBOX),
+            new PatchEntity("replace data", Operation.Replace, JUKEBOX_IID, EMPTY_JUKEBOX),
+            new PatchEntity("delete data", Operation.Delete, GAP_IID)));
+
+        doNothing().when(readWrite).delete(LogicalDatastoreType.CONFIGURATION, GAP_IID);
         doReturn(immediateFalseFluentFuture())
-                .when(readWrite).exists(LogicalDatastoreType.CONFIGURATION, iidBase);
+                .when(readWrite).exists(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
         doReturn(immediateTrueFluentFuture())
-                .when(readWrite).exists(LogicalDatastoreType.CONFIGURATION, iidleaf);
-        final PatchStatusContext status = dataService.patchData(patch, uriInfo);
-        assertTrue(status.isOk());
-        assertEquals(3, status.getEditCollection().size());
-        assertEquals("replace data", status.getEditCollection().get(1).getEditId());
+                .when(readWrite).exists(LogicalDatastoreType.CONFIGURATION, GAP_IID);
+        doReturn(true).when(asyncResponse).resume(responseCaptor.capture());
+        dataService.yangPatchData(JUKEBOX_SCHEMA, patch, null, asyncResponse);
+        final var response = responseCaptor.getValue();
+        assertEquals(200, response.getStatus());
+        final var status = assertInstanceOf(PatchStatusContext.class, response.getEntity());
+
+        assertTrue(status.ok());
+        assertEquals(3, status.editCollection().size());
+        assertEquals("replace data", status.editCollection().get(1).getEditId());
     }
 
     @Test
     public void testPatchDataMountPoint() throws Exception {
-        final InstanceIdentifierContext iidContext = InstanceIdentifierContext.ofMountPointPath(mountPoint,
-            JUKEBOX_SCHEMA, iidBase);
-        final YangInstanceIdentifier iidleaf = YangInstanceIdentifier.builder(iidBase)
-                .node(containerPlayerQname)
-                .node(leafQname)
-                .build();
-        final PatchContext patch = new PatchContext(iidContext, List.of(
-            new PatchEntity("create data", CREATE, iidBase, buildBaseCont),
-            new PatchEntity("replace data", REPLACE, iidBase, buildBaseCont),
-            new PatchEntity("delete data", DELETE, iidleaf)), "test patch id");
-
-        doNothing().when(readWrite).delete(LogicalDatastoreType.CONFIGURATION, iidleaf);
+        final var patch = new PatchContext("test patch id", List.of(
+            new PatchEntity("create data", Operation.Create, JUKEBOX_IID, EMPTY_JUKEBOX),
+            new PatchEntity("replace data", Operation.Replace, JUKEBOX_IID, EMPTY_JUKEBOX),
+            new PatchEntity("delete data", Operation.Delete, GAP_IID)));
+
+        doNothing().when(readWrite).delete(LogicalDatastoreType.CONFIGURATION, GAP_IID);
         doReturn(immediateFalseFluentFuture())
-                .when(readWrite).exists(LogicalDatastoreType.CONFIGURATION, iidBase);
-        doReturn(immediateTrueFluentFuture()).when(readWrite).exists(LogicalDatastoreType.CONFIGURATION, iidleaf);
+                .when(readWrite).exists(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
+        doReturn(immediateTrueFluentFuture()).when(readWrite).exists(LogicalDatastoreType.CONFIGURATION, GAP_IID);
+
+        doReturn(true).when(asyncResponse).resume(responseCaptor.capture());
+        dataService.yangPatchData(JUKEBOX_SCHEMA, patch, mountPoint, asyncResponse);
+        final var response = responseCaptor.getValue();
+        assertEquals(200, response.getStatus());
+        final var status = assertInstanceOf(PatchStatusContext.class, response.getEntity());
 
-        final PatchStatusContext status = dataService.patchData(patch, uriInfo);
-        assertTrue(status.isOk());
-        assertEquals(3, status.getEditCollection().size());
-        assertNull(status.getGlobalErrors());
+        assertTrue(status.ok());
+        assertEquals(3, status.editCollection().size());
+        assertNull(status.globalErrors());
     }
 
     @Test
     public void testPatchDataDeleteNotExist() {
-        final InstanceIdentifierContext iidContext = InstanceIdentifierContext.ofLocalPath(JUKEBOX_SCHEMA, iidBase);
-        final YangInstanceIdentifier iidleaf = YangInstanceIdentifier.builder(iidBase)
-                .node(containerPlayerQname)
-                .node(leafQname)
-                .build();
-        final PatchContext patch = new PatchContext(iidContext, List.of(
-            new PatchEntity("create data", CREATE, iidBase, buildBaseCont),
-            new PatchEntity("remove data", REMOVE, iidleaf),
-            new PatchEntity("delete data", DELETE, iidleaf)), "test patch id");
-
-        doNothing().when(readWrite).delete(LogicalDatastoreType.CONFIGURATION, iidleaf);
+        final var patch = new PatchContext("test patch id", List.of(
+            new PatchEntity("create data", Operation.Create, JUKEBOX_IID, EMPTY_JUKEBOX),
+            new PatchEntity("remove data", Operation.Remove, GAP_IID),
+            new PatchEntity("delete data", Operation.Delete, GAP_IID)));
+
+        doNothing().when(readWrite).delete(LogicalDatastoreType.CONFIGURATION, GAP_IID);
         doReturn(immediateFalseFluentFuture())
-                .when(readWrite).exists(LogicalDatastoreType.CONFIGURATION, iidBase);
+                .when(readWrite).exists(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
         doReturn(immediateFalseFluentFuture())
-                .when(readWrite).exists(LogicalDatastoreType.CONFIGURATION, iidleaf);
+                .when(readWrite).exists(LogicalDatastoreType.CONFIGURATION, GAP_IID);
         doReturn(true).when(readWrite).cancel();
-        final PatchStatusContext status = dataService.patchData(patch, uriInfo);
-
-        assertFalse(status.isOk());
-        assertEquals(3, status.getEditCollection().size());
-        assertTrue(status.getEditCollection().get(0).isOk());
-        assertTrue(status.getEditCollection().get(1).isOk());
-        assertFalse(status.getEditCollection().get(2).isOk());
-        assertFalse(status.getEditCollection().get(2).getEditErrors().isEmpty());
-        final String errorMessage = status.getEditCollection().get(2).getEditErrors().get(0).getErrorMessage();
-        assertEquals("Data does not exist", errorMessage);
-    }
 
-    @Test
-    public void testGetRestconfStrategy() {
-        RestconfStrategy restconfStrategy = dataService.getRestconfStrategy(mountPoint);
-        assertTrue(restconfStrategy instanceof MdsalRestconfStrategy);
-
-        doReturn(Optional.of(netconfService)).when(mountPoint).getService(NetconfDataTreeService.class);
-        restconfStrategy = dataService.getRestconfStrategy(mountPoint);
-        assertTrue(restconfStrategy instanceof NetconfRestconfStrategy);
+        doReturn(true).when(asyncResponse).resume(responseCaptor.capture());
+        dataService.yangPatchData(JUKEBOX_SCHEMA, patch, null, asyncResponse);
+        final var response = responseCaptor.getValue();
+        assertEquals(409, response.getStatus());
+        final var status = assertInstanceOf(PatchStatusContext.class, response.getEntity());
+
+        assertFalse(status.ok());
+        assertEquals(3, status.editCollection().size());
+        assertTrue(status.editCollection().get(0).isOk());
+        assertTrue(status.editCollection().get(1).isOk());
+        assertFalse(status.editCollection().get(2).isOk());
+        assertFalse(status.editCollection().get(2).getEditErrors().isEmpty());
+        final String errorMessage = status.editCollection().get(2).getEditErrors().get(0).getErrorMessage();
+        assertEquals("Data does not exist", errorMessage);
     }
 }