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