2 * Copyright (c) 2023 PANTHEON.tech, s.r.o. 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.transactions;
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;
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;
50 @RunWith(MockitoJUnitRunner.StrictStubs.class)
51 public final class NetconfRestconfStrategyTest extends AbstractRestconfStrategyTest {
53 private NetconfDataTreeService netconfService;
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(),
68 RestconfStrategy newStrategy(final DatabindContext databind) {
69 return new NetconfRestconfStrategy(databind, netconfService, null, null, null, null);
73 RestconfStrategy testDeleteDataStrategy() {
74 return jukeboxStrategy();
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();
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();
94 RestconfStrategy testPostListDataStrategy(final MapEntryNode entryNode, final YangInstanceIdentifier node) {
95 doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService)
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();
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();
115 RestconfStrategy testPatchContainerDataStrategy() {
116 doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService).merge(any(), any(),any(),
118 return jukeboxStrategy();
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();
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();
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());
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,
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());
159 jukeboxStrategy().putData(JUKEBOX_IID, EMPTY_JUKEBOX, null);
160 verify(netconfService).getConfig(JUKEBOX_IID);
161 verify(netconfService).replace(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID, EMPTY_JUKEBOX,
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());
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());
178 public void testPutReplaceLeafData() {
179 doReturn(immediateFluentFuture(Optional.of(mock(ContainerNode.class)))).when(netconfService)
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());
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());
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());
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,
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
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());
217 doReturn(PLAYLIST_WITH_SONGS).when(spyTx).readList(any(YangInstanceIdentifier.class));
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");
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("""
228 "example-jukebox:song" : [
234 }""")), queryParams);
236 // Counting how many times we insert items in list
237 verify(spyTx, times(3)).replace(any(), any());
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
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());
254 doReturn(PLAYLIST_WITH_SONGS).when(spyTx).readList(any(YangInstanceIdentifier.class));
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");
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("""
265 "example-jukebox:song" : [
271 }""")), queryParams);
273 // Counting how many times we insert items in list
274 verify(spyTx, times(3)).replace(any(), any());
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());
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,
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();
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();
311 RestconfStrategy testPatchMergePutContainerStrategy() {
312 return jukeboxStrategy();
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();
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());
339 RestconfStrategy readDataConfigTestStrategy() {
340 doReturn(immediateFluentFuture(Optional.of(DATA_3))).when(netconfService).getConfig(PATH);
341 return mockStrategy();
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();
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();
359 RestconfStrategy readDataNonConfigTestStrategy() {
360 doReturn(immediateFluentFuture(Optional.of(DATA_2))).when(netconfService).get(PATH_2);
361 return mockStrategy();
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();
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();
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();
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();
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();
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();
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();
418 RestconfStrategy readDataWrongPathOrNoContentTestStrategy() {
419 doReturn(immediateFluentFuture(Optional.empty())).when(netconfService).getConfig(PATH_2);
420 return mockStrategy();