Fix XML parser condition in XmlPatchBodyReader
[netconf.git] / restconf / restconf-nb / src / test / java / org / opendaylight / restconf / nb / rfc8040 / jersey / providers / patch / XmlPatchBodyReaderTest.java
1 /*
2  * Copyright (c) 2016 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.restconf.nb.rfc8040.jersey.providers.patch;
9
10 import static org.junit.Assert.assertEquals;
11 import static org.junit.Assert.assertThrows;
12
13 import java.io.ByteArrayInputStream;
14 import java.io.InputStream;
15 import java.nio.charset.StandardCharsets;
16 import javax.ws.rs.core.MediaType;
17 import org.junit.BeforeClass;
18 import org.junit.Test;
19 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
20 import org.opendaylight.restconf.nb.rfc8040.jersey.providers.test.AbstractBodyReaderTest;
21 import org.opendaylight.restconf.nb.rfc8040.jersey.providers.test.XmlBodyReaderTest;
22 import org.opendaylight.yangtools.yang.common.ErrorTag;
23 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
24 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
25 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
26 import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
27 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
28 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
29
30 public class XmlPatchBodyReaderTest extends AbstractBodyReaderTest {
31     private static EffectiveModelContext schemaContext;
32
33     private final XmlPatchBodyReader xmlToPatchBodyReader;
34
35     public XmlPatchBodyReaderTest() throws Exception {
36         super(schemaContext);
37         xmlToPatchBodyReader = new XmlPatchBodyReader(databindProvider, mountPointService);
38     }
39
40     @Override
41     protected MediaType getMediaType() {
42         return new MediaType(MediaType.APPLICATION_XML, null);
43     }
44
45     @BeforeClass
46     public static void initialization() {
47         schemaContext = schemaContextLoader("/instanceidentifier/yang", schemaContext);
48     }
49
50     @Test
51     public void moduleDataTest() throws Exception {
52         mockBodyReader("instance-identifier-patch-module:patch-cont/my-list1=leaf1", xmlToPatchBodyReader, false);
53         checkPatchContext(xmlToPatchBodyReader.readFrom(null, null, null, mediaType, null,
54             XmlBodyReaderTest.class.getResourceAsStream("/instanceidentifier/xml/xmlPATCHdata.xml")));
55     }
56
57     /**
58      * Test trying to use Patch create operation which requires value without value. Error code 400 should be returned.
59      */
60     @Test
61     public void moduleDataValueMissingNegativeTest() throws Exception {
62         mockBodyReader("instance-identifier-patch-module:patch-cont/my-list1=leaf1", xmlToPatchBodyReader, false);
63         final InputStream inputStream = XmlBodyReaderTest.class.getResourceAsStream(
64             "/instanceidentifier/xml/xmlPATCHdataValueMissing.xml");
65         final RestconfDocumentedException ex = assertThrows(RestconfDocumentedException.class,
66             () -> xmlToPatchBodyReader.readFrom(null, null, null, mediaType, null, inputStream));
67         assertEquals(ErrorTag.MALFORMED_MESSAGE, ex.getErrors().get(0).getErrorTag());
68     }
69
70     /**
71      * Test trying to use value with Patch delete operation which does not support value. Error code 400 should be
72      * returned.
73      */
74     @Test
75     public void moduleDataNotValueNotSupportedNegativeTest() throws Exception {
76         mockBodyReader("instance-identifier-patch-module:patch-cont/my-list1=leaf1", xmlToPatchBodyReader, false);
77         final InputStream inputStream = XmlBodyReaderTest.class.getResourceAsStream(
78             "/instanceidentifier/xml/xmlPATCHdataValueNotSupported.xml");
79         final RestconfDocumentedException ex = assertThrows(RestconfDocumentedException.class,
80             () -> xmlToPatchBodyReader.readFrom(null, null, null, mediaType, null, inputStream));
81         assertEquals(ErrorTag.MALFORMED_MESSAGE, ex.getErrors().get(0).getErrorTag());
82     }
83
84     /**
85      * Test of Yang Patch with absolute target path.
86      */
87     @Test
88     public void moduleDataAbsoluteTargetPathTest() throws Exception {
89         mockBodyReader("", xmlToPatchBodyReader, false);
90         checkPatchContext(xmlToPatchBodyReader.readFrom(null, null, null, mediaType, null,
91             XmlBodyReaderTest.class.getResourceAsStream("/instanceidentifier/xml/xmlPATCHdataAbsoluteTargetPath.xml")));
92     }
93
94     /**
95      * Test using Patch when target is completely specified in request URI and thus target leaf contains only '/' sign.
96      */
97     @Test
98     public void modulePatchCompleteTargetInURITest() throws Exception {
99         mockBodyReader("instance-identifier-patch-module:patch-cont", xmlToPatchBodyReader, false);
100         checkPatchContext(xmlToPatchBodyReader.readFrom(null, null, null, mediaType, null,
101             XmlBodyReaderTest.class.getResourceAsStream(
102                 "/instanceidentifier/xml/xmlPATCHdataCompleteTargetInURI.xml")));
103     }
104
105     /**
106      * Test of Yang Patch merge operation on list. Test consists of two edit operations - replace and merge.
107      */
108     @Test
109     public void moduleDataMergeOperationOnListTest() throws Exception {
110         mockBodyReader("instance-identifier-patch-module:patch-cont/my-list1=leaf1", xmlToPatchBodyReader, false);
111         final InputStream inputStream = XmlBodyReaderTest.class.getResourceAsStream(
112             "/instanceidentifier/xml/xmlPATCHdataMergeOperationOnList.xml");
113         checkPatchContext(xmlToPatchBodyReader.readFrom(null, null, null, mediaType, null, inputStream));
114     }
115
116     /**
117      * Test of Yang Patch merge operation on container. Test consists of two edit operations - create and merge.
118      */
119     @Test
120     public void moduleDataMergeOperationOnContainerTest() throws Exception {
121         mockBodyReader("instance-identifier-patch-module:patch-cont", xmlToPatchBodyReader, false);
122         final InputStream inputStream = XmlBodyReaderTest.class
123                 .getResourceAsStream("/instanceidentifier/xml/xmlPATCHdataMergeOperationOnContainer.xml");
124         checkPatchContext(xmlToPatchBodyReader.readFrom(null, null, null, mediaType, null, inputStream));
125     }
126
127     /**
128      * Test of Yang Patch on the top-level container with empty URI for data root.
129      */
130     @Test
131     public void modulePatchTargetTopLevelContainerWithEmptyURITest() throws Exception {
132         mockBodyReader("", xmlToPatchBodyReader, false);
133         final InputStream inputStream = XmlBodyReaderTest.class
134                 .getResourceAsStream("/instanceidentifier/xml/xmlPATCHTargetTopLevelContainerWithEmptyURI.xml");
135         checkPatchContext(xmlToPatchBodyReader.readFrom(null, null, null, mediaType, null, inputStream));
136     }
137
138     /**
139      * Test of Yang Patch on the top system map node element.
140      */
141     @Test
142     public void moduleTargetMapNodeTest() throws Exception {
143         mockBodyReader("", xmlToPatchBodyReader, false);
144         final var inputStream = new ByteArrayInputStream("""
145             <yang-patch xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
146                 <patch-id>map-patch</patch-id>
147                 <comment>YANG patch comment</comment>
148                 <edit>
149                     <edit-id>edit1</edit-id>
150                     <operation>replace</operation>
151                     <target>/map-model:cont-root/map-model:cont1/map-model:my-map=key</target>
152                     <value>
153                         <my-map xmlns="map:ns">
154                             <key-leaf>key</key-leaf>
155                             <data-leaf>data</data-leaf>
156                         </my-map>
157                     </value>
158                 </edit>
159             </yang-patch>
160             """.getBytes(StandardCharsets.UTF_8));
161         final var expectedData = Builders.mapBuilder()
162                 .withNodeIdentifier(new NodeIdentifier(MAP_CONT_QNAME))
163                 .withChild(Builders.mapEntryBuilder()
164                         .withNodeIdentifier(NodeIdentifierWithPredicates.of(MAP_CONT_QNAME, KEY_LEAF_QNAME, "key"))
165                         .withChild(ImmutableNodes.leafNode(KEY_LEAF_QNAME, "key"))
166                         .withChild(ImmutableNodes.leafNode(DATA_LEAF_QNAME, "data"))
167                         .build())
168                 .build();
169         final var returnValue = xmlToPatchBodyReader.readFrom(null, null, null, mediaType, null, inputStream);
170         checkPatchContext(returnValue);
171         final var data = returnValue.getData().get(0).getNode();
172         assertEquals(MAP_CONT_QNAME, data.getIdentifier().getNodeType());
173         assertEquals(expectedData, data);
174     }
175
176     /**
177      * Test of Yang Patch on the leaf set node element.
178      */
179     @Test
180     public void modulePatchTargetLeafSetNodeTest() throws Exception {
181         mockBodyReader("", xmlToPatchBodyReader, false);
182         final var inputStream = new ByteArrayInputStream("""
183             <yang-patch xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
184                 <patch-id>set-patch</patch-id>
185                 <comment>YANG patch comment</comment>
186                 <edit>
187                     <edit-id>edit1</edit-id>
188                     <operation>replace</operation>
189                     <target>/set-model:cont-root/set-model:cont1/set-model:my-set="data1"</target>
190                     <value>
191                         <my-set xmlns="set:ns">data1</my-set>
192                     </value>
193                 </edit>
194             </yang-patch>
195             """.getBytes(StandardCharsets.UTF_8));
196         final var expectedData = Builders.leafSetBuilder()
197                 .withNodeIdentifier(new NodeIdentifier(LEAF_SET_QNAME))
198                 .withChild(Builders.leafSetEntryBuilder()
199                         .withNodeIdentifier(new NodeWithValue<>(LEAF_SET_QNAME, "data1"))
200                         .withValue("data1")
201                         .build())
202                 .build();
203         final var returnValue = xmlToPatchBodyReader.readFrom(null, null, null, mediaType, null, inputStream);
204         checkPatchContext(returnValue);
205         final var data = returnValue.getData().get(0).getNode();
206         assertEquals(LEAF_SET_QNAME, data.getIdentifier().getNodeType());
207         assertEquals(expectedData, data);
208     }
209
210     /**
211      * Test of Yang Patch on the top unkeyed list element.
212      */
213     @Test
214     public void moduleTargetUnkeyedListNodeTest() throws Exception {
215         mockBodyReader("", xmlToPatchBodyReader, false);
216         final var inputStream = new ByteArrayInputStream("""
217             <yang-patch xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
218                 <patch-id>list-patch</patch-id>
219                 <comment>YANG patch comment</comment>
220                 <edit>
221                     <edit-id>edit1</edit-id>
222                     <operation>replace</operation>
223                     <target>/list-model:cont-root/list-model:cont1/list-model:unkeyed-list</target>
224                     <value>
225                         <unkeyed-list xmlns="list:ns">
226                             <leaf1>data1</leaf1>
227                             <leaf2>data2</leaf2>
228                         </unkeyed-list>
229                     </value>
230                 </edit>
231             </yang-patch>
232             """.getBytes(StandardCharsets.UTF_8));
233         final var expectedData = Builders.unkeyedListBuilder()
234                 .withNodeIdentifier(new NodeIdentifier(LIST_QNAME))
235                 .withChild(Builders.unkeyedListEntryBuilder()
236                         .withNodeIdentifier(new NodeIdentifier(LIST_QNAME))
237                         .withChild(ImmutableNodes.leafNode(LIST_LEAF1_QNAME, "data1"))
238                         .withChild(ImmutableNodes.leafNode(LIST_LEAF2_QNAME, "data2"))
239                         .build())
240                 .build();
241         final var returnValue = xmlToPatchBodyReader.readFrom(null, null, null, mediaType, null, inputStream);
242         checkPatchContext(returnValue);
243         final var data = returnValue.getData().get(0).getNode();
244         assertEquals(LIST_QNAME, data.getIdentifier().getNodeType());
245         assertEquals(expectedData, data);
246     }
247
248     /**
249      * Test of Yang Patch on the top case node element.
250      */
251     @Test
252     public void moduleTargetCaseNodeTest() throws Exception {
253         mockBodyReader("", xmlToPatchBodyReader, false);
254         final var inputStream = new ByteArrayInputStream("""
255             <yang-patch xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
256                 <patch-id>choice-patch</patch-id>
257                 <comment>YANG patch comment</comment>
258                 <edit>
259                     <edit-id>edit1</edit-id>
260                     <operation>replace</operation>
261                     <target>/choice-model:cont-root/choice-model:cont1/choice-model:case-cont1</target>
262                     <value>
263                         <case-cont1 xmlns="choice:ns">
264                             <case-leaf1>data</case-leaf1>
265                         </case-cont1>
266                     </value>
267                 </edit>
268             </yang-patch>
269             """.getBytes(StandardCharsets.UTF_8));
270         final var expectedData = Builders.containerBuilder()
271                 .withNodeIdentifier(new NodeIdentifier(CHOICE_CONT_QNAME))
272                 .withChild(ImmutableNodes.leafNode(CASE_LEAF1_QNAME, "data"))
273                 .build();
274         final var returnValue = xmlToPatchBodyReader.readFrom(null, null, null, mediaType, null, inputStream);
275         checkPatchContext(returnValue);
276         final var data = returnValue.getData().get(0).getNode();
277         assertEquals(CHOICE_CONT_QNAME, data.getIdentifier().getNodeType());
278         assertEquals(expectedData, data);
279     }
280
281     /**
282      * Test reading simple leaf value.
283      */
284     @Test
285     public void modulePatchSimpleLeafValueTest() throws Exception {
286         mockBodyReader("instance-identifier-patch-module:patch-cont/my-list1=leaf1", xmlToPatchBodyReader, false);
287         final var inputStream = new ByteArrayInputStream("""
288                 <yang-patch xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
289                     <patch-id>test-patch</patch-id>
290                     <comment>this is test patch</comment>
291                     <edit>
292                         <edit-id>edit1</edit-id>
293                         <operation>replace</operation>
294                         <target>/my-list2=my-leaf20/name</target>
295                         <value>
296                             <name xmlns="instance:identifier:patch:module">my-leaf20</name>
297                         </value>
298                     </edit>
299                 </yang-patch>
300                 """.getBytes(StandardCharsets.UTF_8));
301         final var returnValue = xmlToPatchBodyReader.readFrom(null, null, null, mediaType, null, inputStream);
302         checkPatchContext(returnValue);
303         final var data = returnValue.getData().get(0).getNode();
304         assertEquals(LEAF_NAME_QNAME, data.getIdentifier().getNodeType());
305         assertEquals(ImmutableNodes.leafNode(LEAF_NAME_QNAME, "my-leaf20"), data);
306     }
307 }