07e535285e15b68cb3bcced89c57825599024eea
[yangtools.git] / data / yang-data-tree-ri / src / test / java / org / opendaylight / yangtools / yang / data / tree / impl / ListConstraintsValidation.java
1 /*
2  * Copyright (c) 2015 Cisco Systems, Inc. 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 org.hamcrest.CoreMatchers.instanceOf;
11 import static org.hamcrest.MatcherAssert.assertThat;
12 import static org.junit.Assert.assertEquals;
13 import static org.junit.Assert.assertNotNull;
14 import static org.junit.Assert.assertThrows;
15 import static org.junit.Assert.assertTrue;
16
17 import java.util.List;
18 import java.util.Optional;
19 import org.junit.AfterClass;
20 import org.junit.Before;
21 import org.junit.BeforeClass;
22 import org.junit.Test;
23 import org.opendaylight.yangtools.yang.common.ErrorSeverity;
24 import org.opendaylight.yangtools.yang.common.ErrorTag;
25 import org.opendaylight.yangtools.yang.common.ErrorType;
26 import org.opendaylight.yangtools.yang.common.QName;
27 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
28 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
29 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
30 import org.opendaylight.yangtools.yang.data.api.YangNetconfError;
31 import org.opendaylight.yangtools.yang.data.api.YangNetconfErrorAware;
32 import org.opendaylight.yangtools.yang.data.api.schema.DistinctNodeContainer;
33 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
34 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
35 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
36 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer;
37 import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListNode;
38 import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
39 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
40 import org.opendaylight.yangtools.yang.data.tree.api.DataTree;
41 import org.opendaylight.yangtools.yang.data.tree.api.DataTreeCandidate;
42 import org.opendaylight.yangtools.yang.data.tree.api.DataTreeConfiguration;
43 import org.opendaylight.yangtools.yang.data.tree.api.DataTreeModification;
44 import org.opendaylight.yangtools.yang.data.tree.api.DataTreeSnapshot;
45 import org.opendaylight.yangtools.yang.data.tree.api.DataValidationFailedException;
46 import org.opendaylight.yangtools.yang.data.tree.impl.di.InMemoryDataTreeFactory;
47 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
48 import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils;
49
50 public class ListConstraintsValidation {
51     private static final QName MASTER_CONTAINER_QNAME = QName.create(
52             "urn:opendaylight:params:xml:ns:yang:list-constraints-validation-test-model", "2015-02-02",
53             "master-container");
54     private static final QName MIN_MAX_LIST_QNAME = QName.create(MASTER_CONTAINER_QNAME, "min-max-list");
55     private static final QName MIN_MAX_KEY_LEAF_QNAME = QName.create(MASTER_CONTAINER_QNAME, "min-max-key-leaf");
56     private static final QName UNBOUNDED_LIST_QNAME = QName.create(MASTER_CONTAINER_QNAME, "unbounded-list");
57     private static final QName UNBOUNDED_KEY_LEAF_QNAME = QName.create(MASTER_CONTAINER_QNAME, "unbounded-key-leaf");
58     private static final QName MIN_MAX_LEAF_LIST_QNAME = QName.create(MASTER_CONTAINER_QNAME, "min-max-leaf-list");
59     private static final QName UNBOUNDED_LEAF_LIST_QNAME = QName.create(MASTER_CONTAINER_QNAME, "unbounded-leaf-list");
60     private static final QName UNKEYED_LIST_QNAME = QName.create(MASTER_CONTAINER_QNAME, "unkeyed-list");
61     private static final QName UNKEYED_LEAF_QNAME = QName.create(MASTER_CONTAINER_QNAME, "unkeyed-leaf");
62
63     private static final YangInstanceIdentifier MASTER_CONTAINER_PATH = YangInstanceIdentifier
64             .of(MASTER_CONTAINER_QNAME);
65     private static final YangInstanceIdentifier MIN_MAX_LIST_PATH = YangInstanceIdentifier
66             .builder(MASTER_CONTAINER_PATH).node(MIN_MAX_LIST_QNAME).build();
67     private static final YangInstanceIdentifier UNBOUNDED_LIST_PATH = YangInstanceIdentifier
68             .builder(MASTER_CONTAINER_PATH).node(UNBOUNDED_LIST_QNAME).build();
69     private static final YangInstanceIdentifier MIN_MAX_LEAF_LIST_PATH = YangInstanceIdentifier
70             .builder(MASTER_CONTAINER_PATH).node(MIN_MAX_LEAF_LIST_QNAME).build();
71     private static final YangInstanceIdentifier UNBOUNDED_LEAF_LIST_PATH = YangInstanceIdentifier
72             .builder(MASTER_CONTAINER_PATH).node(UNBOUNDED_LEAF_LIST_QNAME).build();
73     private static final YangInstanceIdentifier UNKEYED_LIST_PATH = YangInstanceIdentifier
74             .builder(MASTER_CONTAINER_PATH).node(UNKEYED_LIST_QNAME).build();
75
76     private static EffectiveModelContext schemaContext;
77
78     private DataTree inMemoryDataTree;
79
80     @BeforeClass
81     public static void beforeClass() {
82         schemaContext = YangParserTestUtils.parseYang("""
83             module list-constraints-validation-test-model  {
84               yang-version 1;
85               namespace "urn:opendaylight:params:xml:ns:yang:list-constraints-validation-test-model";
86               prefix "list-constraints-validation";
87
88               revision "2015-02-02" {
89                 description "Initial revision.";
90               }
91
92               container master-container {
93                 list min-max-list {
94                   min-elements 2;
95                   max-elements 3;
96                   key "min-max-key-leaf";
97                   leaf min-max-key-leaf {
98                     type string;
99                   }
100                 }
101
102                 list unbounded-list {
103                   key "unbounded-key-leaf";
104                   leaf unbounded-key-leaf {
105                     type int8;
106                   }
107                 }
108
109                 leaf-list min-max-leaf-list {
110                   min-elements 1;
111                   max-elements 3;
112                   type string;
113                 }
114
115                 leaf-list unbounded-leaf-list {
116                   type string;
117                 }
118
119                 list unkeyed-list {
120                   max-elements 1;
121                   leaf unkeyed-leaf {
122                     type string;
123                   }
124                 }
125               }
126             }""");
127     }
128
129     @AfterClass
130     public static void afterClass() {
131         schemaContext = null;
132     }
133
134     @Before
135     public void prepare() throws DataValidationFailedException {
136         inMemoryDataTree = new InMemoryDataTreeFactory().create(DataTreeConfiguration.DEFAULT_OPERATIONAL,
137             schemaContext);
138         final DataTreeSnapshot initialDataTreeSnapshot = inMemoryDataTree.takeSnapshot();
139         final DataTreeModification modificationTree = initialDataTreeSnapshot.newModification();
140
141         modificationTree.write(MASTER_CONTAINER_PATH, ImmutableNodes.containerNode(MASTER_CONTAINER_QNAME));
142         modificationTree.ready();
143         inMemoryDataTree.commit(inMemoryDataTree.prepare(modificationTree));
144     }
145
146     @Test
147     public void minMaxListTestPass() throws DataValidationFailedException {
148
149         final MapEntryNode fooEntryNode = ImmutableNodes.mapEntry(MIN_MAX_LIST_QNAME, MIN_MAX_KEY_LEAF_QNAME, "foo");
150         final MapEntryNode barEntryNode = ImmutableNodes.mapEntry(MIN_MAX_LIST_QNAME, MIN_MAX_KEY_LEAF_QNAME, "bar");
151         final MapNode mapNode1 = ImmutableNodes.mapNodeBuilder()
152                 .withNodeIdentifier(new NodeIdentifier(MIN_MAX_LIST_QNAME))
153                 .withChild(fooEntryNode).build();
154         final MapNode mapNode2 = ImmutableNodes.mapNodeBuilder()
155                 .withNodeIdentifier(new NodeIdentifier(MIN_MAX_LIST_QNAME))
156                 .withChild(barEntryNode).build();
157
158         final DataTreeModification modificationTree = inMemoryDataTree.takeSnapshot().newModification();
159         modificationTree.write(MIN_MAX_LIST_PATH, mapNode1);
160         modificationTree.merge(MIN_MAX_LIST_PATH, mapNode2);
161         modificationTree.ready();
162
163         inMemoryDataTree.validate(modificationTree);
164         final DataTreeCandidate prepare = inMemoryDataTree.prepare(modificationTree);
165         inMemoryDataTree.commit(prepare);
166
167         final DataTreeSnapshot snapshotAfterCommit = inMemoryDataTree.takeSnapshot();
168         final Optional<NormalizedNode> minMaxListRead = snapshotAfterCommit.readNode(MIN_MAX_LIST_PATH);
169         assertTrue(minMaxListRead.isPresent());
170         assertEquals(2, ((NormalizedNodeContainer<?>) minMaxListRead.orElseThrow()).size());
171     }
172
173     @Test(expected = DataValidationFailedException.class)
174     public void minMaxListFail() throws DataValidationFailedException {
175         DataTreeModification modificationTree = inMemoryDataTree.takeSnapshot().newModification();
176
177         final MapEntryNode fooEntryNode = ImmutableNodes.mapEntry(MIN_MAX_LIST_QNAME, MIN_MAX_KEY_LEAF_QNAME, "foo");
178         final MapEntryNode barEntryNode = ImmutableNodes.mapEntry(MIN_MAX_LIST_QNAME, MIN_MAX_KEY_LEAF_QNAME, "bar");
179         final MapEntryNode gooEntryNode = ImmutableNodes.mapEntry(MIN_MAX_LIST_QNAME, MIN_MAX_KEY_LEAF_QNAME, "goo");
180         final MapNode mapNode = ImmutableNodes.mapNodeBuilder()
181                 .withNodeIdentifier(new NodeIdentifier(MIN_MAX_LIST_QNAME))
182                 .withChild(fooEntryNode).build();
183
184         final YangInstanceIdentifier fooPath = MIN_MAX_LIST_PATH.node(fooEntryNode.name());
185         final YangInstanceIdentifier barPath = MIN_MAX_LIST_PATH.node(barEntryNode.name());
186         final YangInstanceIdentifier gooPath = MIN_MAX_LIST_PATH.node(gooEntryNode.name());
187
188         modificationTree.write(MIN_MAX_LIST_PATH, mapNode);
189         modificationTree.merge(barPath, barEntryNode);
190         modificationTree.write(gooPath, gooEntryNode);
191         modificationTree.delete(gooPath);
192         modificationTree.ready();
193
194         inMemoryDataTree.validate(modificationTree);
195         DataTreeCandidate prepare1 = inMemoryDataTree.prepare(modificationTree);
196         inMemoryDataTree.commit(prepare1);
197
198         DataTreeSnapshot snapshotAfterCommit = inMemoryDataTree.takeSnapshot();
199         Optional<NormalizedNode> minMaxListRead = snapshotAfterCommit.readNode(MIN_MAX_LIST_PATH);
200         assertTrue(minMaxListRead.isPresent());
201         assertEquals(2, ((NormalizedNodeContainer<?>) minMaxListRead.orElseThrow()).size());
202
203         modificationTree = inMemoryDataTree.takeSnapshot().newModification();
204         modificationTree.write(gooPath, gooEntryNode);
205         modificationTree.ready();
206
207         inMemoryDataTree.validate(modificationTree);
208         prepare1 = inMemoryDataTree.prepare(modificationTree);
209         inMemoryDataTree.commit(prepare1);
210
211         snapshotAfterCommit = inMemoryDataTree.takeSnapshot();
212         minMaxListRead = snapshotAfterCommit.readNode(MIN_MAX_LIST_PATH);
213         assertTrue(minMaxListRead.isPresent());
214         assertEquals(3, ((NormalizedNodeContainer<?>) minMaxListRead.orElseThrow()).size());
215
216         modificationTree = inMemoryDataTree.takeSnapshot().newModification();
217
218         modificationTree.delete(gooPath);
219         modificationTree.delete(fooPath);
220         modificationTree.ready();
221
222         inMemoryDataTree.validate(modificationTree);
223     }
224
225     @Test
226     public void minMaxLeafListPass() throws DataValidationFailedException {
227         final DataTreeModification modificationTree = inMemoryDataTree.takeSnapshot().newModification();
228
229         final NodeWithValue<Object> barPath = new NodeWithValue<>(MIN_MAX_LIST_QNAME, "bar");
230         final NodeWithValue<Object> gooPath = new NodeWithValue<>(MIN_MAX_LIST_QNAME, "goo");
231
232         modificationTree.write(MIN_MAX_LEAF_LIST_PATH, Builders.leafSetBuilder()
233             .withNodeIdentifier(new NodeIdentifier(MIN_MAX_LEAF_LIST_QNAME))
234             .withChildValue("foo")
235             .build());
236         modificationTree.write(MIN_MAX_LEAF_LIST_PATH.node(barPath), Builders.leafSetEntryBuilder()
237             .withNodeIdentifier(barPath)
238             .withValue("bar")
239             .build());
240         modificationTree.merge(MIN_MAX_LEAF_LIST_PATH.node(gooPath), Builders.leafSetEntryBuilder()
241             .withNodeIdentifier(gooPath)
242             .withValue("goo")
243             .build());
244         modificationTree.delete(MIN_MAX_LEAF_LIST_PATH.node(gooPath));
245         modificationTree.ready();
246
247         inMemoryDataTree.validate(modificationTree);
248         final DataTreeCandidate prepare1 = inMemoryDataTree.prepare(modificationTree);
249         inMemoryDataTree.commit(prepare1);
250
251         final DataTreeSnapshot snapshotAfterCommit = inMemoryDataTree.takeSnapshot();
252         final Optional<NormalizedNode> masterContainer = snapshotAfterCommit.readNode(MASTER_CONTAINER_PATH);
253         assertTrue(masterContainer.isPresent());
254         final NormalizedNodeContainer<?> leafList =
255             (NormalizedNodeContainer<?>) ((DistinctNodeContainer) masterContainer.orElseThrow())
256                 .childByArg(new NodeIdentifier(MIN_MAX_LEAF_LIST_QNAME));
257         assertNotNull(leafList);
258         assertEquals(2, leafList.size());
259     }
260
261     @Test
262     public void minMaxLeafListFail() {
263         final DataTreeModification modificationTree = inMemoryDataTree.takeSnapshot().newModification();
264
265         final NodeWithValue<Object> barPath = new NodeWithValue<>(MIN_MAX_LIST_QNAME, "bar");
266         final NodeWithValue<Object> gooPath = new NodeWithValue<>(MIN_MAX_LIST_QNAME, "goo");
267         final NodeWithValue<Object> fuuPath = new NodeWithValue<>(MIN_MAX_LIST_QNAME, "fuu");
268
269         modificationTree.write(MIN_MAX_LEAF_LIST_PATH, Builders.leafSetBuilder()
270             .withNodeIdentifier(new NodeIdentifier(MIN_MAX_LEAF_LIST_QNAME))
271             .withChildValue("foo")
272             .build());
273         modificationTree.write(MIN_MAX_LEAF_LIST_PATH.node(barPath), Builders.leafSetEntryBuilder()
274             .withNodeIdentifier(barPath)
275             .withValue("bar")
276             .build());
277         modificationTree.merge(MIN_MAX_LEAF_LIST_PATH.node(gooPath), Builders.leafSetEntryBuilder()
278             .withNodeIdentifier(gooPath)
279             .withValue("goo")
280             .build());
281         modificationTree.write(MIN_MAX_LEAF_LIST_PATH.node(fuuPath), Builders.leafSetEntryBuilder()
282             .withNodeIdentifier(fuuPath)
283             .withValue("fuu")
284             .build());
285
286         final MinMaxElementsValidationFailedException ex = assertThrows(MinMaxElementsValidationFailedException.class,
287             () -> modificationTree.ready());
288         assertEquals("(urn:opendaylight:params:xml:ns:yang:list-constraints-validation-test-model?"
289             + "revision=2015-02-02)min-max-leaf-list has too many elements (4), can have at most 3",
290             ex.getMessage());
291         assertTooManyElements(ex);
292     }
293
294     @Test
295     public void unkeyedListTestPass() throws DataValidationFailedException {
296         final DataTreeModification modificationTree = inMemoryDataTree.takeSnapshot().newModification();
297
298         final UnkeyedListNode unkeyedListNode = Builders.unkeyedListBuilder()
299             .withNodeIdentifier(new NodeIdentifier(UNKEYED_LIST_QNAME))
300             .withValue(List.of(Builders.unkeyedListEntryBuilder()
301                 .withNodeIdentifier(new NodeIdentifier(UNKEYED_LEAF_QNAME))
302                 .withChild(ImmutableNodes.leafNode(UNKEYED_LEAF_QNAME, "foo"))
303                 .build()))
304             .build();
305
306         modificationTree.write(MASTER_CONTAINER_PATH, ImmutableNodes.containerNode(MASTER_CONTAINER_QNAME));
307         modificationTree.merge(UNKEYED_LIST_PATH, unkeyedListNode);
308         modificationTree.ready();
309
310         inMemoryDataTree.validate(modificationTree);
311         final DataTreeCandidate prepare1 = inMemoryDataTree.prepare(modificationTree);
312         inMemoryDataTree.commit(prepare1);
313
314         final DataTreeSnapshot snapshotAfterCommit = inMemoryDataTree.takeSnapshot();
315         final Optional<NormalizedNode> unkeyedListRead = snapshotAfterCommit.readNode(UNKEYED_LIST_PATH);
316         assertTrue(unkeyedListRead.isPresent());
317         assertEquals(1, ((UnkeyedListNode) unkeyedListRead.orElseThrow()).size());
318     }
319
320     @Test
321     public void unkeyedListTestFail() {
322         final DataTreeModification modificationTree = inMemoryDataTree.takeSnapshot().newModification();
323
324         modificationTree.write(UNKEYED_LIST_PATH, Builders.unkeyedListBuilder()
325             .withNodeIdentifier(new NodeIdentifier(UNKEYED_LIST_QNAME))
326             .withValue(List.of(
327                 Builders.unkeyedListEntryBuilder()
328                     .withNodeIdentifier(new NodeIdentifier(UNKEYED_LEAF_QNAME))
329                     .withChild(ImmutableNodes.leafNode(UNKEYED_LEAF_QNAME, "foo"))
330                     .build(),
331                 Builders.unkeyedListEntryBuilder()
332                     .withNodeIdentifier(new NodeIdentifier(UNKEYED_LEAF_QNAME))
333                     .withChild(ImmutableNodes.leafNode(UNKEYED_LEAF_QNAME, "bar"))
334                     .build()))
335             .build());
336         final MinMaxElementsValidationFailedException ex = assertThrows(MinMaxElementsValidationFailedException.class,
337             () -> modificationTree.ready());
338         assertEquals("(urn:opendaylight:params:xml:ns:yang:list-constraints-validation-test-model?"
339             + "revision=2015-02-02)unkeyed-list has too many elements (2), can have at most 1", ex.getMessage());
340         assertTooManyElements(ex);
341     }
342
343     static void assertTooFewElements(final Exception ex) {
344         assertOperationFailed(ex, "too-few-elements");
345     }
346
347     static void assertTooManyElements(final Exception ex) {
348         assertOperationFailed(ex, "too-many-elements");
349     }
350
351     private static void assertOperationFailed(final Exception ex, final String expectedAppTag) {
352         assertThat(ex, instanceOf(YangNetconfErrorAware.class));
353         final List<YangNetconfError> errors = ((YangNetconfErrorAware) ex).getNetconfErrors();
354         assertEquals(1, errors.size());
355         final YangNetconfError error = errors.get(0);
356         assertEquals(ErrorSeverity.ERROR, error.severity());
357         assertEquals(ErrorType.APPLICATION, error.type());
358         assertEquals(ErrorTag.OPERATION_FAILED, error.tag());
359         assertEquals(expectedAppTag, error.appTag());
360     }
361 }