Move AbstractBody et al.
[netconf.git] / restconf / restconf-nb / src / test / java / org / opendaylight / restconf / nb / rfc8040 / databind / XmlResourceBodyTest.java
1 /*
2  * Copyright (c) 2023 PANTHEON.tech, s.r.o. 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.restconf.nb.rfc8040.databind;
9
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;
14
15 import java.util.Map;
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.restconf.server.api.XmlResourceBody;
27 import org.opendaylight.yangtools.yang.common.ErrorTag;
28 import org.opendaylight.yangtools.yang.common.ErrorType;
29 import org.opendaylight.yangtools.yang.common.QName;
30 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
31 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
32 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
33 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
34 import org.opendaylight.yangtools.yang.data.spi.node.ImmutableNodes;
35
36 class XmlResourceBodyTest extends AbstractResourceBodyTest {
37     private static final QName TOP_LEVEL_LIST = QName.create("foo", "2017-08-09", "top-level-list");
38
39     XmlResourceBodyTest() {
40         super(XmlResourceBody::new);
41     }
42
43     private void mockMount() {
44         doReturn(Optional.of(mountPoint)).when(mountPointService).getMountPoint(any(YangInstanceIdentifier.class));
45         doReturn(Optional.of(new FixedDOMSchemaService(IID_SCHEMA))).when(mountPoint)
46             .getService(DOMSchemaService.class);
47         doReturn(Optional.of(dataBroker)).when(mountPoint).getService(DOMDataBroker.class);
48         doReturn(Optional.empty()).when(mountPoint).getService(DOMActionService.class);
49         doReturn(Optional.empty()).when(mountPoint).getService(DOMRpcService.class);
50         doReturn(Optional.empty()).when(mountPoint).getService(DOMMountPointService.class);
51         doReturn(Optional.empty()).when(mountPoint).getService(NetconfDataTreeService.class);
52     }
53
54     /**
55      * Test PUT operation when message root element is not the same as the last element in request URI.
56      * PUT operation message should always start with schema node from URI otherwise exception should be
57      * thrown.
58      */
59     @Test
60     void wrongRootElementTest() {
61         mockMount();
62
63         assertThrowsException("",
64             "Incorrect message root element (instance:identifier:module)cont1, should be "
65                 + "(urn:ietf:params:xml:ns:yang:ietf-restconf)data");
66         assertThrowsException("instance-identifier-module:cont/yang-ext:mount",
67             "Incorrect message root element (instance:identifier:module)cont1, should be "
68                 + "(urn:ietf:params:xml:ns:yang:ietf-restconf)data");
69         assertThrowsException("instance-identifier-module:cont",
70             "Incorrect message root element (instance:identifier:module)cont1, should be "
71                 + "(instance:identifier:module)cont");
72         assertThrowsException("instance-identifier-module:cont/yang-ext:mount/instance-identifier-module:cont",
73             "Incorrect message root element (instance:identifier:module)cont1, should be "
74                 + "(instance:identifier:module)cont");
75     }
76
77     private void assertThrowsException(final String uriPath, final String expectedErrorMessage) {
78         final var ex = assertThrows(RestconfDocumentedException.class,
79             () -> parse(uriPath, """
80                 <cont1 xmlns="instance:identifier:module"/>"""));
81         final var restconfError = ex.getErrors().get(0);
82         assertEquals(ErrorType.PROTOCOL, restconfError.getErrorType());
83         assertEquals(ErrorTag.MALFORMED_MESSAGE, restconfError.getErrorTag());
84         assertEquals(expectedErrorMessage, restconfError.getErrorMessage());
85     }
86
87     @Test
88     void testRangeViolation() throws Exception {
89         assertRangeViolation(() -> parse("netconf786:foo", """
90             <foo xmlns="netconf786"><bar>100</bar></foo>"""));
91     }
92
93     @Test
94     void putXmlTest() throws Exception {
95         final var keyName = QName.create(TOP_LEVEL_LIST, "key-leaf");
96         assertEquals(ImmutableNodes.newMapEntryBuilder()
97             .withNodeIdentifier(NodeIdentifierWithPredicates.of(TOP_LEVEL_LIST, keyName, "key-value"))
98             .withChild(ImmutableNodes.leafNode(keyName, "key-value"))
99             .withChild(ImmutableNodes.leafNode(QName.create(keyName, "ordinary-leaf"), "leaf-value"))
100             .build(), parse("foo:top-level-list=key-value", """
101                 <top-level-list xmlns="foo">
102                     <key-leaf>key-value</key-leaf>
103                     <ordinary-leaf>leaf-value</ordinary-leaf>
104                 </top-level-list>"""));
105     }
106
107     @Test
108     void moduleDataTest() throws Exception {
109         testModuleData("instance-identifier-module:cont");
110     }
111
112     @Test
113     void moduleDataMountPointTest() throws Exception {
114         mockMount();
115
116         testModuleData("instance-identifier-module:cont/yang-ext:mount/instance-identifier-module:cont");
117     }
118
119     private void testModuleData(final String uriPath) throws Exception {
120         final var entryId = NodeIdentifierWithPredicates.of(LST11,
121             Map.of(KEYVALUE111, "value1", KEYVALUE112, "value2"));
122
123         assertEquals(ImmutableNodes.newContainerBuilder()
124             .withNodeIdentifier(CONT_NID)
125             .withChild(ImmutableNodes.newContainerBuilder()
126                 .withNodeIdentifier(CONT1_NID)
127                 .withChild(ImmutableNodes.newSystemMapBuilder()
128                     .withNodeIdentifier(new NodeIdentifier(LST11))
129                     .withChild(ImmutableNodes.newMapEntryBuilder()
130                         .withNodeIdentifier(entryId)
131                         .withChild(ImmutableNodes.leafNode(KEYVALUE111, "value1"))
132                         .withChild(ImmutableNodes.leafNode(KEYVALUE112, "value2"))
133                         .withChild(ImmutableNodes.leafNode(LF111, YangInstanceIdentifier.of(CONT_NID, CONT1_NID,
134                             new NodeIdentifier(LST11), entryId, LF112_NID)))
135                         .withChild(ImmutableNodes.leafNode(LF112_NID, "lf112 value"))
136                         .build())
137                     .build())
138                 .build())
139             .build(), parse(uriPath, """
140                 <cont xmlns="instance:identifier:module">
141                   <cont1>
142                     <lst11 xmlns="augment:module" xmlns:c="augment:augment:module">
143                       <keyvalue111>value1</keyvalue111>
144                       <keyvalue112>value2</keyvalue112>
145                       <lf111 xmlns="augment:augment:module" xmlns:a="instance:identifier:module" \
146                 xmlns:b="augment:module">/a:cont/a:cont1/b:lst11[b:keyvalue111="value1"][b:keyvalue112="value2"]\
147                 /c:lf112</lf111>
148                       <lf112 xmlns="augment:augment:module">lf112 value</lf112>
149                     </lst11>
150                   </cont1>
151                 </cont>"""));
152     }
153
154     @Test
155     void moduleSubContainerDataPutTest() throws Exception {
156         testModuleSubContainerDataPut("instance-identifier-module:cont/cont1");
157     }
158
159     @Test
160     void moduleSubContainerDataPutMountPointTest() throws Exception {
161         mockMount();
162
163         testModuleSubContainerDataPut(
164             "instance-identifier-module:cont/yang-ext:mount/instance-identifier-module:cont/cont1");
165     }
166
167     private void testModuleSubContainerDataPut(final String uriPath) throws Exception {
168         assertEquals(ImmutableNodes.newContainerBuilder()
169             .withNodeIdentifier(CONT1_NID)
170             .withChild(ImmutableNodes.newSystemLeafSetBuilder()
171                 .withNodeIdentifier(new NodeIdentifier(LFLST11))
172                 .withChild(ImmutableNodes.newLeafSetEntryBuilder()
173                     .withNodeIdentifier(new NodeWithValue<>(LFLST11, "lflst11_3"))
174                     .withValue("lflst11_3")
175                     .build())
176                 .withChild(ImmutableNodes.newLeafSetEntryBuilder()
177                     .withNodeIdentifier(new NodeWithValue<>(LFLST11, "lflst11_1"))
178                     .withValue("lflst11_1")
179                     .build())
180                 .withChild(ImmutableNodes.newLeafSetEntryBuilder()
181                     .withNodeIdentifier(new NodeWithValue<>(LFLST11, "lflst11_2"))
182                     .withValue("lflst11_2")
183                     .build())
184                 .build())
185             .withChild(ImmutableNodes.leafNode(LF11, YangInstanceIdentifier.of(CONT_NID, CONT1_NID,
186                 new NodeIdentifier(LFLST11), new NodeWithValue<>(LFLST11, "lflst11_1"))))
187             .build(), parse(uriPath, """
188                 <cont1 xmlns="instance:identifier:module">
189                   <lflst11 xmlns="augment:module:leaf:list">lflst11_1</lflst11>
190                   <lflst11 xmlns="augment:module:leaf:list">lflst11_2</lflst11>
191                   <lflst11 xmlns="augment:module:leaf:list">lflst11_3</lflst11>
192                   <lf11 xmlns:a="instance:identifier:module" xmlns:b="augment:module:leaf:list" \
193                 xmlns="augment:module:leaf:list">/a:cont/a:cont1/b:lflst11[.="lflst11_1"]</lf11>
194                 </cont1>"""));
195     }
196
197     @Test
198     void testMismatchedInput() throws Exception {
199         final var error = assertError(() -> parse("base:cont", """
200             <restconf-state xmlns="urn:ietf:params:xml:ns:yang:ietf-restconf"/>"""));
201         assertEquals("""
202             Incorrect message root element (urn:ietf:params:xml:ns:yang:ietf-restconf)restconf-state, should be \
203             (ns)cont""", error.getErrorMessage());
204         assertEquals(ErrorType.PROTOCOL, error.getErrorType());
205         assertEquals(ErrorTag.MALFORMED_MESSAGE, error.getErrorTag());
206     }
207
208     @Test
209     void testMissingKeys() throws Exception {
210         final var error = assertError(() -> parse("nested-module:depth1-cont/depth2-list2=one,two", """
211                 <depth2-list2 xmlns="urn:nested:module">
212                   <depth3-lf1-key>one</depth3-lf1-key>
213                 </depth2-list2>"""));
214         assertEquals("""
215             Error parsing input: List entry (urn:nested:module?revision=2014-06-03)depth2-list2 is missing leaf values \
216             for [depth3-lf2-key]""", error.getErrorMessage());
217         assertEquals(ErrorType.PROTOCOL, error.getErrorType());
218         assertEquals(ErrorTag.MALFORMED_MESSAGE, error.getErrorTag());
219     }
220 }