Rework body formatting wiring
[netconf.git] / restconf / restconf-nb / src / test / java / org / opendaylight / restconf / nb / rfc8040 / rests / transactions / AbstractRestconfStrategyTest.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.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;
19
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.Before;
28 import org.junit.Test;
29 import org.mockito.Mock;
30 import org.opendaylight.restconf.api.ApiPath;
31 import org.opendaylight.restconf.api.QueryParameters;
32 import org.opendaylight.restconf.api.query.ContentParam;
33 import org.opendaylight.restconf.api.query.PrettyPrintParam;
34 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
35 import org.opendaylight.restconf.common.patch.PatchContext;
36 import org.opendaylight.restconf.common.patch.PatchEntity;
37 import org.opendaylight.restconf.nb.rfc8040.AbstractJukeboxTest;
38 import org.opendaylight.restconf.server.api.DatabindContext;
39 import org.opendaylight.restconf.server.api.PatchStatusContext;
40 import org.opendaylight.restconf.server.api.PatchStatusEntity;
41 import org.opendaylight.restconf.server.api.ServerRequest;
42 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.patch.rev170222.yang.patch.yang.patch.Edit.Operation;
43 import org.opendaylight.yangtools.yang.common.ErrorTag;
44 import org.opendaylight.yangtools.yang.common.ErrorType;
45 import org.opendaylight.yangtools.yang.common.QName;
46 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
47 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
48 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
49 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
50 import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
51 import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode;
52 import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode;
53 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
54 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
55 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
56 import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListEntryNode;
57 import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListNode;
58 import org.opendaylight.yangtools.yang.data.api.schema.UserMapNode;
59 import org.opendaylight.yangtools.yang.data.spi.node.ImmutableNodes;
60 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
61 import org.w3c.dom.DOMException;
62
63 abstract class AbstractRestconfStrategyTest extends AbstractJukeboxTest {
64     static final ContainerNode JUKEBOX_WITH_BANDS = ImmutableNodes.newContainerBuilder()
65         .withNodeIdentifier(new NodeIdentifier(JUKEBOX_QNAME))
66         .withChild(ImmutableNodes.newSystemMapBuilder()
67             .withNodeIdentifier(new NodeIdentifier(PLAYLIST_QNAME))
68             .withChild(BAND_ENTRY)
69             .withChild(ImmutableNodes.newMapEntryBuilder()
70                 .withNodeIdentifier(NodeIdentifierWithPredicates.of(PLAYLIST_QNAME, NAME_QNAME, "name of band 2"))
71                 .withChild(ImmutableNodes.leafNode(NAME_QNAME, "name of band 2"))
72                 .withChild(ImmutableNodes.leafNode(DESCRIPTION_QNAME, "band description 2"))
73                 .build())
74             .build())
75         .build();
76     static final ContainerNode JUKEBOX_WITH_PLAYLIST = ImmutableNodes.newContainerBuilder()
77         .withNodeIdentifier(new NodeIdentifier(JUKEBOX_QNAME))
78         .withChild(ImmutableNodes.newSystemMapBuilder()
79             .withNodeIdentifier(new NodeIdentifier(PLAYLIST_QNAME))
80             .withChild(ImmutableNodes.newMapEntryBuilder()
81                 .withNodeIdentifier(NodeIdentifierWithPredicates.of(PLAYLIST_QNAME, NAME_QNAME, "MyFavoriteBand-A"))
82                 .withChild(ImmutableNodes.leafNode(NAME_QNAME, "MyFavoriteBand-A"))
83                 .withChild(ImmutableNodes.leafNode(DESCRIPTION_QNAME, "band description A"))
84                 .build())
85             .withChild(ImmutableNodes.newMapEntryBuilder()
86                 .withNodeIdentifier(NodeIdentifierWithPredicates.of(PLAYLIST_QNAME, NAME_QNAME, "MyFavoriteBand-B"))
87                 .withChild(ImmutableNodes.leafNode(NAME_QNAME, "MyFavoriteBand-B"))
88                 .withChild(ImmutableNodes.leafNode(DESCRIPTION_QNAME, "band description B"))
89                 .build())
90             .build())
91         .build();
92     static final MapNode PLAYLIST = ImmutableNodes.newSystemMapBuilder()
93         .withNodeIdentifier(new NodeIdentifier(PLAYLIST_QNAME))
94         .withChild(BAND_ENTRY)
95         .build();
96     // instance identifier for accessing container node "player"
97     static final YangInstanceIdentifier PLAYER_IID = YangInstanceIdentifier.of(JUKEBOX_QNAME, PLAYER_QNAME);
98     static final YangInstanceIdentifier ARTIST_IID = YangInstanceIdentifier.builder()
99         .node(JUKEBOX_QNAME)
100         .node(LIBRARY_QNAME)
101         .node(ARTIST_QNAME)
102         .nodeWithKey(ARTIST_QNAME, NAME_QNAME, "name of artist")
103         .build();
104     // FIXME: this looks weird
105     static final YangInstanceIdentifier CREATE_AND_DELETE_TARGET = GAP_IID.node(PLAYER_QNAME).node(GAP_QNAME);
106
107     // Read mock data
108     static final QName BASE = QName.create("ns", "2016-02-28", "base");
109     private static final QName LIST_KEY_QNAME = QName.create(BASE, "list-key");
110     private static final QName LEAF_LIST_QNAME = QName.create(BASE, "leaf-list");
111     private static final QName LIST_QNAME = QName.create(BASE, "list");
112     static final QName CONT_QNAME = QName.create(BASE, "cont");
113
114     private static final NodeIdentifierWithPredicates NODE_WITH_KEY =
115         NodeIdentifierWithPredicates.of(LIST_QNAME, LIST_KEY_QNAME, "keyValue");
116     private static final NodeIdentifierWithPredicates NODE_WITH_KEY_2 =
117         NodeIdentifierWithPredicates.of(LIST_QNAME, LIST_KEY_QNAME, "keyValue2");
118
119     private static final LeafNode<?> CONTENT = ImmutableNodes.leafNode(QName.create(BASE, "leaf-content"), "content");
120     private static final LeafNode<?> CONTENT_2 =
121         ImmutableNodes.leafNode(QName.create(BASE, "leaf-content-different"), "content-different");
122     static final YangInstanceIdentifier PATH = YangInstanceIdentifier.builder()
123         .node(CONT_QNAME)
124         .node(LIST_QNAME)
125         .node(NODE_WITH_KEY)
126         .build();
127     static final YangInstanceIdentifier PATH_2 = YangInstanceIdentifier.builder()
128         .node(CONT_QNAME)
129         .node(LIST_QNAME)
130         .node(NODE_WITH_KEY_2)
131         .build();
132     static final YangInstanceIdentifier PATH_3 = YangInstanceIdentifier.of(CONT_QNAME, LIST_QNAME);
133     private static final MapEntryNode DATA = ImmutableNodes.newMapEntryBuilder()
134         .withNodeIdentifier(NODE_WITH_KEY)
135         .withChild(CONTENT)
136         .build();
137     static final MapEntryNode DATA_2 = ImmutableNodes.newMapEntryBuilder()
138         .withNodeIdentifier(NODE_WITH_KEY)
139         .withChild(CONTENT_2)
140         .build();
141     private static final LeafNode<?> CONTENT_LEAF = ImmutableNodes.leafNode(QName.create(BASE, "content"), "test");
142     private static final LeafNode<?> CONTENT_LEAF_2 = ImmutableNodes.leafNode(QName.create(BASE, "content2"), "test2");
143     static final ContainerNode DATA_3 = ImmutableNodes.newContainerBuilder()
144         .withNodeIdentifier(new NodeIdentifier(QName.create(BASE, "container")))
145         .withChild(CONTENT_LEAF)
146         .build();
147     static final ContainerNode DATA_4 = ImmutableNodes.newContainerBuilder()
148         .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(QName.create(BASE, "container2")))
149         .withChild(CONTENT_LEAF_2)
150         .build();
151     static final MapNode LIST_DATA = ImmutableNodes.newSystemMapBuilder()
152         .withNodeIdentifier(new NodeIdentifier(QName.create(LIST_QNAME, "list")))
153         .withChild(DATA)
154         .build();
155     static final MapNode LIST_DATA_2 = ImmutableNodes.newSystemMapBuilder()
156         .withNodeIdentifier(new NodeIdentifier(QName.create(LIST_QNAME, "list")))
157         .withChild(DATA)
158         .withChild(DATA_2)
159         .build();
160     static final UserMapNode ORDERED_MAP_NODE_1 = ImmutableNodes.newUserMapBuilder()
161         .withNodeIdentifier(new NodeIdentifier(LIST_QNAME))
162         .withChild(DATA)
163         .build();
164     static final UserMapNode ORDERED_MAP_NODE_2 = ImmutableNodes.newUserMapBuilder()
165         .withNodeIdentifier(new NodeIdentifier(LIST_QNAME))
166         .withChild(DATA)
167         .withChild(DATA_2)
168         .build();
169     private static final MapEntryNode CHECK_DATA = ImmutableNodes.newMapEntryBuilder()
170         .withNodeIdentifier(NODE_WITH_KEY)
171         .withChild(CONTENT_2)
172         .withChild(CONTENT)
173         .build();
174     static final LeafSetNode<String> LEAF_SET_NODE_1 = ImmutableNodes.<String>newSystemLeafSetBuilder()
175         .withNodeIdentifier(new NodeIdentifier(LEAF_LIST_QNAME))
176         .withChildValue("one")
177         .withChildValue("two")
178         .build();
179     static final LeafSetNode<String> LEAF_SET_NODE_2 = ImmutableNodes.<String>newSystemLeafSetBuilder()
180         .withNodeIdentifier(new NodeIdentifier(LEAF_LIST_QNAME))
181         .withChildValue("three")
182         .build();
183     static final LeafSetNode<String> ORDERED_LEAF_SET_NODE_1 = ImmutableNodes.<String>newUserLeafSetBuilder()
184         .withNodeIdentifier(new NodeIdentifier(LEAF_LIST_QNAME))
185         .withChildValue("one")
186         .withChildValue("two")
187         .build();
188     static final LeafSetNode<String> ORDERED_LEAF_SET_NODE_2 = ImmutableNodes.<String>newUserLeafSetBuilder()
189         .withNodeIdentifier(new NodeIdentifier(LEAF_LIST_QNAME))
190         .withChildValue("three")
191         .withChildValue("four")
192         .build();
193     static final YangInstanceIdentifier LEAF_SET_NODE_PATH = YangInstanceIdentifier.builder()
194         .node(CONT_QNAME)
195         .node(LEAF_LIST_QNAME)
196         .build();
197     private static final UnkeyedListEntryNode UNKEYED_LIST_ENTRY_NODE_1 = ImmutableNodes.newUnkeyedListEntryBuilder()
198         .withNodeIdentifier(new NodeIdentifier(LIST_QNAME))
199         .withChild(CONTENT)
200         .build();
201     private static final UnkeyedListEntryNode UNKEYED_LIST_ENTRY_NODE_2 = ImmutableNodes.newUnkeyedListEntryBuilder()
202         .withNodeIdentifier(new NodeIdentifier(LIST_QNAME))
203         .withChild(CONTENT_2)
204         .build();
205     static final UnkeyedListNode UNKEYED_LIST_NODE_1 = ImmutableNodes.newUnkeyedListBuilder()
206         .withNodeIdentifier(new NodeIdentifier(LIST_QNAME))
207         .withChild(UNKEYED_LIST_ENTRY_NODE_1)
208         .build();
209     static final UnkeyedListNode UNKEYED_LIST_NODE_2 = ImmutableNodes.newUnkeyedListBuilder()
210         .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(LIST_QNAME))
211         .withChild(UNKEYED_LIST_ENTRY_NODE_2)
212         .build();
213     private static final NodeIdentifier NODE_IDENTIFIER =
214         new NodeIdentifier(QName.create("ns", "2016-02-28", "container"));
215     private static final ServerRequest REQUEST = ServerRequest.of(QueryParameters.of(), PrettyPrintParam.TRUE);
216
217     @Mock
218     private EffectiveModelContext mockSchemaContext;
219     @Mock
220     private UriInfo uriInfo;
221
222     private DatabindContext mockDatabind;
223
224     @Before
225     public void initMockDatabind() {
226         mockDatabind = DatabindContext.ofModel(mockSchemaContext);
227     }
228
229     abstract @NonNull RestconfStrategy newStrategy(DatabindContext databind);
230
231     final @NonNull RestconfStrategy jukeboxStrategy() {
232         return newStrategy(JUKEBOX_DATABIND);
233     }
234
235     final @NonNull RestconfStrategy mockStrategy() {
236         return newStrategy(mockDatabind);
237     }
238
239     /**
240      * Test of successful DELETE operation.
241      */
242     @Test
243     public final void testDeleteData() throws Exception {
244         final var future = testDeleteDataStrategy().dataDELETE(REQUEST, ApiPath.empty());
245         assertNotNull(Futures.getDone(future));
246     }
247
248     abstract @NonNull RestconfStrategy testDeleteDataStrategy();
249
250     /**
251      * Negative test for DELETE operation when data to delete does not exist. Error DATA_MISSING is expected.
252      */
253     @Test
254     public final void testNegativeDeleteData() {
255         final var future = testNegativeDeleteDataStrategy().dataDELETE(REQUEST, ApiPath.empty());
256         final var ex = assertThrows(ExecutionException.class, () -> Futures.getDone(future)).getCause();
257         assertThat(ex, instanceOf(RestconfDocumentedException.class));
258         final var errors = ((RestconfDocumentedException) ex).getErrors();
259         assertEquals(1, errors.size());
260         final var error = errors.get(0);
261         assertEquals(ErrorType.PROTOCOL, error.getErrorType());
262         assertEquals(ErrorTag.DATA_MISSING, error.getErrorTag());
263     }
264
265     abstract @NonNull RestconfStrategy testNegativeDeleteDataStrategy();
266
267     @Test
268     public final void testPostContainerData() {
269         testPostContainerDataStrategy().postData(JUKEBOX_IID, EMPTY_JUKEBOX, null);
270     }
271
272     abstract @NonNull RestconfStrategy testPostContainerDataStrategy();
273
274     @Test
275     public final void testPostListData() {
276         testPostListDataStrategy(BAND_ENTRY, PLAYLIST_IID.node(BAND_ENTRY.name())).postData(PLAYLIST_IID, PLAYLIST,
277             null);
278     }
279
280     abstract @NonNull RestconfStrategy testPostListDataStrategy(MapEntryNode entryNode, YangInstanceIdentifier node);
281
282     @Test
283     public final void testPostDataFail() {
284         final var domException = new DOMException((short) 414, "Post request failed");
285         final var future = testPostDataFailStrategy(domException).postData(JUKEBOX_IID, EMPTY_JUKEBOX, null);
286         final var cause = assertThrows(ExecutionException.class, () -> Futures.getDone(future)).getCause();
287         assertThat(cause, instanceOf(RestconfDocumentedException.class));
288         final var errors = ((RestconfDocumentedException) cause).getErrors();
289         assertEquals(1, errors.size());
290         assertThat(errors.get(0).getErrorInfo(), containsString(domException.getMessage()));
291     }
292
293     abstract @NonNull RestconfStrategy testPostDataFailStrategy(DOMException domException);
294
295     @Test
296     public final void testPatchContainerData() {
297         testPatchContainerDataStrategy().merge(JUKEBOX_IID, EMPTY_JUKEBOX).getOrThrow();
298     }
299
300     abstract @NonNull RestconfStrategy testPatchContainerDataStrategy();
301
302     @Test
303     public final void testPatchLeafData() {
304         testPatchLeafDataStrategy().merge(GAP_IID, GAP_LEAF).getOrThrow();
305     }
306
307     abstract @NonNull RestconfStrategy testPatchLeafDataStrategy();
308
309     @Test
310     public final void testPatchListData() {
311         testPatchListDataStrategy().merge(JUKEBOX_IID, JUKEBOX_WITH_PLAYLIST).getOrThrow();
312     }
313
314     abstract @NonNull RestconfStrategy testPatchListDataStrategy();
315
316     @Test
317     public final void testPatchDataReplaceMergeAndRemove() {
318         final var buildArtistList = ImmutableNodes.newSystemMapBuilder()
319             .withNodeIdentifier(new NodeIdentifier(ARTIST_QNAME))
320             .withChild(ImmutableNodes.newMapEntryBuilder()
321                 .withNodeIdentifier(NodeIdentifierWithPredicates.of(ARTIST_QNAME, NAME_QNAME, "name of artist"))
322                 .withChild(ImmutableNodes.leafNode(NAME_QNAME, "name of artist"))
323                 .withChild(ImmutableNodes.leafNode(DESCRIPTION_QNAME, "description of artist"))
324                 .build())
325             .build();
326
327         patch(new PatchContext("patchRMRm",
328             List.of(new PatchEntity("edit1", Operation.Replace, ARTIST_IID, buildArtistList),
329                 new PatchEntity("edit2", Operation.Merge, ARTIST_IID, buildArtistList),
330                 new PatchEntity("edit3", Operation.Remove, ARTIST_IID))),
331             testPatchDataReplaceMergeAndRemoveStrategy(), false);
332     }
333
334     abstract @NonNull RestconfStrategy testPatchDataReplaceMergeAndRemoveStrategy();
335
336     @Test
337     public final void testPatchDataCreateAndDelete() {
338         patch(new PatchContext("patchCD", List.of(
339             new PatchEntity("edit1", Operation.Create, PLAYER_IID, EMPTY_JUKEBOX),
340             new PatchEntity("edit2", Operation.Delete, CREATE_AND_DELETE_TARGET))),
341             testPatchDataCreateAndDeleteStrategy(), true);
342     }
343
344     abstract @NonNull RestconfStrategy testPatchDataCreateAndDeleteStrategy();
345
346     @Test
347     public final void testPatchMergePutContainer() {
348         patch(new PatchContext("patchM", List.of(new PatchEntity("edit1", Operation.Merge, PLAYER_IID, EMPTY_JUKEBOX))),
349             testPatchMergePutContainerStrategy(), false);
350     }
351
352     abstract @NonNull RestconfStrategy testPatchMergePutContainerStrategy();
353
354     @Test
355     public final void testDeleteNonexistentData() {
356         final var status = deleteNonexistentDataTestStrategy().patchData(
357             new PatchContext("patchD", List.of(new PatchEntity("edit", Operation.Delete, CREATE_AND_DELETE_TARGET))))
358             .getOrThrow().status();
359         assertEquals("patchD", status.patchId());
360         assertFalse(status.ok());
361         final var edits = status.editCollection();
362         assertEquals(1, edits.size());
363         final var edit = edits.get(0);
364         assertEquals("edit", edit.getEditId());
365         assertTestDeleteNonexistentData(status, edit);
366     }
367
368     abstract @NonNull RestconfStrategy deleteNonexistentDataTestStrategy();
369
370     abstract void assertTestDeleteNonexistentData(@NonNull PatchStatusContext status, @NonNull PatchStatusEntity edit);
371
372     @Test
373     public final void readDataConfigTest() {
374         assertEquals(DATA_3, readData(ContentParam.CONFIG, PATH, readDataConfigTestStrategy()));
375     }
376
377     abstract @NonNull RestconfStrategy readDataConfigTestStrategy();
378
379     @Test
380     public final void readAllHavingOnlyConfigTest() {
381         assertEquals(DATA_3, readData(ContentParam.ALL, PATH, readAllHavingOnlyConfigTestStrategy()));
382     }
383
384     abstract @NonNull RestconfStrategy readAllHavingOnlyConfigTestStrategy();
385
386     @Test
387     public final void readAllHavingOnlyNonConfigTest() {
388         assertEquals(DATA_2, readData(ContentParam.ALL, PATH_2, readAllHavingOnlyNonConfigTestStrategy()));
389     }
390
391     abstract @NonNull RestconfStrategy readAllHavingOnlyNonConfigTestStrategy();
392
393     @Test
394     public final void readDataNonConfigTest() {
395         assertEquals(DATA_2, readData(ContentParam.NONCONFIG, PATH_2, readDataNonConfigTestStrategy()));
396     }
397
398     abstract @NonNull RestconfStrategy readDataNonConfigTestStrategy();
399
400     @Test
401     public final void readContainerDataAllTest() {
402         assertEquals(ImmutableNodes.newContainerBuilder()
403             .withNodeIdentifier(NODE_IDENTIFIER)
404             .withChild(CONTENT_LEAF)
405             .withChild(CONTENT_LEAF_2)
406             .build(), readData(ContentParam.ALL, PATH, readContainerDataAllTestStrategy()));
407     }
408
409     abstract @NonNull RestconfStrategy readContainerDataAllTestStrategy();
410
411     @Test
412     public final void readContainerDataConfigNoValueOfContentTest() {
413         assertEquals(ImmutableNodes.newContainerBuilder()
414             .withNodeIdentifier(NODE_IDENTIFIER)
415             .withChild(CONTENT_LEAF)
416             .withChild(CONTENT_LEAF_2)
417             .build(), readData(ContentParam.ALL, PATH, readContainerDataConfigNoValueOfContentTestStrategy()));
418     }
419
420     abstract @NonNull RestconfStrategy readContainerDataConfigNoValueOfContentTestStrategy();
421
422     @Test
423     public final void readListDataAllTest() {
424         assertEquals(ImmutableNodes.newSystemMapBuilder()
425             .withNodeIdentifier(new NodeIdentifier(QName.create("ns", "2016-02-28", "list")))
426             .withChild(CHECK_DATA)
427             .build(), readData(ContentParam.ALL, PATH_3, readListDataAllTestStrategy()));
428     }
429
430     abstract @NonNull RestconfStrategy readListDataAllTestStrategy();
431
432     @Test
433     public final void readOrderedListDataAllTest() {
434         assertEquals(ImmutableNodes.newUserMapBuilder()
435             .withNodeIdentifier(new NodeIdentifier(LIST_QNAME))
436             .withChild(CHECK_DATA)
437             .build(), readData(ContentParam.ALL, PATH_3, readOrderedListDataAllTestStrategy()));
438     }
439
440     abstract @NonNull RestconfStrategy readOrderedListDataAllTestStrategy();
441
442     @Test
443     public void readUnkeyedListDataAllTest() {
444         assertEquals(ImmutableNodes.newUnkeyedListBuilder()
445             .withNodeIdentifier(new NodeIdentifier(LIST_QNAME))
446             .withChild(ImmutableNodes.newUnkeyedListEntryBuilder()
447                 .withNodeIdentifier(new NodeIdentifier(LIST_QNAME))
448                 .withChild(UNKEYED_LIST_ENTRY_NODE_1.body().iterator().next())
449                 .withChild(UNKEYED_LIST_ENTRY_NODE_2.body().iterator().next())
450                 .build())
451             .build(), readData(ContentParam.ALL, PATH_3, readUnkeyedListDataAllTestStrategy()));
452     }
453
454     abstract @NonNull RestconfStrategy readUnkeyedListDataAllTestStrategy();
455
456     @Test
457     public final void readLeafListDataAllTest() {
458         assertEquals(ImmutableNodes.<String>newSystemLeafSetBuilder()
459             .withNodeIdentifier(new NodeIdentifier(LEAF_LIST_QNAME))
460             .withValue(ImmutableList.<LeafSetEntryNode<String>>builder()
461                 .addAll(LEAF_SET_NODE_1.body())
462                 .addAll(LEAF_SET_NODE_2.body())
463                 .build())
464             .build(), readData(ContentParam.ALL, LEAF_SET_NODE_PATH, readLeafListDataAllTestStrategy()));
465     }
466
467     abstract @NonNull RestconfStrategy readLeafListDataAllTestStrategy();
468
469     @Test
470     public final void readOrderedLeafListDataAllTest() {
471         assertEquals(ImmutableNodes.<String>newUserLeafSetBuilder()
472             .withNodeIdentifier(new NodeIdentifier(LEAF_LIST_QNAME))
473             .withValue(ImmutableList.<LeafSetEntryNode<String>>builder()
474                 .addAll(ORDERED_LEAF_SET_NODE_1.body())
475                 .addAll(ORDERED_LEAF_SET_NODE_2.body())
476                 .build())
477             .build(), readData(ContentParam.ALL, LEAF_SET_NODE_PATH, readOrderedLeafListDataAllTestStrategy()));
478     }
479
480     abstract @NonNull RestconfStrategy readOrderedLeafListDataAllTestStrategy();
481
482     @Test
483     public void readDataWrongPathOrNoContentTest() {
484         assertNull(readData(ContentParam.CONFIG, PATH_2, readDataWrongPathOrNoContentTestStrategy()));
485     }
486
487     abstract @NonNull RestconfStrategy readDataWrongPathOrNoContentTestStrategy();
488
489     /**
490      * Read specific type of data from data store via transaction.
491      *
492      * @param content        type of data to read (config, state, all)
493      * @param strategy       {@link RestconfStrategy} - wrapper for variables
494      * @return {@link NormalizedNode}
495      */
496     private static @Nullable NormalizedNode readData(final @NonNull ContentParam content,
497             final YangInstanceIdentifier path, final @NonNull RestconfStrategy strategy) {
498         return strategy.readData(content, path, null);
499     }
500
501     private static void patch(final PatchContext patchContext, final RestconfStrategy strategy, final boolean failed) {
502         final var patchStatusContext = strategy.patchData(patchContext).getOrThrow().status();
503         for (var entity : patchStatusContext.editCollection()) {
504             if (failed) {
505                 assertTrue("Edit " + entity.getEditId() + " failed", entity.isOk());
506             } else {
507                 assertTrue(entity.isOk());
508             }
509         }
510         assertTrue(patchStatusContext.ok());
511     }
512 }