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.assertNull;
14 import static org.junit.jupiter.api.Assertions.assertTrue;
15 import static org.mockito.ArgumentMatchers.any;
16 import static org.mockito.Mockito.doNothing;
17 import static org.mockito.Mockito.doReturn;
18 import static org.mockito.Mockito.mock;
19 import static org.opendaylight.yangtools.util.concurrent.FluentFutures.immediateFalseFluentFuture;
20 import static org.opendaylight.yangtools.util.concurrent.FluentFutures.immediateTrueFluentFuture;
22 import java.io.ByteArrayInputStream;
23 import java.io.InputStream;
25 import java.nio.charset.StandardCharsets;
26 import java.util.List;
27 import java.util.Optional;
29 import javax.ws.rs.container.AsyncResponse;
30 import javax.ws.rs.core.MultivaluedHashMap;
31 import javax.ws.rs.core.MultivaluedMap;
32 import javax.ws.rs.core.Response;
33 import javax.ws.rs.core.UriBuilder;
34 import javax.ws.rs.core.UriInfo;
35 import org.junit.Before;
36 import org.junit.Test;
37 import org.junit.runner.RunWith;
38 import org.mockito.ArgumentCaptor;
39 import org.mockito.Captor;
40 import org.mockito.Mock;
41 import org.mockito.junit.MockitoJUnitRunner;
42 import org.opendaylight.mdsal.common.api.CommitInfo;
43 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
44 import org.opendaylight.mdsal.dom.api.DOMActionService;
45 import org.opendaylight.mdsal.dom.api.DOMDataBroker;
46 import org.opendaylight.mdsal.dom.api.DOMDataTreeReadTransaction;
47 import org.opendaylight.mdsal.dom.api.DOMDataTreeReadWriteTransaction;
48 import org.opendaylight.mdsal.dom.api.DOMMountPoint;
49 import org.opendaylight.mdsal.dom.api.DOMMountPointService;
50 import org.opendaylight.mdsal.dom.api.DOMRpcService;
51 import org.opendaylight.mdsal.dom.api.DOMSchemaService;
52 import org.opendaylight.mdsal.dom.spi.FixedDOMSchemaService;
53 import org.opendaylight.netconf.dom.api.NetconfDataTreeService;
54 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
55 import org.opendaylight.restconf.common.patch.PatchContext;
56 import org.opendaylight.restconf.common.patch.PatchEntity;
57 import org.opendaylight.restconf.common.patch.PatchStatusContext;
58 import org.opendaylight.restconf.nb.rfc8040.AbstractJukeboxTest;
59 import org.opendaylight.restconf.nb.rfc8040.databind.DatabindContext;
60 import org.opendaylight.restconf.nb.rfc8040.databind.DatabindProvider;
61 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.patch.rev170222.yang.patch.yang.patch.Edit.Operation;
62 import org.opendaylight.yangtools.yang.common.ErrorTag;
63 import org.opendaylight.yangtools.yang.common.ErrorType;
64 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
65 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
66 import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
68 @RunWith(MockitoJUnitRunner.StrictStubs.class)
69 public class RestconfDataServiceImplTest extends AbstractJukeboxTest {
71 private UriInfo uriInfo;
73 private DOMDataTreeReadWriteTransaction readWrite;
75 private DOMDataTreeReadTransaction read;
77 private DOMMountPointService mountPointService;
79 private DOMMountPoint mountPoint;
81 private DOMDataBroker mountDataBroker;
83 private NetconfDataTreeService netconfService;
85 private DOMActionService actionService;
87 private DOMRpcService rpcService;
89 private MultivaluedMap<String, String> queryParamenters;
91 private AsyncResponse asyncResponse;
93 private ArgumentCaptor<Response> responseCaptor;
95 private RestconfDataServiceImpl dataService;
98 public void setUp() throws Exception {
99 doReturn(Set.of()).when(queryParamenters).entrySet();
100 doReturn(queryParamenters).when(uriInfo).getQueryParameters();
102 doReturn(CommitInfo.emptyFluentFuture()).when(readWrite).commit();
104 final var dataBroker = mock(DOMDataBroker.class);
105 doReturn(read).when(dataBroker).newReadOnlyTransaction();
106 doReturn(readWrite).when(dataBroker).newReadWriteTransaction();
108 final DatabindProvider databindProvider = () -> DatabindContext.ofModel(JUKEBOX_SCHEMA);
109 dataService = new RestconfDataServiceImpl(databindProvider,
110 new MdsalRestconfServer(databindProvider, dataBroker, rpcService, actionService, mountPointService));
111 doReturn(Optional.of(mountPoint)).when(mountPointService)
112 .getMountPoint(any(YangInstanceIdentifier.class));
113 doReturn(Optional.of(FixedDOMSchemaService.of(JUKEBOX_SCHEMA))).when(mountPoint)
114 .getService(DOMSchemaService.class);
115 doReturn(Optional.of(mountDataBroker)).when(mountPoint).getService(DOMDataBroker.class);
116 doReturn(Optional.of(rpcService)).when(mountPoint).getService(DOMRpcService.class);
117 doReturn(Optional.empty()).when(mountPoint).getService(NetconfDataTreeService.class);
118 doReturn(read).when(mountDataBroker).newReadOnlyTransaction();
119 doReturn(readWrite).when(mountDataBroker).newReadWriteTransaction();
123 public void testPutData() {
124 doReturn(immediateTrueFluentFuture()).when(read)
125 .exists(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
126 doNothing().when(readWrite).put(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID, EMPTY_JUKEBOX);
128 doReturn(true).when(asyncResponse).resume(responseCaptor.capture());
129 dataService.putDataJSON("example-jukebox:jukebox", uriInfo, stringInputStream("""
131 "example-jukebox:jukebox" : {
136 }"""), asyncResponse);
137 final var response = responseCaptor.getValue();
138 assertEquals(Response.Status.NO_CONTENT.getStatusCode(), response.getStatus());
142 public void testPutDataWithMountPoint() {
143 doReturn(immediateTrueFluentFuture()).when(read)
144 .exists(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
145 doNothing().when(readWrite).put(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID, EMPTY_JUKEBOX);
147 doReturn(true).when(asyncResponse).resume(responseCaptor.capture());
148 dataService.putDataXML("example-jukebox:jukebox/yang-ext:mount/example-jukebox:jukebox",
149 uriInfo, stringInputStream("""
150 <jukebox xmlns="http://example.com/ns/example-jukebox">
154 </jukebox>"""), asyncResponse);
155 final var response = responseCaptor.getValue();
156 assertEquals(Response.Status.NO_CONTENT.getStatusCode(), response.getStatus());
159 private static InputStream stringInputStream(final String str) {
160 return new ByteArrayInputStream(str.getBytes(StandardCharsets.UTF_8));
164 public void testPostData() {
165 doReturn(new MultivaluedHashMap<>()).when(uriInfo).getQueryParameters();
166 doReturn(immediateFalseFluentFuture()).when(readWrite).exists(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
167 doNothing().when(readWrite).put(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID,
168 Builders.containerBuilder().withNodeIdentifier(new NodeIdentifier(JUKEBOX_QNAME)).build());
169 doReturn(UriBuilder.fromUri("http://localhost:8181/rests/")).when(uriInfo).getBaseUriBuilder();
171 final var captor = ArgumentCaptor.forClass(Response.class);
172 doReturn(true).when(asyncResponse).resume(captor.capture());
173 dataService.postDataJSON(stringInputStream("""
175 "example-jukebox:jukebox" : {
177 }"""), uriInfo, asyncResponse);
178 final var response = captor.getValue();
179 assertEquals(201, response.getStatus());
180 assertEquals(URI.create("http://localhost:8181/rests/data/example-jukebox:jukebox"), response.getLocation());
184 public void testPostMapEntryData() {
185 doReturn(new MultivaluedHashMap<>()).when(uriInfo).getQueryParameters();
186 final var node = PLAYLIST_IID.node(BAND_ENTRY.name());
187 doReturn(immediateFalseFluentFuture()).when(readWrite).exists(LogicalDatastoreType.CONFIGURATION, node);
188 doNothing().when(readWrite).put(LogicalDatastoreType.CONFIGURATION, node, BAND_ENTRY);
189 doReturn(UriBuilder.fromUri("http://localhost:8181/rests/")).when(uriInfo).getBaseUriBuilder();
191 final var captor = ArgumentCaptor.forClass(Response.class);
192 doReturn(true).when(asyncResponse).resume(captor.capture());
193 dataService.postDataJSON("example-jukebox:jukebox", stringInputStream("""
195 "example-jukebox:playlist" : {
196 "name" : "name of band",
197 "description" : "band description"
199 }"""), uriInfo, asyncResponse);
200 final var response = captor.getValue();
201 assertEquals(201, response.getStatus());
202 assertEquals(URI.create("http://localhost:8181/rests/data/example-jukebox:jukebox/playlist=name%20of%20band"),
203 response.getLocation());
207 public void testDeleteData() {
208 doNothing().when(readWrite).delete(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
209 doReturn(immediateTrueFluentFuture())
210 .when(readWrite).exists(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
211 final var captor = ArgumentCaptor.forClass(Response.class);
212 doReturn(true).when(asyncResponse).resume(captor.capture());
213 dataService.deleteData("example-jukebox:jukebox", asyncResponse);
215 assertEquals(204, captor.getValue().getStatus());
219 public void testDeleteDataNotExisting() {
220 doReturn(immediateFalseFluentFuture())
221 .when(readWrite).exists(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
222 final var captor = ArgumentCaptor.forClass(RestconfDocumentedException.class);
223 doReturn(true).when(asyncResponse).resume(captor.capture());
224 dataService.deleteData("example-jukebox:jukebox", asyncResponse);
226 final var errors = captor.getValue().getErrors();
227 assertEquals(1, errors.size());
228 final var error = errors.get(0);
229 assertEquals(ErrorType.PROTOCOL, error.getErrorType());
230 assertEquals(ErrorTag.DATA_MISSING, error.getErrorTag());
234 * Test of deleting data on mount point.
237 public void testDeleteDataMountPoint() {
238 doNothing().when(readWrite).delete(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
239 doReturn(immediateTrueFluentFuture())
240 .when(readWrite).exists(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
241 final var captor = ArgumentCaptor.forClass(Response.class);
242 doReturn(true).when(asyncResponse).resume(captor.capture());
243 dataService.deleteData("example-jukebox:jukebox/yang-ext:mount/example-jukebox:jukebox", asyncResponse);
245 assertEquals(204, captor.getValue().getStatus());
249 public void testPatchData() {
250 final var patch = new PatchContext("test patch id", List.of(
251 new PatchEntity("create data", Operation.Create, JUKEBOX_IID, EMPTY_JUKEBOX),
252 new PatchEntity("replace data", Operation.Replace, JUKEBOX_IID, EMPTY_JUKEBOX),
253 new PatchEntity("delete data", Operation.Delete, GAP_IID)));
255 doNothing().when(readWrite).delete(LogicalDatastoreType.CONFIGURATION, GAP_IID);
256 doReturn(immediateFalseFluentFuture())
257 .when(readWrite).exists(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
258 doReturn(immediateTrueFluentFuture())
259 .when(readWrite).exists(LogicalDatastoreType.CONFIGURATION, GAP_IID);
260 doReturn(true).when(asyncResponse).resume(responseCaptor.capture());
261 dataService.yangPatchData(JUKEBOX_SCHEMA, patch, null, asyncResponse);
262 final var response = responseCaptor.getValue();
263 assertEquals(200, response.getStatus());
264 final var status = assertInstanceOf(PatchStatusContext.class, response.getEntity());
266 assertTrue(status.ok());
267 assertEquals(3, status.editCollection().size());
268 assertEquals("replace data", status.editCollection().get(1).getEditId());
272 public void testPatchDataMountPoint() throws Exception {
273 final var patch = new PatchContext("test patch id", List.of(
274 new PatchEntity("create data", Operation.Create, JUKEBOX_IID, EMPTY_JUKEBOX),
275 new PatchEntity("replace data", Operation.Replace, JUKEBOX_IID, EMPTY_JUKEBOX),
276 new PatchEntity("delete data", Operation.Delete, GAP_IID)));
278 doNothing().when(readWrite).delete(LogicalDatastoreType.CONFIGURATION, GAP_IID);
279 doReturn(immediateFalseFluentFuture())
280 .when(readWrite).exists(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
281 doReturn(immediateTrueFluentFuture()).when(readWrite).exists(LogicalDatastoreType.CONFIGURATION, GAP_IID);
283 doReturn(true).when(asyncResponse).resume(responseCaptor.capture());
284 dataService.yangPatchData(JUKEBOX_SCHEMA, patch, mountPoint, asyncResponse);
285 final var response = responseCaptor.getValue();
286 assertEquals(200, response.getStatus());
287 final var status = assertInstanceOf(PatchStatusContext.class, response.getEntity());
289 assertTrue(status.ok());
290 assertEquals(3, status.editCollection().size());
291 assertNull(status.globalErrors());
295 public void testPatchDataDeleteNotExist() {
296 final var patch = new PatchContext("test patch id", List.of(
297 new PatchEntity("create data", Operation.Create, JUKEBOX_IID, EMPTY_JUKEBOX),
298 new PatchEntity("remove data", Operation.Remove, GAP_IID),
299 new PatchEntity("delete data", Operation.Delete, GAP_IID)));
301 doNothing().when(readWrite).delete(LogicalDatastoreType.CONFIGURATION, GAP_IID);
302 doReturn(immediateFalseFluentFuture())
303 .when(readWrite).exists(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
304 doReturn(immediateFalseFluentFuture())
305 .when(readWrite).exists(LogicalDatastoreType.CONFIGURATION, GAP_IID);
306 doReturn(true).when(readWrite).cancel();
308 doReturn(true).when(asyncResponse).resume(responseCaptor.capture());
309 dataService.yangPatchData(JUKEBOX_SCHEMA, patch, null, asyncResponse);
310 final var response = responseCaptor.getValue();
311 assertEquals(409, response.getStatus());
312 final var status = assertInstanceOf(PatchStatusContext.class, response.getEntity());
314 assertFalse(status.ok());
315 assertEquals(3, status.editCollection().size());
316 assertTrue(status.editCollection().get(0).isOk());
317 assertTrue(status.editCollection().get(1).isOk());
318 assertFalse(status.editCollection().get(2).isOk());
319 assertFalse(status.editCollection().get(2).getEditErrors().isEmpty());
320 final String errorMessage = status.editCollection().get(2).getEditErrors().get(0).getErrorMessage();
321 assertEquals("Data does not exist", errorMessage);