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.hamcrest.CoreMatchers.containsString;
11 import static org.hamcrest.CoreMatchers.instanceOf;
12 import static org.hamcrest.MatcherAssert.assertThat;
13 import static org.junit.Assert.assertEquals;
14 import static org.junit.Assert.assertFalse;
15 import static org.junit.Assert.assertNotNull;
16 import static org.junit.Assert.assertNull;
17 import static org.junit.Assert.assertThrows;
18 import static org.junit.Assert.assertTrue;
20 import com.google.common.collect.ImmutableList;
21 import com.google.common.util.concurrent.Futures;
22 import java.util.List;
23 import java.util.concurrent.ExecutionException;
24 import javax.ws.rs.core.UriInfo;
25 import org.eclipse.jdt.annotation.NonNull;
26 import org.eclipse.jdt.annotation.Nullable;
27 import org.junit.Test;
28 import org.mockito.Mock;
29 import org.opendaylight.restconf.api.query.ContentParam;
30 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
31 import org.opendaylight.restconf.common.patch.PatchContext;
32 import org.opendaylight.restconf.common.patch.PatchEntity;
33 import org.opendaylight.restconf.common.patch.PatchStatusContext;
34 import org.opendaylight.restconf.nb.rfc8040.AbstractJukeboxTest;
35 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.patch.rev170222.yang.patch.yang.patch.Edit.Operation;
36 import org.opendaylight.yangtools.yang.common.ErrorTag;
37 import org.opendaylight.yangtools.yang.common.ErrorType;
38 import org.opendaylight.yangtools.yang.common.QName;
39 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
40 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
41 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
42 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
43 import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
44 import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode;
45 import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode;
46 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
47 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
48 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
49 import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListEntryNode;
50 import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListNode;
51 import org.opendaylight.yangtools.yang.data.api.schema.UserMapNode;
52 import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
53 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
54 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
55 import org.w3c.dom.DOMException;
57 abstract class AbstractRestconfStrategyTest extends AbstractJukeboxTest {
58 static final ContainerNode JUKEBOX_WITH_BANDS = Builders.containerBuilder()
59 .withNodeIdentifier(new NodeIdentifier(JUKEBOX_QNAME))
60 .withChild(Builders.mapBuilder()
61 .withNodeIdentifier(new NodeIdentifier(PLAYLIST_QNAME))
62 .withChild(BAND_ENTRY)
63 .withChild(Builders.mapEntryBuilder()
64 .withNodeIdentifier(NodeIdentifierWithPredicates.of(PLAYLIST_QNAME, NAME_QNAME, "name of band 2"))
65 .withChild(ImmutableNodes.leafNode(NAME_QNAME, "name of band 2"))
66 .withChild(ImmutableNodes.leafNode(DESCRIPTION_QNAME, "band description 2"))
70 static final ContainerNode JUKEBOX_WITH_PLAYLIST = Builders.containerBuilder()
71 .withNodeIdentifier(new NodeIdentifier(JUKEBOX_QNAME))
72 .withChild(Builders.mapBuilder()
73 .withNodeIdentifier(new NodeIdentifier(PLAYLIST_QNAME))
74 .withChild(Builders.mapEntryBuilder()
75 .withNodeIdentifier(NodeIdentifierWithPredicates.of(PLAYLIST_QNAME, NAME_QNAME, "MyFavoriteBand-A"))
76 .withChild(ImmutableNodes.leafNode(NAME_QNAME, "MyFavoriteBand-A"))
77 .withChild(ImmutableNodes.leafNode(DESCRIPTION_QNAME, "band description A"))
79 .withChild(Builders.mapEntryBuilder()
80 .withNodeIdentifier(NodeIdentifierWithPredicates.of(PLAYLIST_QNAME, NAME_QNAME, "MyFavoriteBand-B"))
81 .withChild(ImmutableNodes.leafNode(NAME_QNAME, "MyFavoriteBand-B"))
82 .withChild(ImmutableNodes.leafNode(DESCRIPTION_QNAME, "band description B"))
86 static final MapNode PLAYLIST = Builders.mapBuilder()
87 .withNodeIdentifier(new NodeIdentifier(PLAYLIST_QNAME))
88 .withChild(BAND_ENTRY)
90 // instance identifier for accessing container node "player"
91 static final YangInstanceIdentifier PLAYER_IID = YangInstanceIdentifier.of(JUKEBOX_QNAME, PLAYER_QNAME);
92 static final YangInstanceIdentifier ARTIST_IID = YangInstanceIdentifier.builder()
96 .nodeWithKey(ARTIST_QNAME, NAME_QNAME, "name of artist")
98 // FIXME: this looks weird
99 static final YangInstanceIdentifier CREATE_AND_DELETE_TARGET = GAP_IID.node(PLAYER_QNAME).node(GAP_QNAME);
102 static final QName BASE = QName.create("ns", "2016-02-28", "base");
103 private static final QName LIST_KEY_QNAME = QName.create(BASE, "list-key");
104 private static final QName LEAF_LIST_QNAME = QName.create(BASE, "leaf-list");
105 private static final QName LIST_QNAME = QName.create(BASE, "list");
106 static final QName CONT_QNAME = QName.create(BASE, "cont");
108 private static final NodeIdentifierWithPredicates NODE_WITH_KEY =
109 NodeIdentifierWithPredicates.of(LIST_QNAME, LIST_KEY_QNAME, "keyValue");
110 private static final NodeIdentifierWithPredicates NODE_WITH_KEY_2 =
111 NodeIdentifierWithPredicates.of(LIST_QNAME, LIST_KEY_QNAME, "keyValue2");
113 private static final LeafNode<?> CONTENT = ImmutableNodes.leafNode(QName.create(BASE, "leaf-content"), "content");
114 private static final LeafNode<?> CONTENT_2 =
115 ImmutableNodes.leafNode(QName.create(BASE, "leaf-content-different"), "content-different");
116 static final YangInstanceIdentifier PATH = YangInstanceIdentifier.builder()
121 static final YangInstanceIdentifier PATH_2 = YangInstanceIdentifier.builder()
124 .node(NODE_WITH_KEY_2)
126 static final YangInstanceIdentifier PATH_3 = YangInstanceIdentifier.of(CONT_QNAME, LIST_QNAME);
127 private static final MapEntryNode DATA = Builders.mapEntryBuilder()
128 .withNodeIdentifier(NODE_WITH_KEY)
131 static final MapEntryNode DATA_2 = Builders.mapEntryBuilder()
132 .withNodeIdentifier(NODE_WITH_KEY)
133 .withChild(CONTENT_2)
135 private static final LeafNode<?> CONTENT_LEAF = ImmutableNodes.leafNode(QName.create(BASE, "content"), "test");
136 private static final LeafNode<?> CONTENT_LEAF_2 = ImmutableNodes.leafNode(QName.create(BASE, "content2"), "test2");
137 static final ContainerNode DATA_3 = Builders.containerBuilder()
138 .withNodeIdentifier(new NodeIdentifier(QName.create(BASE, "container")))
139 .withChild(CONTENT_LEAF)
141 static final ContainerNode DATA_4 = Builders.containerBuilder()
142 .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(QName.create(BASE, "container2")))
143 .withChild(CONTENT_LEAF_2)
145 static final MapNode LIST_DATA = Builders.mapBuilder()
146 .withNodeIdentifier(new NodeIdentifier(QName.create(LIST_QNAME, "list")))
149 static final MapNode LIST_DATA_2 = Builders.mapBuilder()
150 .withNodeIdentifier(new NodeIdentifier(QName.create(LIST_QNAME, "list")))
154 static final UserMapNode ORDERED_MAP_NODE_1 = Builders.orderedMapBuilder()
155 .withNodeIdentifier(new NodeIdentifier(LIST_QNAME))
158 static final UserMapNode ORDERED_MAP_NODE_2 = Builders.orderedMapBuilder()
159 .withNodeIdentifier(new NodeIdentifier(LIST_QNAME))
163 private static final MapEntryNode CHECK_DATA = Builders.mapEntryBuilder()
164 .withNodeIdentifier(NODE_WITH_KEY)
165 .withChild(CONTENT_2)
168 static final LeafSetNode<String> LEAF_SET_NODE_1 = Builders.<String>leafSetBuilder()
169 .withNodeIdentifier(new NodeIdentifier(LEAF_LIST_QNAME))
170 .withChildValue("one")
171 .withChildValue("two")
173 static final LeafSetNode<String> LEAF_SET_NODE_2 = Builders.<String>leafSetBuilder()
174 .withNodeIdentifier(new NodeIdentifier(LEAF_LIST_QNAME))
175 .withChildValue("three")
177 static final LeafSetNode<String> ORDERED_LEAF_SET_NODE_1 = Builders.<String>orderedLeafSetBuilder()
178 .withNodeIdentifier(new NodeIdentifier(LEAF_LIST_QNAME))
179 .withChildValue("one")
180 .withChildValue("two")
182 static final LeafSetNode<String> ORDERED_LEAF_SET_NODE_2 = Builders.<String>orderedLeafSetBuilder()
183 .withNodeIdentifier(new NodeIdentifier(LEAF_LIST_QNAME))
184 .withChildValue("three")
185 .withChildValue("four")
187 static final YangInstanceIdentifier LEAF_SET_NODE_PATH = YangInstanceIdentifier.builder()
189 .node(LEAF_LIST_QNAME)
191 private static final UnkeyedListEntryNode UNKEYED_LIST_ENTRY_NODE_1 = Builders.unkeyedListEntryBuilder()
192 .withNodeIdentifier(new NodeIdentifier(LIST_QNAME))
195 private static final UnkeyedListEntryNode UNKEYED_LIST_ENTRY_NODE_2 = Builders.unkeyedListEntryBuilder()
196 .withNodeIdentifier(new NodeIdentifier(LIST_QNAME))
197 .withChild(CONTENT_2)
199 static final UnkeyedListNode UNKEYED_LIST_NODE_1 = Builders.unkeyedListBuilder()
200 .withNodeIdentifier(new NodeIdentifier(LIST_QNAME))
201 .withChild(UNKEYED_LIST_ENTRY_NODE_1)
203 static final UnkeyedListNode UNKEYED_LIST_NODE_2 = Builders.unkeyedListBuilder()
204 .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(LIST_QNAME))
205 .withChild(UNKEYED_LIST_ENTRY_NODE_2)
207 private static final NodeIdentifier NODE_IDENTIFIER =
208 new NodeIdentifier(QName.create("ns", "2016-02-28", "container"));
211 EffectiveModelContext mockSchemaContext;
213 private UriInfo uriInfo;
216 * Test of successful DELETE operation.
219 public final void testDeleteData() throws Exception {
220 final var future = testDeleteDataStrategy().delete(YangInstanceIdentifier.of());
221 assertNotNull(Futures.getDone(future));
224 abstract @NonNull RestconfStrategy testDeleteDataStrategy();
227 * Negative test for DELETE operation when data to delete does not exist. Error DATA_MISSING is expected.
230 public final void testNegativeDeleteData() {
231 final var future = testNegativeDeleteDataStrategy().delete(YangInstanceIdentifier.of());
232 final var ex = assertThrows(ExecutionException.class, () -> Futures.getDone(future)).getCause();
233 assertThat(ex, instanceOf(RestconfDocumentedException.class));
234 final var errors = ((RestconfDocumentedException) ex).getErrors();
235 assertEquals(1, errors.size());
236 final var error = errors.get(0);
237 assertEquals(ErrorType.PROTOCOL, error.getErrorType());
238 assertEquals(ErrorTag.DATA_MISSING, error.getErrorTag());
241 abstract @NonNull RestconfStrategy testNegativeDeleteDataStrategy();
244 public final void testPostContainerData() {
245 testPostContainerDataStrategy().postData(JUKEBOX_IID, EMPTY_JUKEBOX, null);
248 abstract @NonNull RestconfStrategy testPostContainerDataStrategy();
251 public final void testPostListData() {
252 testPostListDataStrategy(BAND_ENTRY, PLAYLIST_IID.node(BAND_ENTRY.name())).postData(PLAYLIST_IID, PLAYLIST,
256 abstract @NonNull RestconfStrategy testPostListDataStrategy(MapEntryNode entryNode, YangInstanceIdentifier node);
259 public final void testPostDataFail() {
260 final var domException = new DOMException((short) 414, "Post request failed");
261 final var future = testPostDataFailStrategy(domException).postData(JUKEBOX_IID, EMPTY_JUKEBOX, null);
262 final var cause = assertThrows(ExecutionException.class, () -> Futures.getDone(future)).getCause();
263 assertThat(cause, instanceOf(RestconfDocumentedException.class));
264 final var errors = ((RestconfDocumentedException) cause).getErrors();
265 assertEquals(1, errors.size());
266 assertThat(errors.get(0).getErrorInfo(), containsString(domException.getMessage()));
269 abstract @NonNull RestconfStrategy testPostDataFailStrategy(DOMException domException);
272 public final void testPatchContainerData() {
273 testPatchContainerDataStrategy().merge(JUKEBOX_IID, EMPTY_JUKEBOX).getOrThrow();
276 abstract @NonNull RestconfStrategy testPatchContainerDataStrategy();
279 public final void testPatchLeafData() {
280 testPatchLeafDataStrategy().merge(GAP_IID, GAP_LEAF).getOrThrow();
283 abstract @NonNull RestconfStrategy testPatchLeafDataStrategy();
286 public final void testPatchListData() {
287 testPatchListDataStrategy().merge(JUKEBOX_IID, JUKEBOX_WITH_PLAYLIST).getOrThrow();
290 abstract @NonNull RestconfStrategy testPatchListDataStrategy();
293 public final void testPatchDataReplaceMergeAndRemove() {
294 final var buildArtistList = Builders.mapBuilder()
295 .withNodeIdentifier(new NodeIdentifier(ARTIST_QNAME))
296 .withChild(Builders.mapEntryBuilder()
297 .withNodeIdentifier(NodeIdentifierWithPredicates.of(ARTIST_QNAME, NAME_QNAME, "name of artist"))
298 .withChild(ImmutableNodes.leafNode(NAME_QNAME, "name of artist"))
299 .withChild(ImmutableNodes.leafNode(DESCRIPTION_QNAME, "description of artist"))
303 patch(new PatchContext("patchRMRm",
304 List.of(new PatchEntity("edit1", Operation.Replace, ARTIST_IID, buildArtistList),
305 new PatchEntity("edit2", Operation.Merge, ARTIST_IID, buildArtistList),
306 new PatchEntity("edit3", Operation.Remove, ARTIST_IID))),
307 testPatchDataReplaceMergeAndRemoveStrategy(), false);
310 abstract @NonNull RestconfStrategy testPatchDataReplaceMergeAndRemoveStrategy();
313 public final void testPatchDataCreateAndDelete() {
314 patch(new PatchContext("patchCD", List.of(
315 new PatchEntity("edit1", Operation.Create, PLAYER_IID, EMPTY_JUKEBOX),
316 new PatchEntity("edit2", Operation.Delete, CREATE_AND_DELETE_TARGET))),
317 testPatchDataCreateAndDeleteStrategy(), true);
320 abstract @NonNull RestconfStrategy testPatchDataCreateAndDeleteStrategy();
323 public final void testPatchMergePutContainer() {
324 patch(new PatchContext("patchM", List.of(new PatchEntity("edit1", Operation.Merge, PLAYER_IID, EMPTY_JUKEBOX))),
325 testPatchMergePutContainerStrategy(), false);
328 abstract @NonNull RestconfStrategy testPatchMergePutContainerStrategy();
331 public final void testDeleteNonexistentData() {
332 final var patchStatusContext = deleteNonexistentDataTestStrategy().patchData(new PatchContext("patchD",
333 List.of(new PatchEntity("edit", Operation.Delete, CREATE_AND_DELETE_TARGET))));
334 assertFalse(patchStatusContext.getOrThrow().ok());
337 abstract @NonNull RestconfStrategy deleteNonexistentDataTestStrategy();
339 abstract void assertTestDeleteNonexistentData(@NonNull PatchStatusContext status);
342 public final void readDataConfigTest() {
343 assertEquals(DATA_3, readData(ContentParam.CONFIG, PATH, readDataConfigTestStrategy()));
346 abstract @NonNull RestconfStrategy readDataConfigTestStrategy();
349 public final void readAllHavingOnlyConfigTest() {
350 assertEquals(DATA_3, readData(ContentParam.ALL, PATH, readAllHavingOnlyConfigTestStrategy()));
353 abstract @NonNull RestconfStrategy readAllHavingOnlyConfigTestStrategy();
356 public final void readAllHavingOnlyNonConfigTest() {
357 assertEquals(DATA_2, readData(ContentParam.ALL, PATH_2, readAllHavingOnlyNonConfigTestStrategy()));
360 abstract @NonNull RestconfStrategy readAllHavingOnlyNonConfigTestStrategy();
363 public final void readDataNonConfigTest() {
364 assertEquals(DATA_2, readData(ContentParam.NONCONFIG, PATH_2, readDataNonConfigTestStrategy()));
367 abstract @NonNull RestconfStrategy readDataNonConfigTestStrategy();
370 public final void readContainerDataAllTest() {
371 assertEquals(Builders.containerBuilder()
372 .withNodeIdentifier(NODE_IDENTIFIER)
373 .withChild(CONTENT_LEAF)
374 .withChild(CONTENT_LEAF_2)
375 .build(), readData(ContentParam.ALL, PATH, readContainerDataAllTestStrategy()));
378 abstract @NonNull RestconfStrategy readContainerDataAllTestStrategy();
381 public final void readContainerDataConfigNoValueOfContentTest() {
382 assertEquals(Builders.containerBuilder()
383 .withNodeIdentifier(NODE_IDENTIFIER)
384 .withChild(CONTENT_LEAF)
385 .withChild(CONTENT_LEAF_2)
386 .build(), readData(ContentParam.ALL, PATH, readContainerDataConfigNoValueOfContentTestStrategy()));
389 abstract @NonNull RestconfStrategy readContainerDataConfigNoValueOfContentTestStrategy();
392 public final void readListDataAllTest() {
393 assertEquals(Builders.mapBuilder()
394 .withNodeIdentifier(new NodeIdentifier(QName.create("ns", "2016-02-28", "list")))
395 .withChild(CHECK_DATA)
396 .build(), readData(ContentParam.ALL, PATH_3, readListDataAllTestStrategy()));
399 abstract @NonNull RestconfStrategy readListDataAllTestStrategy();
402 public final void readOrderedListDataAllTest() {
403 assertEquals(Builders.orderedMapBuilder()
404 .withNodeIdentifier(new NodeIdentifier(LIST_QNAME))
405 .withChild(CHECK_DATA)
406 .build(), readData(ContentParam.ALL, PATH_3, readOrderedListDataAllTestStrategy()));
409 abstract @NonNull RestconfStrategy readOrderedListDataAllTestStrategy();
412 public void readUnkeyedListDataAllTest() {
413 assertEquals(Builders.unkeyedListBuilder()
414 .withNodeIdentifier(new NodeIdentifier(LIST_QNAME))
415 .withChild(Builders.unkeyedListEntryBuilder()
416 .withNodeIdentifier(new NodeIdentifier(LIST_QNAME))
417 .withChild(UNKEYED_LIST_ENTRY_NODE_1.body().iterator().next())
418 .withChild(UNKEYED_LIST_ENTRY_NODE_2.body().iterator().next())
420 .build(), readData(ContentParam.ALL, PATH_3, readUnkeyedListDataAllTestStrategy()));
423 abstract @NonNull RestconfStrategy readUnkeyedListDataAllTestStrategy();
426 public final void readLeafListDataAllTest() {
427 assertEquals(Builders.<String>leafSetBuilder()
428 .withNodeIdentifier(new NodeIdentifier(LEAF_LIST_QNAME))
429 .withValue(ImmutableList.<LeafSetEntryNode<String>>builder()
430 .addAll(LEAF_SET_NODE_1.body())
431 .addAll(LEAF_SET_NODE_2.body())
433 .build(), readData(ContentParam.ALL, LEAF_SET_NODE_PATH, readLeafListDataAllTestStrategy()));
436 abstract @NonNull RestconfStrategy readLeafListDataAllTestStrategy();
439 public final void readOrderedLeafListDataAllTest() {
440 assertEquals(Builders.<String>orderedLeafSetBuilder()
441 .withNodeIdentifier(new NodeIdentifier(LEAF_LIST_QNAME))
442 .withValue(ImmutableList.<LeafSetEntryNode<String>>builder()
443 .addAll(ORDERED_LEAF_SET_NODE_1.body())
444 .addAll(ORDERED_LEAF_SET_NODE_2.body())
446 .build(), readData(ContentParam.ALL, LEAF_SET_NODE_PATH, readOrderedLeafListDataAllTestStrategy()));
449 abstract @NonNull RestconfStrategy readOrderedLeafListDataAllTestStrategy();
452 public void readDataWrongPathOrNoContentTest() {
453 assertNull(readData(ContentParam.CONFIG, PATH_2, readDataWrongPathOrNoContentTestStrategy()));
456 abstract @NonNull RestconfStrategy readDataWrongPathOrNoContentTestStrategy();
459 * Read specific type of data from data store via transaction.
461 * @param content type of data to read (config, state, all)
462 * @param strategy {@link RestconfStrategy} - wrapper for variables
463 * @return {@link NormalizedNode}
465 private static @Nullable NormalizedNode readData(final @NonNull ContentParam content,
466 final YangInstanceIdentifier path, final @NonNull RestconfStrategy strategy) {
467 return strategy.readData(content, path, null);
470 private static void patch(final PatchContext patchContext, final RestconfStrategy strategy, final boolean failed) {
471 final var patchStatusContext = strategy.patchData(patchContext).getOrThrow();
472 for (var entity : patchStatusContext.editCollection()) {
474 assertTrue("Edit " + entity.getEditId() + " failed", entity.isOk());
476 assertTrue(entity.isOk());
479 assertTrue(patchStatusContext.ok());