2 * Copyright (c) 2023 PANTHEON.tech, s.r.o. 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.restconf.nb.rfc8040.databind;
10 import static org.junit.jupiter.api.Assertions.assertEquals;
11 import static org.junit.jupiter.api.Assertions.assertThrows;
12 import static org.mockito.ArgumentMatchers.any;
13 import static org.mockito.Mockito.doReturn;
16 import java.util.Optional;
17 import org.junit.jupiter.api.Test;
18 import org.opendaylight.mdsal.dom.api.DOMActionService;
19 import org.opendaylight.mdsal.dom.api.DOMDataBroker;
20 import org.opendaylight.mdsal.dom.api.DOMMountPointService;
21 import org.opendaylight.mdsal.dom.api.DOMRpcService;
22 import org.opendaylight.mdsal.dom.api.DOMSchemaService;
23 import org.opendaylight.mdsal.dom.spi.FixedDOMSchemaService;
24 import org.opendaylight.netconf.dom.api.NetconfDataTreeService;
25 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
26 import org.opendaylight.yangtools.yang.common.ErrorTag;
27 import org.opendaylight.yangtools.yang.common.ErrorType;
28 import org.opendaylight.yangtools.yang.common.QName;
29 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
30 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
31 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
32 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
33 import org.opendaylight.yangtools.yang.data.spi.node.ImmutableNodes;
35 class XmlResourceBodyTest extends AbstractResourceBodyTest {
36 private static final QName TOP_LEVEL_LIST = QName.create("foo", "2017-08-09", "top-level-list");
38 XmlResourceBodyTest() {
39 super(XmlResourceBody::new);
42 private void mockMount() {
43 doReturn(Optional.of(mountPoint)).when(mountPointService).getMountPoint(any(YangInstanceIdentifier.class));
44 doReturn(Optional.of(new FixedDOMSchemaService(IID_SCHEMA))).when(mountPoint)
45 .getService(DOMSchemaService.class);
46 doReturn(Optional.of(dataBroker)).when(mountPoint).getService(DOMDataBroker.class);
47 doReturn(Optional.empty()).when(mountPoint).getService(DOMActionService.class);
48 doReturn(Optional.empty()).when(mountPoint).getService(DOMRpcService.class);
49 doReturn(Optional.empty()).when(mountPoint).getService(DOMMountPointService.class);
50 doReturn(Optional.empty()).when(mountPoint).getService(NetconfDataTreeService.class);
54 * Test PUT operation when message root element is not the same as the last element in request URI.
55 * PUT operation message should always start with schema node from URI otherwise exception should be
59 void wrongRootElementTest() {
62 assertThrowsException("",
63 "Incorrect message root element (instance:identifier:module)cont1, should be "
64 + "(urn:ietf:params:xml:ns:yang:ietf-restconf)data");
65 assertThrowsException("instance-identifier-module:cont/yang-ext:mount",
66 "Incorrect message root element (instance:identifier:module)cont1, should be "
67 + "(urn:ietf:params:xml:ns:yang:ietf-restconf)data");
68 assertThrowsException("instance-identifier-module:cont",
69 "Incorrect message root element (instance:identifier:module)cont1, should be "
70 + "(instance:identifier:module)cont");
71 assertThrowsException("instance-identifier-module:cont/yang-ext:mount/instance-identifier-module:cont",
72 "Incorrect message root element (instance:identifier:module)cont1, should be "
73 + "(instance:identifier:module)cont");
76 private void assertThrowsException(final String uriPath, final String expectedErrorMessage) {
77 final var ex = assertThrows(RestconfDocumentedException.class,
78 () -> parse(uriPath, """
79 <cont1 xmlns="instance:identifier:module"/>"""));
80 final var restconfError = ex.getErrors().get(0);
81 assertEquals(ErrorType.PROTOCOL, restconfError.getErrorType());
82 assertEquals(ErrorTag.MALFORMED_MESSAGE, restconfError.getErrorTag());
83 assertEquals(expectedErrorMessage, restconfError.getErrorMessage());
87 void testRangeViolation() throws Exception {
88 assertRangeViolation(() -> parse("netconf786:foo", """
89 <foo xmlns="netconf786"><bar>100</bar></foo>"""));
93 void putXmlTest() throws Exception {
94 final var keyName = QName.create(TOP_LEVEL_LIST, "key-leaf");
95 assertEquals(ImmutableNodes.newMapEntryBuilder()
96 .withNodeIdentifier(NodeIdentifierWithPredicates.of(TOP_LEVEL_LIST, keyName, "key-value"))
97 .withChild(ImmutableNodes.leafNode(keyName, "key-value"))
98 .withChild(ImmutableNodes.leafNode(QName.create(keyName, "ordinary-leaf"), "leaf-value"))
99 .build(), parse("foo:top-level-list=key-value", """
100 <top-level-list xmlns="foo">
101 <key-leaf>key-value</key-leaf>
102 <ordinary-leaf>leaf-value</ordinary-leaf>
103 </top-level-list>"""));
107 void moduleDataTest() throws Exception {
108 testModuleData("instance-identifier-module:cont");
112 void moduleDataMountPointTest() throws Exception {
115 testModuleData("instance-identifier-module:cont/yang-ext:mount/instance-identifier-module:cont");
118 private void testModuleData(final String uriPath) throws Exception {
119 final var entryId = NodeIdentifierWithPredicates.of(LST11,
120 Map.of(KEYVALUE111, "value1", KEYVALUE112, "value2"));
122 assertEquals(ImmutableNodes.newContainerBuilder()
123 .withNodeIdentifier(CONT_NID)
124 .withChild(ImmutableNodes.newContainerBuilder()
125 .withNodeIdentifier(CONT1_NID)
126 .withChild(ImmutableNodes.newSystemMapBuilder()
127 .withNodeIdentifier(new NodeIdentifier(LST11))
128 .withChild(ImmutableNodes.newMapEntryBuilder()
129 .withNodeIdentifier(entryId)
130 .withChild(ImmutableNodes.leafNode(KEYVALUE111, "value1"))
131 .withChild(ImmutableNodes.leafNode(KEYVALUE112, "value2"))
132 .withChild(ImmutableNodes.leafNode(LF111, YangInstanceIdentifier.of(CONT_NID, CONT1_NID,
133 new NodeIdentifier(LST11), entryId, LF112_NID)))
134 .withChild(ImmutableNodes.leafNode(LF112_NID, "lf112 value"))
138 .build(), parse(uriPath, """
139 <cont xmlns="instance:identifier:module">
141 <lst11 xmlns="augment:module" xmlns:c="augment:augment:module">
142 <keyvalue111>value1</keyvalue111>
143 <keyvalue112>value2</keyvalue112>
144 <lf111 xmlns="augment:augment:module" xmlns:a="instance:identifier:module" \
145 xmlns:b="augment:module">/a:cont/a:cont1/b:lst11[b:keyvalue111="value1"][b:keyvalue112="value2"]\
147 <lf112 xmlns="augment:augment:module">lf112 value</lf112>
154 void moduleSubContainerDataPutTest() throws Exception {
155 testModuleSubContainerDataPut("instance-identifier-module:cont/cont1");
159 void moduleSubContainerDataPutMountPointTest() throws Exception {
162 testModuleSubContainerDataPut(
163 "instance-identifier-module:cont/yang-ext:mount/instance-identifier-module:cont/cont1");
166 private void testModuleSubContainerDataPut(final String uriPath) throws Exception {
167 assertEquals(ImmutableNodes.newContainerBuilder()
168 .withNodeIdentifier(CONT1_NID)
169 .withChild(ImmutableNodes.newSystemLeafSetBuilder()
170 .withNodeIdentifier(new NodeIdentifier(LFLST11))
171 .withChild(ImmutableNodes.newLeafSetEntryBuilder()
172 .withNodeIdentifier(new NodeWithValue<>(LFLST11, "lflst11_3"))
173 .withValue("lflst11_3")
175 .withChild(ImmutableNodes.newLeafSetEntryBuilder()
176 .withNodeIdentifier(new NodeWithValue<>(LFLST11, "lflst11_1"))
177 .withValue("lflst11_1")
179 .withChild(ImmutableNodes.newLeafSetEntryBuilder()
180 .withNodeIdentifier(new NodeWithValue<>(LFLST11, "lflst11_2"))
181 .withValue("lflst11_2")
184 .withChild(ImmutableNodes.leafNode(LF11, YangInstanceIdentifier.of(CONT_NID, CONT1_NID,
185 new NodeIdentifier(LFLST11), new NodeWithValue<>(LFLST11, "lflst11_1"))))
186 .build(), parse(uriPath, """
187 <cont1 xmlns="instance:identifier:module">
188 <lflst11 xmlns="augment:module:leaf:list">lflst11_1</lflst11>
189 <lflst11 xmlns="augment:module:leaf:list">lflst11_2</lflst11>
190 <lflst11 xmlns="augment:module:leaf:list">lflst11_3</lflst11>
191 <lf11 xmlns:a="instance:identifier:module" xmlns:b="augment:module:leaf:list" \
192 xmlns="augment:module:leaf:list">/a:cont/a:cont1/b:lflst11[.="lflst11_1"]</lf11>
197 void testMismatchedInput() throws Exception {
198 final var error = assertError(() -> parse("base:cont", """
199 <restconf-state xmlns="urn:ietf:params:xml:ns:yang:ietf-restconf"/>"""));
201 Incorrect message root element (urn:ietf:params:xml:ns:yang:ietf-restconf)restconf-state, should be \
202 (ns)cont""", error.getErrorMessage());
203 assertEquals(ErrorType.PROTOCOL, error.getErrorType());
204 assertEquals(ErrorTag.MALFORMED_MESSAGE, error.getErrorTag());
208 void testMissingKeys() throws Exception {
209 final var error = assertError(() -> parse("nested-module:depth1-cont/depth2-list2=one,two", """
210 <depth2-list2 xmlns="urn:nested:module">
211 <depth3-lf1-key>one</depth3-lf1-key>
212 </depth2-list2>"""));
214 Error parsing input: List entry (urn:nested:module?revision=2014-06-03)depth2-list2 is missing leaf values \
215 for [depth3-lf2-key]""", error.getErrorMessage());
216 assertEquals(ErrorType.PROTOCOL, error.getErrorType());
217 assertEquals(ErrorTag.MALFORMED_MESSAGE, error.getErrorTag());