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.NodeWithValue;
28 import org.opendaylight.yangtools.yang.data.api.YangNetconfErrorAware;
29 import org.opendaylight.yangtools.yang.data.api.schema.DistinctNodeContainer;
30 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer;
31 import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListNode;
32 import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
33 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
34 import org.opendaylight.yangtools.yang.data.tree.api.DataTree;
35 import org.opendaylight.yangtools.yang.data.tree.api.DataTreeConfiguration;
36 import org.opendaylight.yangtools.yang.data.tree.api.DataValidationFailedException;
37 import org.opendaylight.yangtools.yang.data.tree.impl.di.InMemoryDataTreeFactory;
38 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
39 import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils;
41 class ListConstraintsValidation {
42 private static final QName MASTER_CONTAINER_QNAME = QName.create(
43 "urn:opendaylight:params:xml:ns:yang:list-constraints-validation-test-model", "2015-02-02",
45 private static final QName MIN_MAX_LIST_QNAME = QName.create(MASTER_CONTAINER_QNAME, "min-max-list");
46 private static final QName MIN_MAX_KEY_LEAF_QNAME = QName.create(MASTER_CONTAINER_QNAME, "min-max-key-leaf");
47 private static final QName UNBOUNDED_LIST_QNAME = QName.create(MASTER_CONTAINER_QNAME, "unbounded-list");
48 private static final QName UNBOUNDED_KEY_LEAF_QNAME = QName.create(MASTER_CONTAINER_QNAME, "unbounded-key-leaf");
49 private static final QName MIN_MAX_LEAF_LIST_QNAME = QName.create(MASTER_CONTAINER_QNAME, "min-max-leaf-list");
50 private static final QName UNBOUNDED_LEAF_LIST_QNAME = QName.create(MASTER_CONTAINER_QNAME, "unbounded-leaf-list");
51 private static final QName UNKEYED_LIST_QNAME = QName.create(MASTER_CONTAINER_QNAME, "unkeyed-list");
52 private static final QName UNKEYED_LEAF_QNAME = QName.create(MASTER_CONTAINER_QNAME, "unkeyed-leaf");
54 private static final YangInstanceIdentifier MASTER_CONTAINER_PATH = YangInstanceIdentifier
55 .of(MASTER_CONTAINER_QNAME);
56 private static final YangInstanceIdentifier MIN_MAX_LIST_PATH = YangInstanceIdentifier
57 .builder(MASTER_CONTAINER_PATH).node(MIN_MAX_LIST_QNAME).build();
58 private static final YangInstanceIdentifier UNBOUNDED_LIST_PATH = YangInstanceIdentifier
59 .builder(MASTER_CONTAINER_PATH).node(UNBOUNDED_LIST_QNAME).build();
60 private static final YangInstanceIdentifier MIN_MAX_LEAF_LIST_PATH = YangInstanceIdentifier
61 .builder(MASTER_CONTAINER_PATH).node(MIN_MAX_LEAF_LIST_QNAME).build();
62 private static final YangInstanceIdentifier UNBOUNDED_LEAF_LIST_PATH = YangInstanceIdentifier
63 .builder(MASTER_CONTAINER_PATH).node(UNBOUNDED_LEAF_LIST_QNAME).build();
64 private static final YangInstanceIdentifier UNKEYED_LIST_PATH = YangInstanceIdentifier
65 .builder(MASTER_CONTAINER_PATH).node(UNKEYED_LIST_QNAME).build();
67 private static EffectiveModelContext schemaContext;
69 private DataTree inMemoryDataTree;
72 static void beforeClass() {
73 schemaContext = YangParserTestUtils.parseYang("""
74 module list-constraints-validation-test-model {
76 namespace "urn:opendaylight:params:xml:ns:yang:list-constraints-validation-test-model";
77 prefix "list-constraints-validation";
79 revision "2015-02-02" {
80 description "Initial revision.";
83 container master-container {
87 key "min-max-key-leaf";
88 leaf min-max-key-leaf {
94 key "unbounded-key-leaf";
95 leaf unbounded-key-leaf {
100 leaf-list min-max-leaf-list {
106 leaf-list unbounded-leaf-list {
121 static void afterClass() {
122 schemaContext = null;
126 void prepare() throws DataValidationFailedException {
127 inMemoryDataTree = new InMemoryDataTreeFactory().create(DataTreeConfiguration.DEFAULT_OPERATIONAL,
129 final var initialDataTreeSnapshot = inMemoryDataTree.takeSnapshot();
130 final var modificationTree = initialDataTreeSnapshot.newModification();
132 modificationTree.write(MASTER_CONTAINER_PATH, ImmutableNodes.containerNode(MASTER_CONTAINER_QNAME));
133 modificationTree.ready();
134 inMemoryDataTree.commit(inMemoryDataTree.prepare(modificationTree));
138 void minMaxListTestPass() throws DataValidationFailedException {
140 final var fooEntryNode = ImmutableNodes.mapEntry(MIN_MAX_LIST_QNAME, MIN_MAX_KEY_LEAF_QNAME, "foo");
141 final var barEntryNode = ImmutableNodes.mapEntry(MIN_MAX_LIST_QNAME, MIN_MAX_KEY_LEAF_QNAME, "bar");
142 final var mapNode1 = ImmutableNodes.mapNodeBuilder()
143 .withNodeIdentifier(new NodeIdentifier(MIN_MAX_LIST_QNAME))
144 .withChild(fooEntryNode).build();
145 final var mapNode2 = ImmutableNodes.mapNodeBuilder()
146 .withNodeIdentifier(new NodeIdentifier(MIN_MAX_LIST_QNAME))
147 .withChild(barEntryNode).build();
149 final var modificationTree = inMemoryDataTree.takeSnapshot().newModification();
150 modificationTree.write(MIN_MAX_LIST_PATH, mapNode1);
151 modificationTree.merge(MIN_MAX_LIST_PATH, mapNode2);
152 modificationTree.ready();
154 inMemoryDataTree.validate(modificationTree);
155 final var prepare = inMemoryDataTree.prepare(modificationTree);
156 inMemoryDataTree.commit(prepare);
158 final var snapshotAfterCommit = inMemoryDataTree.takeSnapshot();
159 final var minMaxListRead = snapshotAfterCommit.readNode(MIN_MAX_LIST_PATH);
160 assertTrue(minMaxListRead.isPresent());
161 assertEquals(2, ((NormalizedNodeContainer<?>) minMaxListRead.orElseThrow()).size());
165 void minMaxListFail() throws DataValidationFailedException {
166 assertThrows(DataValidationFailedException.class, () -> {
167 var modificationTree = inMemoryDataTree.takeSnapshot().newModification();
169 final var fooEntryNode = ImmutableNodes.mapEntry(MIN_MAX_LIST_QNAME, MIN_MAX_KEY_LEAF_QNAME,
171 final var barEntryNode = ImmutableNodes.mapEntry(MIN_MAX_LIST_QNAME, MIN_MAX_KEY_LEAF_QNAME,
173 final var gooEntryNode = ImmutableNodes.mapEntry(MIN_MAX_LIST_QNAME, MIN_MAX_KEY_LEAF_QNAME,
175 final var mapNode = ImmutableNodes.mapNodeBuilder()
176 .withNodeIdentifier(new NodeIdentifier(MIN_MAX_LIST_QNAME))
177 .withChild(fooEntryNode).build();
179 final var fooPath = MIN_MAX_LIST_PATH.node(fooEntryNode.name());
180 final var barPath = MIN_MAX_LIST_PATH.node(barEntryNode.name());
181 final var gooPath = MIN_MAX_LIST_PATH.node(gooEntryNode.name());
183 modificationTree.write(MIN_MAX_LIST_PATH, mapNode);
184 modificationTree.merge(barPath, barEntryNode);
185 modificationTree.write(gooPath, gooEntryNode);
186 modificationTree.delete(gooPath);
187 modificationTree.ready();
189 inMemoryDataTree.validate(modificationTree);
190 var prepare1 = inMemoryDataTree.prepare(modificationTree);
191 inMemoryDataTree.commit(prepare1);
193 var snapshotAfterCommit = inMemoryDataTree.takeSnapshot();
194 var minMaxListRead = snapshotAfterCommit.readNode(MIN_MAX_LIST_PATH);
195 assertTrue(minMaxListRead.isPresent());
196 assertEquals(2, ((NormalizedNodeContainer<?>) minMaxListRead.orElseThrow()).size());
198 modificationTree = inMemoryDataTree.takeSnapshot().newModification();
199 modificationTree.write(gooPath, gooEntryNode);
200 modificationTree.ready();
202 inMemoryDataTree.validate(modificationTree);
203 prepare1 = inMemoryDataTree.prepare(modificationTree);
204 inMemoryDataTree.commit(prepare1);
206 snapshotAfterCommit = inMemoryDataTree.takeSnapshot();
207 minMaxListRead = snapshotAfterCommit.readNode(MIN_MAX_LIST_PATH);
208 assertTrue(minMaxListRead.isPresent());
209 assertEquals(3, ((NormalizedNodeContainer<?>) minMaxListRead.orElseThrow()).size());
211 modificationTree = inMemoryDataTree.takeSnapshot().newModification();
213 modificationTree.delete(gooPath);
214 modificationTree.delete(fooPath);
215 modificationTree.ready();
217 inMemoryDataTree.validate(modificationTree);
222 void minMaxLeafListPass() throws DataValidationFailedException {
223 final var modificationTree = inMemoryDataTree.takeSnapshot().newModification();
225 final var barPath = new NodeWithValue<>(MIN_MAX_LIST_QNAME, "bar");
226 final var gooPath = new NodeWithValue<>(MIN_MAX_LIST_QNAME, "goo");
228 modificationTree.write(MIN_MAX_LEAF_LIST_PATH, Builders.leafSetBuilder()
229 .withNodeIdentifier(new NodeIdentifier(MIN_MAX_LEAF_LIST_QNAME))
230 .withChildValue("foo")
232 modificationTree.write(MIN_MAX_LEAF_LIST_PATH.node(barPath), Builders.leafSetEntryBuilder()
233 .withNodeIdentifier(barPath)
236 modificationTree.merge(MIN_MAX_LEAF_LIST_PATH.node(gooPath), Builders.leafSetEntryBuilder()
237 .withNodeIdentifier(gooPath)
240 modificationTree.delete(MIN_MAX_LEAF_LIST_PATH.node(gooPath));
241 modificationTree.ready();
243 inMemoryDataTree.validate(modificationTree);
244 final var prepare1 = inMemoryDataTree.prepare(modificationTree);
245 inMemoryDataTree.commit(prepare1);
247 final var snapshotAfterCommit = inMemoryDataTree.takeSnapshot();
248 final var masterContainer = snapshotAfterCommit.readNode(MASTER_CONTAINER_PATH);
249 assertTrue(masterContainer.isPresent());
251 (NormalizedNodeContainer<?>) ((DistinctNodeContainer) masterContainer.orElseThrow())
252 .childByArg(new NodeIdentifier(MIN_MAX_LEAF_LIST_QNAME));
253 assertNotNull(leafList);
254 assertEquals(2, leafList.size());
258 void minMaxLeafListFail() {
259 final var modificationTree = inMemoryDataTree.takeSnapshot().newModification();
261 final var barPath = new NodeWithValue<>(MIN_MAX_LIST_QNAME, "bar");
262 final var gooPath = new NodeWithValue<>(MIN_MAX_LIST_QNAME, "goo");
263 final var fuuPath = new NodeWithValue<>(MIN_MAX_LIST_QNAME, "fuu");
265 modificationTree.write(MIN_MAX_LEAF_LIST_PATH, Builders.leafSetBuilder()
266 .withNodeIdentifier(new NodeIdentifier(MIN_MAX_LEAF_LIST_QNAME))
267 .withChildValue("foo")
269 modificationTree.write(MIN_MAX_LEAF_LIST_PATH.node(barPath), Builders.leafSetEntryBuilder()
270 .withNodeIdentifier(barPath)
273 modificationTree.merge(MIN_MAX_LEAF_LIST_PATH.node(gooPath), Builders.leafSetEntryBuilder()
274 .withNodeIdentifier(gooPath)
277 modificationTree.write(MIN_MAX_LEAF_LIST_PATH.node(fuuPath), Builders.leafSetEntryBuilder()
278 .withNodeIdentifier(fuuPath)
282 final var ex = assertThrows(MinMaxElementsValidationFailedException.class,
283 () -> modificationTree.ready());
284 assertEquals("(urn:opendaylight:params:xml:ns:yang:list-constraints-validation-test-model?"
285 + "revision=2015-02-02)min-max-leaf-list has too many elements (4), can have at most 3",
287 assertTooManyElements(ex);
291 void unkeyedListTestPass() throws DataValidationFailedException {
292 final var modificationTree = inMemoryDataTree.takeSnapshot().newModification();
294 final var unkeyedListNode = Builders.unkeyedListBuilder()
295 .withNodeIdentifier(new NodeIdentifier(UNKEYED_LIST_QNAME))
296 .withValue(List.of(Builders.unkeyedListEntryBuilder()
297 .withNodeIdentifier(new NodeIdentifier(UNKEYED_LEAF_QNAME))
298 .withChild(ImmutableNodes.leafNode(UNKEYED_LEAF_QNAME, "foo"))
302 modificationTree.write(MASTER_CONTAINER_PATH, ImmutableNodes.containerNode(MASTER_CONTAINER_QNAME));
303 modificationTree.merge(UNKEYED_LIST_PATH, unkeyedListNode);
304 modificationTree.ready();
306 inMemoryDataTree.validate(modificationTree);
307 final var prepare1 = inMemoryDataTree.prepare(modificationTree);
308 inMemoryDataTree.commit(prepare1);
310 final var snapshotAfterCommit = inMemoryDataTree.takeSnapshot();
311 final var unkeyedListRead = snapshotAfterCommit.readNode(UNKEYED_LIST_PATH);
312 assertTrue(unkeyedListRead.isPresent());
313 assertEquals(1, ((UnkeyedListNode) unkeyedListRead.orElseThrow()).size());
317 void unkeyedListTestFail() {
318 final var modificationTree = inMemoryDataTree.takeSnapshot().newModification();
320 modificationTree.write(UNKEYED_LIST_PATH, Builders.unkeyedListBuilder()
321 .withNodeIdentifier(new NodeIdentifier(UNKEYED_LIST_QNAME))
323 Builders.unkeyedListEntryBuilder()
324 .withNodeIdentifier(new NodeIdentifier(UNKEYED_LEAF_QNAME))
325 .withChild(ImmutableNodes.leafNode(UNKEYED_LEAF_QNAME, "foo"))
327 Builders.unkeyedListEntryBuilder()
328 .withNodeIdentifier(new NodeIdentifier(UNKEYED_LEAF_QNAME))
329 .withChild(ImmutableNodes.leafNode(UNKEYED_LEAF_QNAME, "bar"))
332 final var ex = assertThrows(MinMaxElementsValidationFailedException.class,
333 () -> modificationTree.ready());
334 assertEquals("(urn:opendaylight:params:xml:ns:yang:list-constraints-validation-test-model?"
335 + "revision=2015-02-02)unkeyed-list has too many elements (2), can have at most 1", ex.getMessage());
336 assertTooManyElements(ex);
339 static void assertTooFewElements(final Exception ex) {
340 assertOperationFailed(ex, "too-few-elements");
343 static void assertTooManyElements(final Exception ex) {
344 assertOperationFailed(ex, "too-many-elements");
347 private static void assertOperationFailed(final Exception ex, final String expectedAppTag) {
348 assertInstanceOf(YangNetconfErrorAware.class, ex);
349 final var errors = ((YangNetconfErrorAware) ex).getNetconfErrors();
350 assertEquals(1, errors.size());
351 final var error = errors.get(0);
352 assertEquals(ErrorSeverity.ERROR, error.severity());
353 assertEquals(ErrorType.APPLICATION, error.type());
354 assertEquals(ErrorTag.OPERATION_FAILED, error.tag());
355 assertEquals(expectedAppTag, error.appTag());