74d187a990a300e9fe068f7890afc89430563420
[netconf.git] /
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.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;
21
22 import java.io.ByteArrayInputStream;
23 import java.io.InputStream;
24 import java.net.URI;
25 import java.nio.charset.StandardCharsets;
26 import java.util.List;
27 import java.util.Optional;
28 import java.util.Set;
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;
67
68 @RunWith(MockitoJUnitRunner.StrictStubs.class)
69 public class RestconfDataServiceImplTest extends AbstractJukeboxTest {
70     @Mock
71     private UriInfo uriInfo;
72     @Mock
73     private DOMDataTreeReadWriteTransaction readWrite;
74     @Mock
75     private DOMDataTreeReadTransaction read;
76     @Mock
77     private DOMMountPointService mountPointService;
78     @Mock
79     private DOMMountPoint mountPoint;
80     @Mock
81     private DOMDataBroker mountDataBroker;
82     @Mock
83     private NetconfDataTreeService netconfService;
84     @Mock
85     private DOMActionService actionService;
86     @Mock
87     private DOMRpcService rpcService;
88     @Mock
89     private MultivaluedMap<String, String> queryParamenters;
90     @Mock
91     private AsyncResponse asyncResponse;
92     @Captor
93     private ArgumentCaptor<Response> responseCaptor;
94
95     private RestconfDataServiceImpl dataService;
96
97     @Before
98     public void setUp() throws Exception {
99         doReturn(Set.of()).when(queryParamenters).entrySet();
100         doReturn(queryParamenters).when(uriInfo).getQueryParameters();
101
102         doReturn(CommitInfo.emptyFluentFuture()).when(readWrite).commit();
103
104         final var dataBroker = mock(DOMDataBroker.class);
105         doReturn(read).when(dataBroker).newReadOnlyTransaction();
106         doReturn(readWrite).when(dataBroker).newReadWriteTransaction();
107
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();
120     }
121
122     @Test
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);
127
128         doReturn(true).when(asyncResponse).resume(responseCaptor.capture());
129         dataService.putDataJSON("example-jukebox:jukebox", uriInfo, stringInputStream("""
130             {
131               "example-jukebox:jukebox" : {
132                  "player": {
133                    "gap": "0.2"
134                  }
135               }
136             }"""), asyncResponse);
137         final var response = responseCaptor.getValue();
138         assertEquals(Response.Status.NO_CONTENT.getStatusCode(), response.getStatus());
139     }
140
141     @Test
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);
146
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">
151                   <player>
152                     <gap>0.2</gap>
153                   </player>
154                 </jukebox>"""), asyncResponse);
155         final var response = responseCaptor.getValue();
156         assertEquals(Response.Status.NO_CONTENT.getStatusCode(), response.getStatus());
157     }
158
159     private static InputStream stringInputStream(final String str) {
160         return new ByteArrayInputStream(str.getBytes(StandardCharsets.UTF_8));
161     }
162
163     @Test
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();
170
171         final var captor = ArgumentCaptor.forClass(Response.class);
172         doReturn(true).when(asyncResponse).resume(captor.capture());
173         dataService.postDataJSON(stringInputStream("""
174             {
175               "example-jukebox:jukebox" : {
176               }
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());
181     }
182
183     @Test
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();
190
191         final var captor = ArgumentCaptor.forClass(Response.class);
192         doReturn(true).when(asyncResponse).resume(captor.capture());
193         dataService.postDataJSON("example-jukebox:jukebox", stringInputStream("""
194             {
195               "example-jukebox:playlist" : {
196                 "name" : "name of band",
197                 "description" : "band description"
198               }
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());
204     }
205
206     @Test
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);
214
215         assertEquals(204, captor.getValue().getStatus());
216     }
217
218     @Test
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);
225
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());
231     }
232
233     /**
234      * Test of deleting data on mount point.
235      */
236     @Test
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);
244
245         assertEquals(204, captor.getValue().getStatus());
246     }
247
248     @Test
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)));
254
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());
265
266         assertTrue(status.ok());
267         assertEquals(3, status.editCollection().size());
268         assertEquals("replace data", status.editCollection().get(1).getEditId());
269     }
270
271     @Test
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)));
277
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);
282
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());
288
289         assertTrue(status.ok());
290         assertEquals(3, status.editCollection().size());
291         assertNull(status.globalErrors());
292     }
293
294     @Test
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)));
300
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();
307
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());
313
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);
322     }
323 }