2 * Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved.
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
8 package org.opendaylight.restconf.nb.rfc8040.rests.services.impl;
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;
24 import java.io.ByteArrayInputStream;
25 import java.io.InputStream;
27 import java.nio.charset.StandardCharsets;
28 import java.util.List;
29 import java.util.Optional;
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;
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);
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())
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())
93 private UriInfo uriInfo;
95 private DOMDataTreeReadWriteTransaction readWrite;
97 private DOMDataTreeReadTransaction read;
99 private DOMMountPointService mountPointService;
101 private DOMMountPoint mountPoint;
103 private DOMDataBroker mountDataBroker;
105 private NetconfDataTreeService netconfService;
107 private DOMActionService actionService;
109 private DOMRpcService rpcService;
111 private MultivaluedMap<String, String> queryParamenters;
113 private AsyncResponse asyncResponse;
115 private ArgumentCaptor<Response> responseCaptor;
117 private RestconfDataServiceImpl dataService;
120 public void setUp() throws Exception {
121 doReturn(Set.of()).when(queryParamenters).entrySet();
122 doReturn(queryParamenters).when(uriInfo).getQueryParameters();
124 doReturn(CommitInfo.emptyFluentFuture()).when(readWrite).commit();
126 final var dataBroker = mock(DOMDataBroker.class);
127 doReturn(read).when(dataBroker).newReadOnlyTransaction();
128 doReturn(readWrite).when(dataBroker).newReadWriteTransaction();
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();
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);
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());
160 public void testReadRootData() {
161 doReturn(new MultivaluedHashMap<>()).when(uriInfo).getQueryParameters();
162 doReturn(immediateFluentFuture(Optional.of(wrapNodeByDataRootContainer(CONFIG_JUKEBOX))))
164 .read(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.of());
165 doReturn(immediateFluentFuture(Optional.of(wrapNodeByDataRootContainer(OPER_JUKEBOX))))
167 .read(LogicalDatastoreType.OPERATIONAL, YangInstanceIdentifier.of());
169 doReturn(true).when(asyncResponse).resume(responseCaptor.capture());
170 dataService.dataGET(uriInfo, asyncResponse);
171 final var response = responseCaptor.getValue();
172 assertEquals(200, response.getStatus());
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());
181 private static ContainerNode wrapNodeByDataRootContainer(final DataContainerChild data) {
182 return Builders.containerBuilder()
183 .withNodeIdentifier(NodeIdentifier.create(SchemaContext.NAME))
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.
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);
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());
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));
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);
221 final var rdeCaptor = ArgumentCaptor.forClass(RestconfDocumentedException.class);
222 doReturn(true).when(asyncResponse).resume(rdeCaptor.capture());
223 dataService.dataGET("example-jukebox:jukebox", uriInfo, asyncResponse);
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());
235 * Read data from config datastore according to content parameter.
238 public void testReadDataConfigTest() {
239 final MultivaluedHashMap<String, String> parameters = new MultivaluedHashMap<>();
240 parameters.put("content", List.of("config"));
242 doReturn(parameters).when(uriInfo).getQueryParameters();
243 doReturn(immediateFluentFuture(Optional.of(CONFIG_JUKEBOX))).when(read)
244 .read(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
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());
251 // response must contain only config data
252 final var data = assertInstanceOf(ContainerNode.class, ((NormalizedNodePayload) response.getEntity()).data());
254 // config data present
255 assertNotNull(data.childByArg(CONT_PLAYER.name()));
256 assertNotNull(data.childByArg(LIBRARY_NID));
259 assertNull(data.childByArg(PLAYLIST_NID));
263 * Read data from operational datastore according to content parameter.
266 public void testReadDataOperationalTest() {
267 final MultivaluedHashMap<String, String> parameters = new MultivaluedHashMap<>();
268 parameters.put("content", List.of("nonconfig"));
270 doReturn(parameters).when(uriInfo).getQueryParameters();
271 doReturn(immediateFluentFuture(Optional.of(OPER_JUKEBOX))).when(read)
272 .read(LogicalDatastoreType.OPERATIONAL, JUKEBOX_IID);
274 doReturn(true).when(asyncResponse).resume(responseCaptor.capture());
275 dataService.dataGET("example-jukebox:jukebox", uriInfo, asyncResponse);
276 final var response = responseCaptor.getValue();
278 assertEquals(200, response.getStatus());
280 // response must contain only operational data
281 final var data = assertInstanceOf(ContainerNode.class, ((NormalizedNodePayload) response.getEntity()).data());
283 // state data present
284 assertNotNull(data.childByArg(CONT_PLAYER.name()));
285 assertNotNull(data.childByArg(PLAYLIST_NID));
287 // config data absent
288 assertNull(data.childByArg(LIBRARY_NID));
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);
297 doReturn(true).when(asyncResponse).resume(responseCaptor.capture());
298 dataService.putDataJSON("example-jukebox:jukebox", uriInfo, stringInputStream("""
300 "example-jukebox:jukebox" : {
305 }"""), asyncResponse);
306 final var response = responseCaptor.getValue();
307 assertEquals(Response.Status.NO_CONTENT.getStatusCode(), response.getStatus());
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);
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">
323 </jukebox>"""), asyncResponse);
324 final var response = responseCaptor.getValue();
325 assertEquals(Response.Status.NO_CONTENT.getStatusCode(), response.getStatus());
328 private static InputStream stringInputStream(final String str) {
329 return new ByteArrayInputStream(str.getBytes(StandardCharsets.UTF_8));
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();
340 final var captor = ArgumentCaptor.forClass(Response.class);
341 doReturn(true).when(asyncResponse).resume(captor.capture());
342 dataService.postDataJSON(stringInputStream("""
344 "example-jukebox:jukebox" : {
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());
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();
360 final var captor = ArgumentCaptor.forClass(Response.class);
361 doReturn(true).when(asyncResponse).resume(captor.capture());
362 dataService.postDataJSON("example-jukebox:jukebox", stringInputStream("""
364 "example-jukebox:playlist" : {
365 "name" : "name of band",
366 "description" : "band description"
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());
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);
384 assertEquals(204, captor.getValue().getStatus());
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);
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());
403 * Test of deleting data on mount point.
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);
414 assertEquals(204, captor.getValue().getStatus());
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)));
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());
435 assertTrue(status.ok());
436 assertEquals(3, status.editCollection().size());
437 assertEquals("replace data", status.editCollection().get(1).getEditId());
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)));
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);
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());
458 assertTrue(status.ok());
459 assertEquals(3, status.editCollection().size());
460 assertNull(status.globalErrors());
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)));
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();
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());
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);