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