Fix POST request with insert parameter
[netconf.git] / restconf / restconf-nb / src / test / java / org / opendaylight / restconf / nb / rfc8040 / rests / transactions / NetconfRestconfStrategyTest.java
1 /*
2  * Copyright (c) 2023 PANTHEON.tech, s.r.o. 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.transactions;
9
10 import static org.junit.Assert.assertEquals;
11 import static org.junit.Assert.assertNotNull;
12 import static org.junit.Assert.assertNull;
13 import static org.mockito.ArgumentMatchers.any;
14 import static org.mockito.Mockito.doReturn;
15 import static org.mockito.Mockito.mock;
16 import static org.mockito.Mockito.spy;
17 import static org.mockito.Mockito.times;
18 import static org.mockito.Mockito.verify;
19 import static org.opendaylight.yangtools.util.concurrent.FluentFutures.immediateFailedFluentFuture;
20 import static org.opendaylight.yangtools.util.concurrent.FluentFutures.immediateFluentFuture;
21
22 import com.google.common.util.concurrent.Futures;
23 import java.text.ParseException;
24 import java.util.HashMap;
25 import java.util.Optional;
26 import org.junit.Before;
27 import org.junit.Test;
28 import org.junit.runner.RunWith;
29 import org.mockito.Mock;
30 import org.mockito.junit.MockitoJUnitRunner;
31 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
32 import org.opendaylight.mdsal.common.api.TransactionCommitFailedException;
33 import org.opendaylight.mdsal.dom.spi.DefaultDOMRpcResult;
34 import org.opendaylight.netconf.api.NetconfDocumentedException;
35 import org.opendaylight.netconf.dom.api.NetconfDataTreeService;
36 import org.opendaylight.restconf.api.ApiPath;
37 import org.opendaylight.restconf.common.patch.PatchStatusContext;
38 import org.opendaylight.restconf.common.patch.PatchStatusEntity;
39 import org.opendaylight.restconf.server.api.DatabindContext;
40 import org.opendaylight.restconf.server.api.JsonDataPostBody;
41 import org.opendaylight.restconf.server.api.JsonResourceBody;
42 import org.opendaylight.yangtools.yang.common.ErrorSeverity;
43 import org.opendaylight.yangtools.yang.common.ErrorTag;
44 import org.opendaylight.yangtools.yang.common.ErrorType;
45 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
46 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
47 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
48 import org.w3c.dom.DOMException;
49
50 @RunWith(MockitoJUnitRunner.StrictStubs.class)
51 public final class NetconfRestconfStrategyTest extends AbstractRestconfStrategyTest {
52     @Mock
53     private NetconfDataTreeService netconfService;
54
55     @Before
56     public void before() {
57         doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService).commit();
58         doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService).discardChanges();
59         doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService).unlock();
60         doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService).lock();
61         doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService)
62             .delete(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.of());
63         doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService).merge(any(), any(),
64             any(), any());
65     }
66
67     @Override
68     RestconfStrategy newStrategy(final DatabindContext databind) {
69         return new NetconfRestconfStrategy(databind, netconfService, null, null, null, null);
70     }
71
72     @Override
73     RestconfStrategy testDeleteDataStrategy() {
74         return jukeboxStrategy();
75     }
76
77     @Override
78     RestconfStrategy testNegativeDeleteDataStrategy() {
79         doReturn(Futures.immediateFailedFuture(new TransactionCommitFailedException(
80             "Commit of transaction " + this + " failed", new NetconfDocumentedException("id",
81                 ErrorType.RPC, ErrorTag.DATA_MISSING, ErrorSeverity.ERROR)))).when(netconfService).commit();
82         return jukeboxStrategy();
83     }
84
85     @Override
86     RestconfStrategy testPostContainerDataStrategy() {
87         doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService).commit();
88         doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService)
89             .create(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID, EMPTY_JUKEBOX, Optional.empty());
90         return jukeboxStrategy();
91     }
92
93     @Override
94     RestconfStrategy testPostListDataStrategy(final MapEntryNode entryNode, final YangInstanceIdentifier node) {
95         doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService)
96             // FIXME: exact match
97             .merge(any(), any(), any(), any());
98         doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService).commit();
99         doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService).create(
100             LogicalDatastoreType.CONFIGURATION, node, entryNode, Optional.empty());
101         return jukeboxStrategy();
102     }
103
104     @Override
105     RestconfStrategy testPostDataFailStrategy(final DOMException domException) {
106         doReturn(immediateFailedFluentFuture(domException)).when(netconfService)
107             // FIXME: exact match
108             .create(any(), any(), any(), any());
109         doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService).discardChanges();
110         doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService).unlock();
111         return jukeboxStrategy();
112     }
113
114     @Override
115     RestconfStrategy testPatchContainerDataStrategy() {
116         doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService).merge(any(), any(),any(),
117             any());
118         return jukeboxStrategy();
119     }
120
121     @Override
122     RestconfStrategy testPatchLeafDataStrategy() {
123         doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService)
124             .merge(any(), any(), any(), any());
125         doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService).commit();
126         return jukeboxStrategy();
127     }
128
129     @Override
130     RestconfStrategy testPatchListDataStrategy() {
131         doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService).commit();
132         doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService)
133             .merge(any(), any(),any(),any());
134         return jukeboxStrategy();
135     }
136
137     @Test
138     public void testPutCreateContainerData() {
139         doReturn(immediateFluentFuture(Optional.empty())).when(netconfService).getConfig(JUKEBOX_IID);
140         doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService).commit();
141         doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService)
142             .replace(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID, EMPTY_JUKEBOX, Optional.empty());
143
144         jukeboxStrategy().putData(JUKEBOX_IID, EMPTY_JUKEBOX, null);
145         verify(netconfService).lock();
146         verify(netconfService).getConfig(JUKEBOX_IID);
147         verify(netconfService).replace(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID, EMPTY_JUKEBOX,
148             Optional.empty());
149     }
150
151     @Test
152     public void testPutReplaceContainerData() {
153         doReturn(immediateFluentFuture(Optional.of(mock(ContainerNode.class)))).when(netconfService)
154             .getConfig(JUKEBOX_IID);
155         doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService).commit();
156         doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService)
157             .replace(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID, EMPTY_JUKEBOX, Optional.empty());
158
159         jukeboxStrategy().putData(JUKEBOX_IID, EMPTY_JUKEBOX, null);
160         verify(netconfService).getConfig(JUKEBOX_IID);
161         verify(netconfService).replace(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID, EMPTY_JUKEBOX,
162             Optional.empty());
163     }
164
165     @Test
166     public void testPutCreateLeafData() {
167         doReturn(immediateFluentFuture(Optional.empty())).when(netconfService).getConfig(GAP_IID);
168         doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService).commit();
169         doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService)
170             .replace(LogicalDatastoreType.CONFIGURATION, GAP_IID, GAP_LEAF, Optional.empty());
171
172         jukeboxStrategy().putData(GAP_IID, GAP_LEAF, null);
173         verify(netconfService).getConfig(GAP_IID);
174         verify(netconfService).replace(LogicalDatastoreType.CONFIGURATION, GAP_IID, GAP_LEAF, Optional.empty());
175     }
176
177     @Test
178     public void testPutReplaceLeafData() {
179         doReturn(immediateFluentFuture(Optional.of(mock(ContainerNode.class)))).when(netconfService)
180             .getConfig(GAP_IID);
181         doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService).commit();
182         doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService)
183             .replace(LogicalDatastoreType.CONFIGURATION, GAP_IID, GAP_LEAF, Optional.empty());
184
185         jukeboxStrategy().putData(GAP_IID, GAP_LEAF, null);
186         verify(netconfService).getConfig(GAP_IID);
187         verify(netconfService).replace(LogicalDatastoreType.CONFIGURATION, GAP_IID, GAP_LEAF, Optional.empty());
188     }
189
190     @Test
191     public void testPutCreateListData() {
192         doReturn(immediateFluentFuture(Optional.empty())).when(netconfService).getConfig(JUKEBOX_IID);
193         doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService).commit();
194         doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService)
195             .replace(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID, JUKEBOX_WITH_BANDS, Optional.empty());
196
197         jukeboxStrategy().putData(JUKEBOX_IID, JUKEBOX_WITH_BANDS, null);
198         verify(netconfService).getConfig(JUKEBOX_IID);
199         verify(netconfService).replace(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID, JUKEBOX_WITH_BANDS,
200             Optional.empty());
201     }
202
203     /**
204      * Test for put with insert=after option for last element of the list. Here we're trying to insert new element of
205      * the ordered list after last existing element. Test uses list with two items and add third one at the end. After
206      * this we check how many times replace transaction was called to know how many items was inserted.
207      * @throws ParseException if ApiPath string cannot be parsed
208      */
209     @Test
210     public void testPutDataWithInsertAfterLast() throws ParseException {
211         // Spy of jukeboxStrategy will be used later to count how many items was inserted
212         final var spyStrategy = spy(jukeboxStrategy());
213         final var spyTx = spy(jukeboxStrategy().prepareWriteExecution());
214         doReturn(spyTx).when(spyStrategy).prepareWriteExecution();
215         doReturn(immediateFluentFuture(Optional.empty())).when(netconfService).getConfig(any());
216
217         doReturn(PLAYLIST_WITH_SONGS).when(spyTx).readList(any(YangInstanceIdentifier.class));
218
219         // Creating query params to insert new item after last existing item in list
220         final var queryParams = new HashMap<String, String>();
221         queryParams.put("insert", "after");
222         queryParams.put("point", "example-jukebox:jukebox/playlist=0/song=2");
223
224         // Inserting new song at 3rd position (aka as last element)
225         spyStrategy.dataPUT(ApiPath.parse("example-jukebox:jukebox/playlist=0/song=3"),
226             new JsonResourceBody(stringInputStream("""
227                 {
228                   "example-jukebox:song" : [
229                     {
230                        "index": "3",
231                        "id" = "C"
232                     }
233                   ]
234                 }""")), queryParams);
235
236         // Counting how many times we insert items in list
237         verify(spyTx, times(3)).replace(any(), any());
238     }
239
240     /**
241      * Test for post with insert=after option for last element of the list. Here we're trying to insert new element of
242      * the ordered list after last existing element. Test uses list with two items and add third one at the end. After
243      * this we check how many times replace transaction was called to know how many items was inserted.
244      * @throws ParseException if ApiPath string cannot be parsed
245      */
246     @Test
247     public void testPostDataWithInsertAfterLast() throws ParseException {
248         // Spy of jukeboxStrategy will be used later to count how many items was inserted
249         final var spyStrategy = spy(jukeboxStrategy());
250         final var spyTx = spy(jukeboxStrategy().prepareWriteExecution());
251         doReturn(spyTx).when(spyStrategy).prepareWriteExecution();
252         doReturn(immediateFluentFuture(Optional.empty())).when(netconfService).getConfig(any());
253
254         doReturn(PLAYLIST_WITH_SONGS).when(spyTx).readList(any(YangInstanceIdentifier.class));
255
256         // Creating query params to insert new item after last existing item in list
257         final var queryParams = new HashMap<String, String>();
258         queryParams.put("insert", "after");
259         queryParams.put("point", "example-jukebox:jukebox/playlist=0/song=2");
260
261         // Inserting new song at 3rd position (aka as last element)
262         spyStrategy.dataPOST(ApiPath.parse("example-jukebox:jukebox/playlist=0"),
263             new JsonDataPostBody(stringInputStream("""
264                 {
265                   "example-jukebox:song" : [
266                     {
267                        "index": "3",
268                        "id" = "C"
269                     }
270                   ]
271                 }""")), queryParams);
272
273         // Counting how many times we insert items in list
274         verify(spyTx, times(3)).replace(any(), any());
275     }
276
277     @Test
278     public void testPutReplaceListData() {
279         doReturn(immediateFluentFuture(Optional.of(mock(ContainerNode.class)))).when(netconfService)
280             .getConfig(JUKEBOX_IID);
281         doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService).commit();
282         doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService)
283             .replace(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID, JUKEBOX_WITH_BANDS, Optional.empty());
284
285         jukeboxStrategy().putData(JUKEBOX_IID, JUKEBOX_WITH_BANDS, null);
286         verify(netconfService).getConfig(JUKEBOX_IID);
287         verify(netconfService).replace(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID, JUKEBOX_WITH_BANDS,
288             Optional.empty());
289     }
290
291     @Override
292     RestconfStrategy testPatchDataReplaceMergeAndRemoveStrategy() {
293         doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService)
294             .remove(LogicalDatastoreType.CONFIGURATION, ARTIST_IID);
295         doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService)
296             // FIXME: exact match
297             .replace(any(), any(), any(), any());
298         return jukeboxStrategy();
299     }
300
301     @Override
302     RestconfStrategy testPatchDataCreateAndDeleteStrategy() {
303         doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService)
304             .create(LogicalDatastoreType.CONFIGURATION, PLAYER_IID, EMPTY_JUKEBOX, Optional.empty());
305         doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService)
306             .delete(LogicalDatastoreType.CONFIGURATION, CREATE_AND_DELETE_TARGET);
307         return jukeboxStrategy();
308     }
309
310     @Override
311     RestconfStrategy testPatchMergePutContainerStrategy() {
312         return jukeboxStrategy();
313     }
314
315     @Override
316     RestconfStrategy deleteNonexistentDataTestStrategy() {
317         doReturn(Futures.immediateFailedFuture(
318             new TransactionCommitFailedException("Commit of transaction " + this + " failed",
319                 new NetconfDocumentedException("id", ErrorType.RPC, ErrorTag.DATA_MISSING, ErrorSeverity.ERROR))))
320             .when(netconfService).commit();
321         doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService)
322             .delete(LogicalDatastoreType.CONFIGURATION, CREATE_AND_DELETE_TARGET);
323         return jukeboxStrategy();
324     }
325
326     @Override
327     void assertTestDeleteNonexistentData(final PatchStatusContext status, final PatchStatusEntity edit) {
328         assertNull(edit.getEditErrors());
329         final var globalErrors = status.globalErrors();
330         assertNotNull(globalErrors);
331         assertEquals(1, globalErrors.size());
332         final var globalError = globalErrors.get(0);
333         assertEquals("Data does not exist", globalError.getErrorMessage());
334         assertEquals(ErrorType.PROTOCOL, globalError.getErrorType());
335         assertEquals(ErrorTag.DATA_MISSING, globalError.getErrorTag());
336     }
337
338     @Override
339     RestconfStrategy readDataConfigTestStrategy() {
340         doReturn(immediateFluentFuture(Optional.of(DATA_3))).when(netconfService).getConfig(PATH);
341         return mockStrategy();
342     }
343
344     @Override
345     RestconfStrategy readAllHavingOnlyConfigTestStrategy() {
346         doReturn(immediateFluentFuture(Optional.of(DATA_3))).when(netconfService).getConfig(PATH);
347         doReturn(immediateFluentFuture(Optional.empty())).when(netconfService).get(PATH);
348         return mockStrategy();
349     }
350
351     @Override
352     RestconfStrategy readAllHavingOnlyNonConfigTestStrategy() {
353         doReturn(immediateFluentFuture(Optional.of(DATA_2))).when(netconfService).get(PATH_2);
354         doReturn(immediateFluentFuture(Optional.empty())).when(netconfService).getConfig(PATH_2);
355         return mockStrategy();
356     }
357
358     @Override
359     RestconfStrategy readDataNonConfigTestStrategy() {
360         doReturn(immediateFluentFuture(Optional.of(DATA_2))).when(netconfService).get(PATH_2);
361         return mockStrategy();
362     }
363
364     @Override
365     RestconfStrategy readContainerDataAllTestStrategy() {
366         doReturn(immediateFluentFuture(Optional.of(DATA_3))).when(netconfService).getConfig(PATH);
367         doReturn(immediateFluentFuture(Optional.of(DATA_4))).when(netconfService).get(PATH);
368         return mockStrategy();
369     }
370
371     @Override
372     RestconfStrategy readContainerDataConfigNoValueOfContentTestStrategy() {
373         doReturn(immediateFluentFuture(Optional.of(DATA_3))).when(netconfService).getConfig(PATH);
374         doReturn(immediateFluentFuture(Optional.of(DATA_4))).when(netconfService).get(PATH);
375         return mockStrategy();
376     }
377
378     @Override
379     RestconfStrategy readListDataAllTestStrategy() {
380         doReturn(immediateFluentFuture(Optional.of(LIST_DATA))).when(netconfService).get(PATH_3);
381         doReturn(immediateFluentFuture(Optional.of(LIST_DATA_2))).when(netconfService).getConfig(PATH_3);
382         return mockStrategy();
383     }
384
385     @Override
386     RestconfStrategy readOrderedListDataAllTestStrategy() {
387         doReturn(immediateFluentFuture(Optional.of(ORDERED_MAP_NODE_1))).when(netconfService).get(PATH_3);
388         doReturn(immediateFluentFuture(Optional.of(ORDERED_MAP_NODE_2))).when(netconfService).getConfig(PATH_3);
389         return mockStrategy();
390     }
391
392     @Override
393     RestconfStrategy readUnkeyedListDataAllTestStrategy() {
394         doReturn(immediateFluentFuture(Optional.of(UNKEYED_LIST_NODE_1))).when(netconfService).get(PATH_3);
395         doReturn(immediateFluentFuture(Optional.of(UNKEYED_LIST_NODE_2))).when(netconfService).getConfig(PATH_3);
396         return mockStrategy();
397     }
398
399     @Override
400     RestconfStrategy readLeafListDataAllTestStrategy() {
401         doReturn(immediateFluentFuture(Optional.of(LEAF_SET_NODE_1))).when(netconfService)
402             .get(LEAF_SET_NODE_PATH);
403         doReturn(immediateFluentFuture(Optional.of(LEAF_SET_NODE_2))).when(netconfService)
404             .getConfig(LEAF_SET_NODE_PATH);
405         return mockStrategy();
406     }
407
408     @Override
409     RestconfStrategy readOrderedLeafListDataAllTestStrategy() {
410         doReturn(immediateFluentFuture(Optional.of(ORDERED_LEAF_SET_NODE_1))).when(netconfService)
411             .get(LEAF_SET_NODE_PATH);
412         doReturn(immediateFluentFuture(Optional.of(ORDERED_LEAF_SET_NODE_2))).when(netconfService)
413             .getConfig(LEAF_SET_NODE_PATH);
414         return mockStrategy();
415     }
416
417     @Override
418     RestconfStrategy readDataWrongPathOrNoContentTestStrategy() {
419         doReturn(immediateFluentFuture(Optional.empty())).when(netconfService).getConfig(PATH_2);
420         return mockStrategy();
421     }
422 }