Introduce asynchronous RestconfServer.readData()
[netconf.git] / restconf / restconf-nb / src / test / java / org / opendaylight / restconf / nb / rfc8040 / rests / services / impl / RestconfDataServiceImplTest.java
1 /*
2  * Copyright (c) 2016 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.restconf.nb.rfc8040.rests.services.impl;
9
10 import static org.junit.jupiter.api.Assertions.assertEquals;
11 import static org.junit.jupiter.api.Assertions.assertFalse;
12 import static org.junit.jupiter.api.Assertions.assertInstanceOf;
13 import static org.junit.jupiter.api.Assertions.assertNotNull;
14 import static org.junit.jupiter.api.Assertions.assertNull;
15 import static org.junit.jupiter.api.Assertions.assertTrue;
16 import static org.mockito.ArgumentMatchers.any;
17 import static org.mockito.Mockito.doNothing;
18 import static org.mockito.Mockito.doReturn;
19 import static org.mockito.Mockito.mock;
20 import static org.opendaylight.yangtools.util.concurrent.FluentFutures.immediateFalseFluentFuture;
21 import static org.opendaylight.yangtools.util.concurrent.FluentFutures.immediateFluentFuture;
22 import static org.opendaylight.yangtools.util.concurrent.FluentFutures.immediateTrueFluentFuture;
23
24 import java.io.ByteArrayInputStream;
25 import java.io.InputStream;
26 import java.net.URI;
27 import java.nio.charset.StandardCharsets;
28 import java.util.List;
29 import java.util.Optional;
30 import java.util.Set;
31 import javax.ws.rs.container.AsyncResponse;
32 import javax.ws.rs.core.MultivaluedHashMap;
33 import javax.ws.rs.core.MultivaluedMap;
34 import javax.ws.rs.core.Response;
35 import javax.ws.rs.core.UriBuilder;
36 import javax.ws.rs.core.UriInfo;
37 import org.junit.Before;
38 import org.junit.Test;
39 import org.junit.runner.RunWith;
40 import org.mockito.ArgumentCaptor;
41 import org.mockito.Captor;
42 import org.mockito.Mock;
43 import org.mockito.junit.MockitoJUnitRunner;
44 import org.opendaylight.mdsal.common.api.CommitInfo;
45 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
46 import org.opendaylight.mdsal.dom.api.DOMActionService;
47 import org.opendaylight.mdsal.dom.api.DOMDataBroker;
48 import org.opendaylight.mdsal.dom.api.DOMDataTreeReadTransaction;
49 import org.opendaylight.mdsal.dom.api.DOMDataTreeReadWriteTransaction;
50 import org.opendaylight.mdsal.dom.api.DOMMountPoint;
51 import org.opendaylight.mdsal.dom.api.DOMMountPointService;
52 import org.opendaylight.mdsal.dom.api.DOMRpcService;
53 import org.opendaylight.mdsal.dom.api.DOMSchemaService;
54 import org.opendaylight.mdsal.dom.spi.FixedDOMSchemaService;
55 import org.opendaylight.netconf.dom.api.NetconfDataTreeService;
56 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
57 import org.opendaylight.restconf.common.patch.PatchContext;
58 import org.opendaylight.restconf.common.patch.PatchEntity;
59 import org.opendaylight.restconf.common.patch.PatchStatusContext;
60 import org.opendaylight.restconf.nb.rfc8040.AbstractJukeboxTest;
61 import org.opendaylight.restconf.nb.rfc8040.databind.DatabindContext;
62 import org.opendaylight.restconf.nb.rfc8040.databind.DatabindProvider;
63 import org.opendaylight.restconf.nb.rfc8040.legacy.NormalizedNodePayload;
64 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.patch.rev170222.yang.patch.yang.patch.Edit.Operation;
65 import org.opendaylight.yangtools.yang.common.ErrorTag;
66 import org.opendaylight.yangtools.yang.common.ErrorType;
67 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
68 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
69 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
70 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
71 import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
72 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
73
74 @RunWith(MockitoJUnitRunner.StrictStubs.class)
75 public class RestconfDataServiceImplTest extends AbstractJukeboxTest {
76     private static final NodeIdentifier PLAYLIST_NID = new NodeIdentifier(PLAYLIST_QNAME);
77     private static final NodeIdentifier LIBRARY_NID = new NodeIdentifier(LIBRARY_QNAME);
78
79     // config contains one child the same as in operational and one additional
80     private static final ContainerNode CONFIG_JUKEBOX = Builders.containerBuilder()
81             .withNodeIdentifier(new NodeIdentifier(JUKEBOX_QNAME))
82             .withChild(CONT_PLAYER)
83             .withChild(Builders.containerBuilder().withNodeIdentifier(LIBRARY_NID).build())
84             .build();
85     // operational contains one child the same as in config and one additional
86     private static final ContainerNode OPER_JUKEBOX = Builders.containerBuilder()
87             .withNodeIdentifier(new NodeIdentifier(JUKEBOX_QNAME))
88             .withChild(CONT_PLAYER)
89             .withChild(Builders.mapBuilder().withNodeIdentifier(PLAYLIST_NID).build())
90             .build();
91
92     @Mock
93     private UriInfo uriInfo;
94     @Mock
95     private DOMDataTreeReadWriteTransaction readWrite;
96     @Mock
97     private DOMDataTreeReadTransaction read;
98     @Mock
99     private DOMMountPointService mountPointService;
100     @Mock
101     private DOMMountPoint mountPoint;
102     @Mock
103     private DOMDataBroker mountDataBroker;
104     @Mock
105     private NetconfDataTreeService netconfService;
106     @Mock
107     private DOMActionService actionService;
108     @Mock
109     private DOMRpcService rpcService;
110     @Mock
111     private MultivaluedMap<String, String> queryParamenters;
112     @Mock
113     private AsyncResponse asyncResponse;
114     @Captor
115     private ArgumentCaptor<Response> responseCaptor;
116
117     private RestconfDataServiceImpl dataService;
118
119     @Before
120     public void setUp() throws Exception {
121         doReturn(Set.of()).when(queryParamenters).entrySet();
122         doReturn(queryParamenters).when(uriInfo).getQueryParameters();
123
124         doReturn(CommitInfo.emptyFluentFuture()).when(readWrite).commit();
125
126         final var dataBroker = mock(DOMDataBroker.class);
127         doReturn(read).when(dataBroker).newReadOnlyTransaction();
128         doReturn(readWrite).when(dataBroker).newReadWriteTransaction();
129
130         final DatabindProvider databindProvider = () -> DatabindContext.ofModel(JUKEBOX_SCHEMA);
131         dataService = new RestconfDataServiceImpl(databindProvider,
132             new MdsalRestconfServer(databindProvider, dataBroker, rpcService, actionService, mountPointService));
133         doReturn(Optional.of(mountPoint)).when(mountPointService)
134                 .getMountPoint(any(YangInstanceIdentifier.class));
135         doReturn(Optional.of(FixedDOMSchemaService.of(JUKEBOX_SCHEMA))).when(mountPoint)
136                 .getService(DOMSchemaService.class);
137         doReturn(Optional.of(mountDataBroker)).when(mountPoint).getService(DOMDataBroker.class);
138         doReturn(Optional.of(rpcService)).when(mountPoint).getService(DOMRpcService.class);
139         doReturn(Optional.empty()).when(mountPoint).getService(NetconfDataTreeService.class);
140         doReturn(read).when(mountDataBroker).newReadOnlyTransaction();
141         doReturn(readWrite).when(mountDataBroker).newReadWriteTransaction();
142     }
143
144     @Test
145     public void testReadData() {
146         doReturn(new MultivaluedHashMap<>()).when(uriInfo).getQueryParameters();
147         doReturn(immediateFluentFuture(Optional.of(EMPTY_JUKEBOX))).when(read)
148                 .read(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
149         doReturn(immediateFluentFuture(Optional.empty()))
150                 .when(read).read(LogicalDatastoreType.OPERATIONAL, JUKEBOX_IID);
151
152         doReturn(true).when(asyncResponse).resume(responseCaptor.capture());
153         dataService.dataGET("example-jukebox:jukebox", uriInfo, asyncResponse);
154         final var response = responseCaptor.getValue();
155         assertEquals(200, response.getStatus());
156         assertEquals(EMPTY_JUKEBOX, ((NormalizedNodePayload) response.getEntity()).data());
157     }
158
159     @Test
160     public void testReadRootData() {
161         doReturn(new MultivaluedHashMap<>()).when(uriInfo).getQueryParameters();
162         doReturn(immediateFluentFuture(Optional.of(wrapNodeByDataRootContainer(CONFIG_JUKEBOX))))
163                 .when(read)
164                 .read(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.of());
165         doReturn(immediateFluentFuture(Optional.of(wrapNodeByDataRootContainer(OPER_JUKEBOX))))
166                 .when(read)
167                 .read(LogicalDatastoreType.OPERATIONAL, YangInstanceIdentifier.of());
168
169         doReturn(true).when(asyncResponse).resume(responseCaptor.capture());
170         dataService.dataGET(uriInfo, asyncResponse);
171         final var response = responseCaptor.getValue();
172         assertEquals(200, response.getStatus());
173
174         final var data = assertInstanceOf(ContainerNode.class, ((NormalizedNodePayload) response.getEntity()).data());
175         final var rootNodes = data.body();
176         assertEquals(1, rootNodes.size());
177         final var allDataChildren = assertInstanceOf(ContainerNode.class, rootNodes.iterator().next()).body();
178         assertEquals(3, allDataChildren.size());
179     }
180
181     private static ContainerNode wrapNodeByDataRootContainer(final DataContainerChild data) {
182         return Builders.containerBuilder()
183             .withNodeIdentifier(NodeIdentifier.create(SchemaContext.NAME))
184             .withChild(data)
185             .build();
186     }
187
188     /**
189      * Test read data from mount point when both {@link LogicalDatastoreType#CONFIGURATION} and
190      * {@link LogicalDatastoreType#OPERATIONAL} contains the same data and some additional data to be merged.
191      */
192     @Test
193     public void testReadDataMountPoint() {
194         doReturn(new MultivaluedHashMap<>()).when(uriInfo).getQueryParameters();
195         doReturn(immediateFluentFuture(Optional.of(CONFIG_JUKEBOX))).when(read)
196                 .read(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
197         doReturn(immediateFluentFuture(Optional.of(OPER_JUKEBOX))).when(read)
198                 .read(LogicalDatastoreType.OPERATIONAL, JUKEBOX_IID);
199
200         doReturn(true).when(asyncResponse).resume(responseCaptor.capture());
201         dataService.dataGET("example-jukebox:jukebox/yang-ext:mount/example-jukebox:jukebox", uriInfo, asyncResponse);
202         final var response = responseCaptor.getValue();
203         assertEquals(200, response.getStatus());
204
205         // response must contain all child nodes from config and operational containers merged in one container
206         final var data = assertInstanceOf(ContainerNode.class, ((NormalizedNodePayload) response.getEntity()).data());
207         assertEquals(3, data.size());
208         assertNotNull(data.childByArg(CONT_PLAYER.name()));
209         assertNotNull(data.childByArg(LIBRARY_NID));
210         assertNotNull(data.childByArg(PLAYLIST_NID));
211     }
212
213     @Test
214     public void testReadDataNoData() {
215         doReturn(new MultivaluedHashMap<>()).when(uriInfo).getQueryParameters();
216         doReturn(immediateFluentFuture(Optional.empty()))
217                 .when(read).read(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
218         doReturn(immediateFluentFuture(Optional.empty()))
219                 .when(read).read(LogicalDatastoreType.OPERATIONAL, JUKEBOX_IID);
220
221         final var rdeCaptor = ArgumentCaptor.forClass(RestconfDocumentedException.class);
222         doReturn(true).when(asyncResponse).resume(rdeCaptor.capture());
223         dataService.dataGET("example-jukebox:jukebox", uriInfo, asyncResponse);
224
225         final var errors = rdeCaptor.getValue().getErrors();
226         assertEquals(1, errors.size());
227         final var error = errors.get(0);
228         assertEquals(ErrorType.PROTOCOL, error.getErrorType());
229         assertEquals(ErrorTag.DATA_MISSING, error.getErrorTag());
230         assertEquals("Request could not be completed because the relevant data model content does not exist",
231             error.getErrorMessage());
232     }
233
234     /**
235      * Read data from config datastore according to content parameter.
236      */
237     @Test
238     public void testReadDataConfigTest() {
239         final MultivaluedHashMap<String, String> parameters = new MultivaluedHashMap<>();
240         parameters.put("content", List.of("config"));
241
242         doReturn(parameters).when(uriInfo).getQueryParameters();
243         doReturn(immediateFluentFuture(Optional.of(CONFIG_JUKEBOX))).when(read)
244                 .read(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
245
246         doReturn(true).when(asyncResponse).resume(responseCaptor.capture());
247         dataService.dataGET("example-jukebox:jukebox", uriInfo, asyncResponse);
248         final var response = responseCaptor.getValue();
249         assertEquals(200, response.getStatus());
250
251         // response must contain only config data
252         final var data = assertInstanceOf(ContainerNode.class, ((NormalizedNodePayload) response.getEntity()).data());
253
254         // config data present
255         assertNotNull(data.childByArg(CONT_PLAYER.name()));
256         assertNotNull(data.childByArg(LIBRARY_NID));
257
258         // state data absent
259         assertNull(data.childByArg(PLAYLIST_NID));
260     }
261
262     /**
263      * Read data from operational datastore according to content parameter.
264      */
265     @Test
266     public void testReadDataOperationalTest() {
267         final MultivaluedHashMap<String, String> parameters = new MultivaluedHashMap<>();
268         parameters.put("content", List.of("nonconfig"));
269
270         doReturn(parameters).when(uriInfo).getQueryParameters();
271         doReturn(immediateFluentFuture(Optional.of(OPER_JUKEBOX))).when(read)
272                 .read(LogicalDatastoreType.OPERATIONAL, JUKEBOX_IID);
273
274         doReturn(true).when(asyncResponse).resume(responseCaptor.capture());
275         dataService.dataGET("example-jukebox:jukebox", uriInfo, asyncResponse);
276         final var response = responseCaptor.getValue();
277
278         assertEquals(200, response.getStatus());
279
280         // response must contain only operational data
281         final var data = assertInstanceOf(ContainerNode.class, ((NormalizedNodePayload) response.getEntity()).data());
282
283         // state data present
284         assertNotNull(data.childByArg(CONT_PLAYER.name()));
285         assertNotNull(data.childByArg(PLAYLIST_NID));
286
287         // config data absent
288         assertNull(data.childByArg(LIBRARY_NID));
289     }
290
291     @Test
292     public void testPutData() {
293         doReturn(immediateTrueFluentFuture()).when(read)
294                 .exists(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
295         doNothing().when(readWrite).put(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID, EMPTY_JUKEBOX);
296
297         doReturn(true).when(asyncResponse).resume(responseCaptor.capture());
298         dataService.putDataJSON("example-jukebox:jukebox", uriInfo, stringInputStream("""
299             {
300               "example-jukebox:jukebox" : {
301                  "player": {
302                    "gap": "0.2"
303                  }
304               }
305             }"""), asyncResponse);
306         final var response = responseCaptor.getValue();
307         assertEquals(Response.Status.NO_CONTENT.getStatusCode(), response.getStatus());
308     }
309
310     @Test
311     public void testPutDataWithMountPoint() {
312         doReturn(immediateTrueFluentFuture()).when(read)
313                 .exists(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
314         doNothing().when(readWrite).put(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID, EMPTY_JUKEBOX);
315
316         doReturn(true).when(asyncResponse).resume(responseCaptor.capture());
317         dataService.putDataXML("example-jukebox:jukebox/yang-ext:mount/example-jukebox:jukebox",
318             uriInfo, stringInputStream("""
319                 <jukebox xmlns="http://example.com/ns/example-jukebox">
320                   <player>
321                     <gap>0.2</gap>
322                   </player>
323                 </jukebox>"""), asyncResponse);
324         final var response = responseCaptor.getValue();
325         assertEquals(Response.Status.NO_CONTENT.getStatusCode(), response.getStatus());
326     }
327
328     private static InputStream stringInputStream(final String str) {
329         return new ByteArrayInputStream(str.getBytes(StandardCharsets.UTF_8));
330     }
331
332     @Test
333     public void testPostData() {
334         doReturn(new MultivaluedHashMap<>()).when(uriInfo).getQueryParameters();
335         doReturn(immediateFalseFluentFuture()).when(readWrite).exists(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
336         doNothing().when(readWrite).put(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID,
337             Builders.containerBuilder().withNodeIdentifier(new NodeIdentifier(JUKEBOX_QNAME)).build());
338         doReturn(UriBuilder.fromUri("http://localhost:8181/rests/")).when(uriInfo).getBaseUriBuilder();
339
340         final var captor = ArgumentCaptor.forClass(Response.class);
341         doReturn(true).when(asyncResponse).resume(captor.capture());
342         dataService.postDataJSON(stringInputStream("""
343             {
344               "example-jukebox:jukebox" : {
345               }
346             }"""), uriInfo, asyncResponse);
347         final var response = captor.getValue();
348         assertEquals(201, response.getStatus());
349         assertEquals(URI.create("http://localhost:8181/rests/data/example-jukebox:jukebox"), response.getLocation());
350     }
351
352     @Test
353     public void testPostMapEntryData() {
354         doReturn(new MultivaluedHashMap<>()).when(uriInfo).getQueryParameters();
355         final var node = PLAYLIST_IID.node(BAND_ENTRY.name());
356         doReturn(immediateFalseFluentFuture()).when(readWrite).exists(LogicalDatastoreType.CONFIGURATION, node);
357         doNothing().when(readWrite).put(LogicalDatastoreType.CONFIGURATION, node, BAND_ENTRY);
358         doReturn(UriBuilder.fromUri("http://localhost:8181/rests/")).when(uriInfo).getBaseUriBuilder();
359
360         final var captor = ArgumentCaptor.forClass(Response.class);
361         doReturn(true).when(asyncResponse).resume(captor.capture());
362         dataService.postDataJSON("example-jukebox:jukebox", stringInputStream("""
363             {
364               "example-jukebox:playlist" : {
365                 "name" : "name of band",
366                 "description" : "band description"
367               }
368             }"""), uriInfo, asyncResponse);
369         final var response = captor.getValue();
370         assertEquals(201, response.getStatus());
371         assertEquals(URI.create("http://localhost:8181/rests/data/example-jukebox:jukebox/playlist=name%20of%20band"),
372             response.getLocation());
373     }
374
375     @Test
376     public void testDeleteData() {
377         doNothing().when(readWrite).delete(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
378         doReturn(immediateTrueFluentFuture())
379                 .when(readWrite).exists(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
380         final var captor = ArgumentCaptor.forClass(Response.class);
381         doReturn(true).when(asyncResponse).resume(captor.capture());
382         dataService.deleteData("example-jukebox:jukebox", asyncResponse);
383
384         assertEquals(204, captor.getValue().getStatus());
385     }
386
387     @Test
388     public void testDeleteDataNotExisting() {
389         doReturn(immediateFalseFluentFuture())
390                 .when(readWrite).exists(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
391         final var captor = ArgumentCaptor.forClass(RestconfDocumentedException.class);
392         doReturn(true).when(asyncResponse).resume(captor.capture());
393         dataService.deleteData("example-jukebox:jukebox", asyncResponse);
394
395         final var errors = captor.getValue().getErrors();
396         assertEquals(1, errors.size());
397         final var error = errors.get(0);
398         assertEquals(ErrorType.PROTOCOL, error.getErrorType());
399         assertEquals(ErrorTag.DATA_MISSING, error.getErrorTag());
400     }
401
402     /**
403      * Test of deleting data on mount point.
404      */
405     @Test
406     public void testDeleteDataMountPoint() {
407         doNothing().when(readWrite).delete(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
408         doReturn(immediateTrueFluentFuture())
409                 .when(readWrite).exists(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
410         final var captor = ArgumentCaptor.forClass(Response.class);
411         doReturn(true).when(asyncResponse).resume(captor.capture());
412         dataService.deleteData("example-jukebox:jukebox/yang-ext:mount/example-jukebox:jukebox", asyncResponse);
413
414         assertEquals(204, captor.getValue().getStatus());
415     }
416
417     @Test
418     public void testPatchData() {
419         final var patch = new PatchContext("test patch id", List.of(
420             new PatchEntity("create data", Operation.Create, JUKEBOX_IID, EMPTY_JUKEBOX),
421             new PatchEntity("replace data", Operation.Replace, JUKEBOX_IID, EMPTY_JUKEBOX),
422             new PatchEntity("delete data", Operation.Delete, GAP_IID)));
423
424         doNothing().when(readWrite).delete(LogicalDatastoreType.CONFIGURATION, GAP_IID);
425         doReturn(immediateFalseFluentFuture())
426                 .when(readWrite).exists(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
427         doReturn(immediateTrueFluentFuture())
428                 .when(readWrite).exists(LogicalDatastoreType.CONFIGURATION, GAP_IID);
429         doReturn(true).when(asyncResponse).resume(responseCaptor.capture());
430         dataService.yangPatchData(JUKEBOX_SCHEMA, patch, null, asyncResponse);
431         final var response = responseCaptor.getValue();
432         assertEquals(200, response.getStatus());
433         final var status = assertInstanceOf(PatchStatusContext.class, response.getEntity());
434
435         assertTrue(status.ok());
436         assertEquals(3, status.editCollection().size());
437         assertEquals("replace data", status.editCollection().get(1).getEditId());
438     }
439
440     @Test
441     public void testPatchDataMountPoint() throws Exception {
442         final var patch = new PatchContext("test patch id", List.of(
443             new PatchEntity("create data", Operation.Create, JUKEBOX_IID, EMPTY_JUKEBOX),
444             new PatchEntity("replace data", Operation.Replace, JUKEBOX_IID, EMPTY_JUKEBOX),
445             new PatchEntity("delete data", Operation.Delete, GAP_IID)));
446
447         doNothing().when(readWrite).delete(LogicalDatastoreType.CONFIGURATION, GAP_IID);
448         doReturn(immediateFalseFluentFuture())
449                 .when(readWrite).exists(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
450         doReturn(immediateTrueFluentFuture()).when(readWrite).exists(LogicalDatastoreType.CONFIGURATION, GAP_IID);
451
452         doReturn(true).when(asyncResponse).resume(responseCaptor.capture());
453         dataService.yangPatchData(JUKEBOX_SCHEMA, patch, mountPoint, asyncResponse);
454         final var response = responseCaptor.getValue();
455         assertEquals(200, response.getStatus());
456         final var status = assertInstanceOf(PatchStatusContext.class, response.getEntity());
457
458         assertTrue(status.ok());
459         assertEquals(3, status.editCollection().size());
460         assertNull(status.globalErrors());
461     }
462
463     @Test
464     public void testPatchDataDeleteNotExist() {
465         final var patch = new PatchContext("test patch id", List.of(
466             new PatchEntity("create data", Operation.Create, JUKEBOX_IID, EMPTY_JUKEBOX),
467             new PatchEntity("remove data", Operation.Remove, GAP_IID),
468             new PatchEntity("delete data", Operation.Delete, GAP_IID)));
469
470         doNothing().when(readWrite).delete(LogicalDatastoreType.CONFIGURATION, GAP_IID);
471         doReturn(immediateFalseFluentFuture())
472                 .when(readWrite).exists(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
473         doReturn(immediateFalseFluentFuture())
474                 .when(readWrite).exists(LogicalDatastoreType.CONFIGURATION, GAP_IID);
475         doReturn(true).when(readWrite).cancel();
476
477         doReturn(true).when(asyncResponse).resume(responseCaptor.capture());
478         dataService.yangPatchData(JUKEBOX_SCHEMA, patch, null, asyncResponse);
479         final var response = responseCaptor.getValue();
480         assertEquals(409, response.getStatus());
481         final var status = assertInstanceOf(PatchStatusContext.class, response.getEntity());
482
483         assertFalse(status.ok());
484         assertEquals(3, status.editCollection().size());
485         assertTrue(status.editCollection().get(0).isOk());
486         assertTrue(status.editCollection().get(1).isOk());
487         assertFalse(status.editCollection().get(2).isOk());
488         assertFalse(status.editCollection().get(2).getEditErrors().isEmpty());
489         final String errorMessage = status.editCollection().get(2).getEditErrors().get(0).getErrorMessage();
490         assertEquals("Data does not exist", errorMessage);
491     }
492 }