a0f15f361bc536e144fd37a0b4047d795906eb84
[yangtools.git] / data / yang-data-tree-ri / src / test / java / org / opendaylight / yangtools / yang / data / tree / impl / Bug4454Test.java
1 /*
2  * Copyright (c) 2015 Pantheon Technologies 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.yangtools.yang.data.tree.impl;
9
10 import static junit.framework.TestCase.assertFalse;
11 import static org.hamcrest.CoreMatchers.containsString;
12 import static org.hamcrest.MatcherAssert.assertThat;
13 import static org.junit.Assert.assertEquals;
14 import static org.junit.Assert.assertTrue;
15 import static org.junit.Assert.fail;
16
17 import java.util.Collection;
18 import java.util.HashMap;
19 import java.util.Map;
20 import java.util.Optional;
21 import org.junit.AfterClass;
22 import org.junit.Before;
23 import org.junit.BeforeClass;
24 import org.junit.Test;
25 import org.opendaylight.yangtools.yang.common.QName;
26 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
27 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
28 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
29 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
30 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
31 import org.opendaylight.yangtools.yang.data.api.schema.DistinctNodeContainer;
32 import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
33 import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode;
34 import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode;
35 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
36 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
37 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer;
38 import org.opendaylight.yangtools.yang.data.api.schema.SystemMapNode;
39 import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
40 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
41 import org.opendaylight.yangtools.yang.data.tree.api.DataTree;
42 import org.opendaylight.yangtools.yang.data.tree.api.DataTreeCandidate;
43 import org.opendaylight.yangtools.yang.data.tree.api.DataTreeConfiguration;
44 import org.opendaylight.yangtools.yang.data.tree.api.DataTreeModification;
45 import org.opendaylight.yangtools.yang.data.tree.api.DataTreeSnapshot;
46 import org.opendaylight.yangtools.yang.data.tree.api.DataValidationFailedException;
47 import org.opendaylight.yangtools.yang.data.tree.impl.di.InMemoryDataTreeFactory;
48 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
49 import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils;
50
51 public class Bug4454Test {
52
53     private static final QName MASTER_CONTAINER_QNAME = QName
54             .create("urn:opendaylight:params:xml:ns:yang:list-constraints-validation-test-model", "2015-02-02",
55                     "master-container");
56     private static final QName MIN_MAX_LIST_QNAME = QName.create(MASTER_CONTAINER_QNAME, "min-max-list");
57     private static final QName MIN_MAX_LEAF_LIST_QNAME = QName.create(MASTER_CONTAINER_QNAME, "min-max-leaf-list");
58     private static final QName MIN_MAX_LIST_QNAME_NO_MINMAX = QName
59             .create(MASTER_CONTAINER_QNAME, "min-max-list-no-minmax");
60     private static final QName MIN_MAX_KEY_LEAF_QNAME = QName.create(MASTER_CONTAINER_QNAME, "min-max-key-leaf");
61     private static final QName MIN_MAX_VALUE_LEAF_QNAME = QName.create(MASTER_CONTAINER_QNAME, "min-max-value-leaf");
62     private static final QName PRESENCE_QNAME = QName.create(MASTER_CONTAINER_QNAME, "presence");
63
64     private static final YangInstanceIdentifier MASTER_CONTAINER_PATH =
65         YangInstanceIdentifier.of(MASTER_CONTAINER_QNAME);
66     private static final YangInstanceIdentifier MIN_MAX_LIST_PATH =
67         YangInstanceIdentifier.builder(MASTER_CONTAINER_PATH).node(MIN_MAX_LIST_QNAME).build();
68     private static final YangInstanceIdentifier PRESENCE_PATH = YangInstanceIdentifier.of(PRESENCE_QNAME);
69     private static final YangInstanceIdentifier PRESENCE_MIN_MAX_LIST_PATH = PRESENCE_PATH.node(MIN_MAX_LIST_QNAME);
70     private static final YangInstanceIdentifier MIN_MAX_LIST_NO_MINMAX_PATH =
71         YangInstanceIdentifier.builder(MASTER_CONTAINER_PATH).node(MIN_MAX_LIST_QNAME_NO_MINMAX).build();
72     private static final YangInstanceIdentifier MIN_MAX_LEAF_LIST_PATH =
73         YangInstanceIdentifier.builder(MASTER_CONTAINER_PATH).node(MIN_MAX_LEAF_LIST_QNAME).build();
74
75     private final MapEntryNode fooEntryNodeWithValue = Builders.mapEntryBuilder()
76         .withNodeIdentifier(NodeIdentifierWithPredicates.of(MIN_MAX_LIST_QNAME, MIN_MAX_KEY_LEAF_QNAME, "foo"))
77         .withChild(ImmutableNodes.leafNode(MIN_MAX_VALUE_LEAF_QNAME, "footest"))
78         .build();
79     private final MapEntryNode bazEntryNodeWithValue = Builders.mapEntryBuilder()
80         .withNodeIdentifier(NodeIdentifierWithPredicates.of(MIN_MAX_LIST_QNAME, MIN_MAX_KEY_LEAF_QNAME, "baz"))
81         .withChild(ImmutableNodes.leafNode(MIN_MAX_VALUE_LEAF_QNAME, "baztest"))
82         .build();
83     private final MapEntryNode fooEntryNode = ImmutableNodes.mapEntry(MIN_MAX_LIST_QNAME, MIN_MAX_KEY_LEAF_QNAME,
84             "foo");
85     private final MapEntryNode barEntryNode = ImmutableNodes.mapEntry(MIN_MAX_LIST_QNAME, MIN_MAX_KEY_LEAF_QNAME,
86             "bar");
87     private final MapEntryNode bazEntryNode = ImmutableNodes.mapEntry(MIN_MAX_LIST_QNAME, MIN_MAX_KEY_LEAF_QNAME,
88             "baz");
89     private final SystemMapNode mapNodeBazFuzWithNodes = ImmutableNodes.mapNodeBuilder()
90             .withNodeIdentifier(new NodeIdentifier(MIN_MAX_LIST_QNAME))
91             .withChild(bazEntryNode).withChild(bazEntryNodeWithValue).withChild(fooEntryNode)
92             .build();
93     private final SystemMapNode mapNodeFooWithNodes = ImmutableNodes.mapNodeBuilder()
94             .withNodeIdentifier(new NodeIdentifier(MIN_MAX_LIST_QNAME))
95             .withChild(fooEntryNode).withChild(fooEntryNodeWithValue).withChild(barEntryNode).withChild(bazEntryNode)
96             .build();
97     private final SystemMapNode mapNodeBar = ImmutableNodes.mapNodeBuilder()
98             .withNodeIdentifier(new NodeIdentifier(MIN_MAX_LIST_QNAME))
99             .withChild(barEntryNode).build();
100     private final SystemMapNode mapNodeBaz = ImmutableNodes.mapNodeBuilder()
101             .withNodeIdentifier(new NodeIdentifier(MIN_MAX_LIST_QNAME))
102             .withChild(bazEntryNode).build();
103
104     private static EffectiveModelContext schemaContext;
105
106     private DataTree inMemoryDataTree;
107
108     @BeforeClass
109     public static void beforeClass() {
110         schemaContext = YangParserTestUtils.parseYang("""
111             module Bug4454Test {
112               yang-version 1;
113               namespace "urn:opendaylight:params:xml:ns:yang:list-constraints-validation-test-model";
114               prefix "list-constraints-validation";
115
116               revision "2015-02-02" {
117                 description "Initial revision.";
118               }
119
120               container master-container {
121                 list min-max-list {
122                   min-elements 1;
123                   max-elements 3;
124                   key "min-max-key-leaf";
125                   leaf min-max-key-leaf {
126                     type string;
127                   }
128                   leaf min-max-value-leaf {
129                     type string;
130                   }
131                 }
132
133                 list min-max-list-no-minmax {
134                   key "min-max-key-leaf";
135                   leaf min-max-key-leaf {
136                     type string;
137                   }
138                 }
139
140                 leaf-list min-max-leaf-list {
141                   min-elements 0;
142                   max-elements 10;
143                   type string;
144                 }
145               }
146
147               container presence {
148                 presence "anchor point";
149
150                 list min-max-list {
151                   min-elements 2;
152                   max-elements 3;
153
154                   key "min-max-key-leaf";
155
156                   leaf min-max-key-leaf {
157                     type string;
158                   }
159                 }
160               }
161             }""");
162     }
163
164     @AfterClass
165     public static void afterClass() {
166         schemaContext = null;
167     }
168
169     @Before
170     public void prepare() throws DataValidationFailedException {
171         inMemoryDataTree =  new InMemoryDataTreeFactory().create(DataTreeConfiguration.DEFAULT_OPERATIONAL,
172             schemaContext);
173         final DataTreeSnapshot initialDataTreeSnapshot = inMemoryDataTree.takeSnapshot();
174         final DataTreeModification modificationTree = initialDataTreeSnapshot.newModification();
175
176         modificationTree.write(MASTER_CONTAINER_PATH, ImmutableNodes.containerNode(MASTER_CONTAINER_QNAME));
177         modificationTree.ready();
178         inMemoryDataTree.commit(inMemoryDataTree.prepare(modificationTree));
179     }
180
181     @Test
182     public void minMaxListDeleteWriteTest() throws DataValidationFailedException {
183         final DataTreeModification modificationTree1 = inMemoryDataTree.takeSnapshot().newModification();
184
185         Map<QName, Object> key = new HashMap<>();
186         key.put(MIN_MAX_KEY_LEAF_QNAME, "foo");
187
188         NodeIdentifierWithPredicates mapEntryPath2 = NodeIdentifierWithPredicates.of(MIN_MAX_LIST_QNAME , key);
189
190         final YangInstanceIdentifier minMaxLeafFoo = YangInstanceIdentifier.builder(MASTER_CONTAINER_PATH)
191                 .node(MIN_MAX_LIST_QNAME).node(mapEntryPath2).build();
192
193         key.clear();
194         key.put(MIN_MAX_KEY_LEAF_QNAME, "NON-EXISTING-LEAF");
195
196         mapEntryPath2 = NodeIdentifierWithPredicates.of(MIN_MAX_LIST_QNAME, key);
197
198         final YangInstanceIdentifier minMaxLeafNel = YangInstanceIdentifier.builder(MASTER_CONTAINER_PATH)
199                 .node(MIN_MAX_LIST_QNAME).node(mapEntryPath2).build();
200
201         final Map<QName, Object> keyTemp = new HashMap<>();
202         keyTemp.put(MIN_MAX_KEY_LEAF_QNAME, "baz");
203
204         NodeIdentifierWithPredicates mapEntryPathTest = NodeIdentifierWithPredicates.of(MIN_MAX_LIST_QNAME , keyTemp);
205
206         final YangInstanceIdentifier pathToBaz = YangInstanceIdentifier.builder(MASTER_CONTAINER_PATH)
207                 .node(MIN_MAX_LIST_QNAME).node(mapEntryPathTest).node(MIN_MAX_VALUE_LEAF_QNAME).build();
208
209         keyTemp.clear();
210         keyTemp.put(MIN_MAX_KEY_LEAF_QNAME, "bar");
211
212         mapEntryPathTest = NodeIdentifierWithPredicates.of(MIN_MAX_LIST_QNAME , keyTemp);
213
214         final YangInstanceIdentifier pathToBar = YangInstanceIdentifier.builder(MASTER_CONTAINER_PATH)
215                 .node(MIN_MAX_LIST_QNAME).node(mapEntryPathTest).node(MIN_MAX_VALUE_LEAF_QNAME).build();
216
217         keyTemp.clear();
218         keyTemp.put(MIN_MAX_KEY_LEAF_QNAME, "foo");
219
220         final NodeIdentifierWithPredicates mapEntryPathTestKey = NodeIdentifierWithPredicates.of(MIN_MAX_LIST_QNAME,
221             keyTemp);
222
223         final YangInstanceIdentifier pathToKeyFoo = YangInstanceIdentifier.builder(MASTER_CONTAINER_PATH)
224                 .node(MIN_MAX_LIST_QNAME).node(mapEntryPathTestKey).node(MIN_MAX_KEY_LEAF_QNAME).build();
225
226         final LeafNode<String> newNode = ImmutableNodes.leafNode(MIN_MAX_VALUE_LEAF_QNAME, "test");
227         final LeafNode<String> newNode1 = ImmutableNodes.leafNode(MIN_MAX_VALUE_LEAF_QNAME, "test1");
228         final LeafNode<String> newNode2 = ImmutableNodes.leafNode(MIN_MAX_VALUE_LEAF_QNAME, "test2");
229         final LeafNode<String> newNodekey = ImmutableNodes.leafNode(MIN_MAX_KEY_LEAF_QNAME, "foo");
230
231         assertFalse(inMemoryDataTree.toString().contains("list"));
232
233         DataTreeSnapshot snapshotAfterCommit = inMemoryDataTree.takeSnapshot();
234         Optional<NormalizedNode> minMaxListRead = snapshotAfterCommit.readNode(MIN_MAX_LIST_PATH);
235         assertFalse(minMaxListRead.isPresent());
236
237         modificationTree1.write(MIN_MAX_LIST_PATH, mapNodeFooWithNodes);
238         modificationTree1.write(MIN_MAX_LIST_PATH, mapNodeFooWithNodes);
239         modificationTree1.write(MIN_MAX_LIST_PATH, mapNodeFooWithNodes);
240         modificationTree1.merge(MIN_MAX_LIST_PATH, mapNodeBar);
241         modificationTree1.merge(MIN_MAX_LIST_PATH, mapNodeBaz);
242         modificationTree1.write(pathToKeyFoo, newNodekey);
243         modificationTree1.write(pathToBaz, newNode2);
244         modificationTree1.write(pathToBaz, newNode1);
245         modificationTree1.write(pathToBaz, newNode);
246         modificationTree1.delete(minMaxLeafFoo);
247         modificationTree1.delete(minMaxLeafNel);
248
249         modificationTree1.ready();
250         inMemoryDataTree.validate(modificationTree1);
251         final DataTreeCandidate prepare = inMemoryDataTree.prepare(modificationTree1);
252         inMemoryDataTree.commit(prepare);
253
254         DataTreeSnapshot test = inMemoryDataTree.takeSnapshot();
255         testLoop(test, "bar", "test");
256
257         DataTreeModification tempMod = test.newModification();
258         tempMod.write(pathToBaz, newNode2);
259         tempMod.write(pathToBaz, newNode1);
260         tempMod.merge(pathToBaz, newNode2);
261         tempMod.write(pathToBaz, newNode1);
262
263         tempMod.ready();
264         inMemoryDataTree.validate(tempMod);
265         final DataTreeCandidate prepare1 = inMemoryDataTree.prepare(tempMod);
266         inMemoryDataTree.commit(prepare1);
267
268         DataTreeSnapshot test1 = inMemoryDataTree.takeSnapshot();
269         testLoop(test1, "bar", "test1");
270
271         DataTreeModification tempMod1 = test1.newModification();
272         tempMod1.write(MIN_MAX_LIST_PATH, mapNodeFooWithNodes);
273
274         tempMod1.ready();
275         inMemoryDataTree.validate(tempMod1);
276         final DataTreeCandidate prepare2 = inMemoryDataTree.prepare(tempMod1);
277         inMemoryDataTree.commit(prepare2);
278
279         DataTreeSnapshot test2 = inMemoryDataTree.takeSnapshot();
280         minMaxListRead = test2.readNode(MIN_MAX_LIST_PATH);
281         assertTrue(minMaxListRead.isPresent());
282         assertEquals(3, ((NormalizedNodeContainer<?>) minMaxListRead.orElseThrow()).size());
283
284         DataTreeModification tempMod2 = test2.newModification();
285         tempMod2.write(MIN_MAX_LIST_PATH, mapNodeBaz);
286         tempMod2.write(pathToBaz, newNode2);
287
288         tempMod2.ready();
289         inMemoryDataTree.validate(tempMod2);
290         final DataTreeCandidate prepare3 = inMemoryDataTree.prepare(tempMod2);
291         inMemoryDataTree.commit(prepare3);
292
293         DataTreeSnapshot test3 = inMemoryDataTree.takeSnapshot();
294         minMaxListRead = test3.readNode(MIN_MAX_LIST_PATH);
295         assertTrue(minMaxListRead.isPresent());
296         assertEquals(1, ((NormalizedNodeContainer<?>) minMaxListRead.orElseThrow()).size());
297         assertThat(minMaxListRead.orElseThrow().body().toString(), containsString("test2"));
298
299         DataTreeModification tempMod3 = test3.newModification();
300         tempMod3.merge(MIN_MAX_LIST_PATH, mapNodeBar);
301         tempMod3.merge(pathToBar, newNode1);
302
303         tempMod3.ready();
304         inMemoryDataTree.validate(tempMod3);
305         final DataTreeCandidate prepare4 = inMemoryDataTree.prepare(tempMod3);
306         inMemoryDataTree.commit(prepare4);
307
308         DataTreeSnapshot test4 = inMemoryDataTree.takeSnapshot();
309         testLoop(test4, "test1", "test2");
310     }
311
312     @Test
313     public void minMaxLeafListPass() throws DataValidationFailedException {
314         final DataTreeModification modificationTree = inMemoryDataTree.takeSnapshot().newModification();
315
316         final NodeWithValue<?> barPath = new NodeWithValue<>(MIN_MAX_LIST_QNAME, "bar");
317         final NodeWithValue<?> gooPath = new NodeWithValue<>(MIN_MAX_LIST_QNAME, "goo");
318
319         final LeafSetEntryNode<Object> barLeafSetEntry = Builders.leafSetEntryBuilder()
320                 .withNodeIdentifier(barPath)
321                 .withValue("bar").build();
322         final LeafSetEntryNode<Object> gooLeafSetEntry = Builders.leafSetEntryBuilder()
323                 .withNodeIdentifier(gooPath)
324                 .withValue("goo").build();
325
326         final LeafSetNode<Object> fooLeafSetNode = Builders.leafSetBuilder()
327                 .withNodeIdentifier(new NodeIdentifier(MIN_MAX_LEAF_LIST_QNAME))
328                 .withChildValue("foo")
329                 .build();
330
331         modificationTree.write(MIN_MAX_LEAF_LIST_PATH, fooLeafSetNode);
332         modificationTree.write(MIN_MAX_LEAF_LIST_PATH.node(barPath), barLeafSetEntry);
333         modificationTree.ready();
334
335         inMemoryDataTree.validate(modificationTree);
336         final DataTreeCandidate prepare1 = inMemoryDataTree.prepare(modificationTree);
337         inMemoryDataTree.commit(prepare1);
338
339         DataTreeSnapshot test1 = inMemoryDataTree.takeSnapshot();
340
341         DataTreeModification tempMod1 = test1.newModification();
342         tempMod1.write(MIN_MAX_LEAF_LIST_PATH.node(gooPath), gooLeafSetEntry);
343         tempMod1.write(MIN_MAX_LEAF_LIST_PATH.node(barPath), barLeafSetEntry);
344         tempMod1.ready();
345
346         inMemoryDataTree.validate(tempMod1);
347         final DataTreeCandidate prepare2 = inMemoryDataTree.prepare(tempMod1);
348         inMemoryDataTree.commit(prepare2);
349
350         final DataTreeSnapshot snapshotAfterCommit = inMemoryDataTree.takeSnapshot();
351         final Optional<NormalizedNode> masterContainer = snapshotAfterCommit.readNode(MASTER_CONTAINER_PATH);
352         assertTrue(masterContainer.isPresent());
353         final Optional<NormalizedNodeContainer<?>> leafList = ((DistinctNodeContainer) masterContainer.orElseThrow())
354                 .findChildByArg(new NodeIdentifier(MIN_MAX_LEAF_LIST_QNAME));
355         assertTrue(leafList.isPresent());
356         assertEquals(3, leafList.orElseThrow().size());
357     }
358
359     @Test
360     public void minMaxListDeleteTest() throws DataValidationFailedException {
361         final DataTreeModification modificationTree = inMemoryDataTree.takeSnapshot().newModification();
362
363
364         NodeIdentifierWithPredicates mapEntryPath2 = NodeIdentifierWithPredicates.of(MIN_MAX_LIST_QNAME,
365             MIN_MAX_KEY_LEAF_QNAME, "foo");
366
367         final YangInstanceIdentifier minMaxLeafFoo = MASTER_CONTAINER_PATH
368                 .node(MIN_MAX_LIST_QNAME).node(mapEntryPath2);
369
370         mapEntryPath2 = NodeIdentifierWithPredicates.of(MIN_MAX_LIST_QNAME, MIN_MAX_KEY_LEAF_QNAME, "bar");
371
372         final YangInstanceIdentifier minMaxLeafBar = MASTER_CONTAINER_PATH
373                 .node(MIN_MAX_LIST_QNAME).node(mapEntryPath2);
374
375         mapEntryPath2 = NodeIdentifierWithPredicates.of(MIN_MAX_LIST_QNAME, MIN_MAX_KEY_LEAF_QNAME, "baz");
376
377         final YangInstanceIdentifier minMaxLeafBaz = MASTER_CONTAINER_PATH
378                 .node(MIN_MAX_LIST_QNAME).node(mapEntryPath2);
379
380         modificationTree.write(MIN_MAX_LIST_PATH, mapNodeFooWithNodes);
381         modificationTree.merge(MIN_MAX_LIST_PATH, mapNodeBar);
382         modificationTree.merge(MIN_MAX_LIST_PATH, mapNodeBaz);
383         modificationTree.delete(minMaxLeafFoo);
384         modificationTree.delete(minMaxLeafBar);
385         modificationTree.delete(minMaxLeafBaz);
386
387         modificationTree.ready();
388
389         inMemoryDataTree.validate(modificationTree);
390         final DataTreeCandidate prepare = inMemoryDataTree.prepare(modificationTree);
391         inMemoryDataTree.commit(prepare);
392
393         // Empty list should have disappeared, along with the container, as we are not enforcing root
394         final NormalizedNode data = inMemoryDataTree.takeSnapshot().readNode(YangInstanceIdentifier.of()).orElseThrow();
395         assertTrue(data instanceof ContainerNode);
396         assertEquals(0, ((ContainerNode) data).size());
397     }
398
399     @Test
400     public void minMaxListDeleteExceptionTest() throws DataValidationFailedException {
401         final DataTreeModification modificationTree = inMemoryDataTree.takeSnapshot().newModification();
402
403         NodeIdentifierWithPredicates mapEntryPath2 = NodeIdentifierWithPredicates.of(MIN_MAX_LIST_QNAME,
404             MIN_MAX_KEY_LEAF_QNAME, "foo");
405
406         final YangInstanceIdentifier minMaxLeafFoo = PRESENCE_PATH.node(MIN_MAX_LIST_QNAME).node(mapEntryPath2);
407
408         mapEntryPath2 = NodeIdentifierWithPredicates.of(MIN_MAX_LIST_QNAME, MIN_MAX_KEY_LEAF_QNAME, "bar");
409
410         final YangInstanceIdentifier minMaxLeafBar = PRESENCE_PATH.node(MIN_MAX_LIST_QNAME).node(mapEntryPath2);
411
412         mapEntryPath2 = NodeIdentifierWithPredicates.of(MIN_MAX_LIST_QNAME, MIN_MAX_KEY_LEAF_QNAME, "baz");
413
414         final YangInstanceIdentifier minMaxLeafBaz = PRESENCE_PATH.node(MIN_MAX_LIST_QNAME).node(mapEntryPath2);
415
416         modificationTree.write(PRESENCE_PATH, ImmutableNodes.containerNode(PRESENCE_QNAME));
417         modificationTree.write(PRESENCE_MIN_MAX_LIST_PATH, mapNodeFooWithNodes);
418         modificationTree.merge(PRESENCE_MIN_MAX_LIST_PATH, mapNodeBar);
419         modificationTree.merge(PRESENCE_MIN_MAX_LIST_PATH, mapNodeBaz);
420         modificationTree.delete(minMaxLeafFoo);
421         modificationTree.delete(minMaxLeafBar);
422         modificationTree.delete(minMaxLeafBaz);
423
424         try {
425             // Unlike minMaxListDeleteTest(), presence container enforces the list to be present
426             modificationTree.ready();
427             fail("Should have failed with IAE");
428         } catch (IllegalArgumentException e) {
429             assertEquals("""
430                 Node (urn:opendaylight:params:xml:ns:yang:list-constraints-validation-test-model?revision=2015-02-02)\
431                 presence is missing mandatory descendant /(urn:opendaylight:params:xml:ns:yang:list-constraints-\
432                 validation-test-model?revision=2015-02-02)min-max-list""", e.getMessage());
433         }
434     }
435
436     @Test
437     public void minMaxListNoMinMaxDeleteTest() throws DataValidationFailedException {
438         final MapEntryNode fooEntryNoMinMaxNode =
439                 ImmutableNodes.mapEntry(MIN_MAX_LIST_QNAME_NO_MINMAX, MIN_MAX_KEY_LEAF_QNAME, "foo");
440         final SystemMapNode mapNode1 = ImmutableNodes.mapNodeBuilder()
441                 .withNodeIdentifier(new NodeIdentifier(MIN_MAX_LIST_QNAME_NO_MINMAX))
442                 .withChild(fooEntryNoMinMaxNode).build();
443
444         final DataTreeModification modificationTree = inMemoryDataTree.takeSnapshot().newModification();
445
446         Map<QName, Object> key = new HashMap<>();
447         key.put(MIN_MAX_KEY_LEAF_QNAME, "foo");
448
449         NodeIdentifierWithPredicates mapEntryPath2 = NodeIdentifierWithPredicates.of(MIN_MAX_LIST_QNAME_NO_MINMAX, key);
450
451         final YangInstanceIdentifier minMaxLeafFoo = MASTER_CONTAINER_PATH
452                 .node(MIN_MAX_LIST_QNAME_NO_MINMAX).node(mapEntryPath2);
453
454         key.clear();
455         key.put(MIN_MAX_KEY_LEAF_QNAME, "non-existing-leaf");
456
457         mapEntryPath2 = NodeIdentifierWithPredicates.of(MIN_MAX_LIST_QNAME_NO_MINMAX, key);
458
459         YangInstanceIdentifier minMaxLeafNel = YangInstanceIdentifier.builder(MASTER_CONTAINER_PATH)
460                 .node(MIN_MAX_LIST_QNAME_NO_MINMAX).node(mapEntryPath2).build();
461
462         modificationTree.write(MIN_MAX_LIST_NO_MINMAX_PATH, mapNode1);
463         modificationTree.delete(minMaxLeafFoo);
464         modificationTree.delete(minMaxLeafNel);
465
466         modificationTree.ready();
467
468         inMemoryDataTree.validate(modificationTree);
469         final DataTreeCandidate prepare = inMemoryDataTree.prepare(modificationTree);
470         inMemoryDataTree.commit(prepare);
471
472         final DataTreeSnapshot snapshotAfterCommit = inMemoryDataTree.takeSnapshot();
473         final Optional<NormalizedNode> minMaxListRead = snapshotAfterCommit.readNode(MIN_MAX_LIST_NO_MINMAX_PATH);
474
475         // Empty list should have disappeared
476         assertFalse(minMaxListRead.isPresent());
477     }
478
479     private static void testLoop(final DataTreeSnapshot snapshot, final String first, final String second) {
480         Optional<NormalizedNode> minMaxListRead = snapshot.readNode(MIN_MAX_LIST_PATH);
481         assertTrue(minMaxListRead.isPresent());
482         assertEquals(2, ((NormalizedNodeContainer<?>) minMaxListRead.orElseThrow()).size());
483
484         for (Object collectionChild : (Collection<?>) minMaxListRead.orElseThrow().body()) {
485             if (collectionChild.toString().contains(first)) {
486                 assertTrue(collectionChild.toString().contains(first));
487             } else {
488                 assertTrue(collectionChild.toString().contains(second));
489             }
490         }
491     }
492 }