2 * Copyright (c) 2015 Cisco Systems, Inc. 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.yangtools.yang.data.tree.impl;
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;
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;
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",
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");
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();
68 private static EffectiveModelContext schemaContext;
70 private DataTree inMemoryDataTree;
73 static void beforeClass() {
74 schemaContext = YangParserTestUtils.parseYang("""
75 module list-constraints-validation-test-model {
77 namespace "urn:opendaylight:params:xml:ns:yang:list-constraints-validation-test-model";
78 prefix "list-constraints-validation";
80 revision "2015-02-02" {
81 description "Initial revision.";
84 container master-container {
88 key "min-max-key-leaf";
89 leaf min-max-key-leaf {
95 key "unbounded-key-leaf";
96 leaf unbounded-key-leaf {
101 leaf-list min-max-leaf-list {
107 leaf-list unbounded-leaf-list {
122 static void afterClass() {
123 schemaContext = null;
127 void prepare() throws DataValidationFailedException {
128 inMemoryDataTree = new InMemoryDataTreeFactory().create(DataTreeConfiguration.DEFAULT_OPERATIONAL,
130 final var initialDataTreeSnapshot = inMemoryDataTree.takeSnapshot();
131 final var modificationTree = initialDataTreeSnapshot.newModification();
133 modificationTree.write(MASTER_CONTAINER_PATH, ImmutableNodes.newContainerBuilder()
134 .withNodeIdentifier(new NodeIdentifier(MASTER_CONTAINER_QNAME))
136 modificationTree.ready();
137 inMemoryDataTree.commit(inMemoryDataTree.prepare(modificationTree));
141 void minMaxListTestPass() throws DataValidationFailedException {
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();
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();
157 inMemoryDataTree.validate(modificationTree);
158 final var prepare = inMemoryDataTree.prepare(modificationTree);
159 inMemoryDataTree.commit(prepare);
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());
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))
175 void minMaxListFail() throws DataValidationFailedException {
176 assertThrows(DataValidationFailedException.class, () -> {
177 var modificationTree = inMemoryDataTree.takeSnapshot().newModification();
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)
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());
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();
197 inMemoryDataTree.validate(modificationTree);
198 var prepare1 = inMemoryDataTree.prepare(modificationTree);
199 inMemoryDataTree.commit(prepare1);
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());
206 modificationTree = inMemoryDataTree.takeSnapshot().newModification();
207 modificationTree.write(gooPath, gooEntryNode);
208 modificationTree.ready();
210 inMemoryDataTree.validate(modificationTree);
211 prepare1 = inMemoryDataTree.prepare(modificationTree);
212 inMemoryDataTree.commit(prepare1);
214 snapshotAfterCommit = inMemoryDataTree.takeSnapshot();
215 minMaxListRead = snapshotAfterCommit.readNode(MIN_MAX_LIST_PATH);
216 assertTrue(minMaxListRead.isPresent());
217 assertEquals(3, ((NormalizedNodeContainer<?>) minMaxListRead.orElseThrow()).size());
219 modificationTree = inMemoryDataTree.takeSnapshot().newModification();
221 modificationTree.delete(gooPath);
222 modificationTree.delete(fooPath);
223 modificationTree.ready();
225 inMemoryDataTree.validate(modificationTree);
230 void minMaxLeafListPass() throws DataValidationFailedException {
231 final var modificationTree = inMemoryDataTree.takeSnapshot().newModification();
233 final var barPath = new NodeWithValue<>(MIN_MAX_LIST_QNAME, "bar");
234 final var gooPath = new NodeWithValue<>(MIN_MAX_LIST_QNAME, "goo");
236 modificationTree.write(MIN_MAX_LEAF_LIST_PATH, ImmutableNodes.newSystemLeafSetBuilder()
237 .withNodeIdentifier(new NodeIdentifier(MIN_MAX_LEAF_LIST_QNAME))
238 .withChildValue("foo")
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();
245 inMemoryDataTree.validate(modificationTree);
246 final var prepare1 = inMemoryDataTree.prepare(modificationTree);
247 inMemoryDataTree.commit(prepare1);
249 final var snapshotAfterCommit = inMemoryDataTree.takeSnapshot();
250 final var masterContainer = snapshotAfterCommit.readNode(MASTER_CONTAINER_PATH);
251 assertTrue(masterContainer.isPresent());
253 (NormalizedNodeContainer<?>) ((DistinctNodeContainer) masterContainer.orElseThrow())
254 .childByArg(new NodeIdentifier(MIN_MAX_LEAF_LIST_QNAME));
255 assertNotNull(leafList);
256 assertEquals(2, leafList.size());
260 void minMaxLeafListFail() {
261 final var modificationTree = inMemoryDataTree.takeSnapshot().newModification();
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");
267 modificationTree.write(MIN_MAX_LEAF_LIST_PATH, ImmutableNodes.newSystemLeafSetBuilder()
268 .withNodeIdentifier(new NodeIdentifier(MIN_MAX_LEAF_LIST_QNAME))
269 .withChildValue("foo")
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));
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",
280 assertTooManyElements(ex);
284 void unkeyedListTestPass() throws DataValidationFailedException {
285 final var modificationTree = inMemoryDataTree.takeSnapshot().newModification();
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"))
295 modificationTree.write(MASTER_CONTAINER_PATH, ImmutableNodes.newContainerBuilder()
296 .withNodeIdentifier(new NodeIdentifier(MASTER_CONTAINER_QNAME))
298 modificationTree.merge(UNKEYED_LIST_PATH, unkeyedListNode);
299 modificationTree.ready();
301 inMemoryDataTree.validate(modificationTree);
302 final var prepare1 = inMemoryDataTree.prepare(modificationTree);
303 inMemoryDataTree.commit(prepare1);
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());
312 void unkeyedListTestFail() {
313 final var modificationTree = inMemoryDataTree.takeSnapshot().newModification();
315 modificationTree.write(UNKEYED_LIST_PATH, ImmutableNodes.newUnkeyedListBuilder()
316 .withNodeIdentifier(new NodeIdentifier(UNKEYED_LIST_QNAME))
318 ImmutableNodes.newUnkeyedListEntryBuilder()
319 .withNodeIdentifier(new NodeIdentifier(UNKEYED_LEAF_QNAME))
320 .withChild(ImmutableNodes.leafNode(UNKEYED_LEAF_QNAME, "foo"))
322 ImmutableNodes.newUnkeyedListEntryBuilder()
323 .withNodeIdentifier(new NodeIdentifier(UNKEYED_LEAF_QNAME))
324 .withChild(ImmutableNodes.leafNode(UNKEYED_LEAF_QNAME, "bar"))
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);
334 static void assertTooFewElements(final Exception ex) {
335 assertOperationFailed(ex, "too-few-elements");
338 static void assertTooManyElements(final Exception ex) {
339 assertOperationFailed(ex, "too-many-elements");
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());