Fix YANG patch request for augmented element
[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 augmented element.
140      */
141     @Test
142     public void moduleTargetTopLevelAugmentedContainerTest() 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>test-patch</patch-id>
147                 <comment>This test patch for augmented element</comment>
148                 <edit>
149                     <edit-id>edit1</edit-id>
150                     <operation>replace</operation>
151                     <target>/test-m:container-root/test-m:container-lvl1/test-m-aug:container-aug</target>
152                     <value>
153                         <container-aug xmlns="test-ns-aug">
154                             <leaf-aug>data</leaf-aug>
155                         </container-aug>
156                     </value>
157                 </edit>
158             </yang-patch>
159             """.getBytes(StandardCharsets.UTF_8));
160         final var expectedData = Builders.containerBuilder()
161                 .withNodeIdentifier(new NodeIdentifier(CONT_AUG_QNAME))
162                 .withChild(ImmutableNodes.leafNode(LEAF_AUG_QNAME, "data"))
163                 .build();
164         final var returnValue = xmlToPatchBodyReader.readFrom(null, null, null, mediaType, null, inputStream);
165         checkPatchContext(returnValue);
166         final var data = returnValue.getData().get(0).getNode();
167         assertEquals(CONT_AUG_QNAME, data.getIdentifier().getNodeType());
168         assertEquals(expectedData, data);
169     }
170
171     /**
172      * Test of Yang Patch on the top system map node element.
173      */
174     @Test
175     public void moduleTargetMapNodeTest() throws Exception {
176         mockBodyReader("", xmlToPatchBodyReader, false);
177         final var inputStream = new ByteArrayInputStream("""
178             <yang-patch xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
179                 <patch-id>map-patch</patch-id>
180                 <comment>YANG patch comment</comment>
181                 <edit>
182                     <edit-id>edit1</edit-id>
183                     <operation>replace</operation>
184                     <target>/map-model:cont-root/map-model:cont1/map-model:my-map=key</target>
185                     <value>
186                         <my-map xmlns="map:ns">
187                             <key-leaf>key</key-leaf>
188                             <data-leaf>data</data-leaf>
189                         </my-map>
190                     </value>
191                 </edit>
192             </yang-patch>
193             """.getBytes(StandardCharsets.UTF_8));
194         final var expectedData = Builders.mapBuilder()
195                 .withNodeIdentifier(new NodeIdentifier(MAP_CONT_QNAME))
196                 .withChild(Builders.mapEntryBuilder()
197                         .withNodeIdentifier(NodeIdentifierWithPredicates.of(MAP_CONT_QNAME, KEY_LEAF_QNAME, "key"))
198                         .withChild(ImmutableNodes.leafNode(KEY_LEAF_QNAME, "key"))
199                         .withChild(ImmutableNodes.leafNode(DATA_LEAF_QNAME, "data"))
200                         .build())
201                 .build();
202         final var returnValue = xmlToPatchBodyReader.readFrom(null, null, null, mediaType, null, inputStream);
203         checkPatchContext(returnValue);
204         final var data = returnValue.getData().get(0).getNode();
205         assertEquals(MAP_CONT_QNAME, data.getIdentifier().getNodeType());
206         assertEquals(expectedData, data);
207     }
208
209     /**
210      * Test of Yang Patch on the leaf set node element.
211      */
212     @Test
213     public void modulePatchTargetLeafSetNodeTest() throws Exception {
214         mockBodyReader("", xmlToPatchBodyReader, false);
215         final var inputStream = new ByteArrayInputStream("""
216             <yang-patch xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
217                 <patch-id>set-patch</patch-id>
218                 <comment>YANG patch comment</comment>
219                 <edit>
220                     <edit-id>edit1</edit-id>
221                     <operation>replace</operation>
222                     <target>/set-model:cont-root/set-model:cont1/set-model:my-set="data1"</target>
223                     <value>
224                         <my-set xmlns="set:ns">data1</my-set>
225                     </value>
226                 </edit>
227             </yang-patch>
228             """.getBytes(StandardCharsets.UTF_8));
229         final var expectedData = Builders.leafSetBuilder()
230                 .withNodeIdentifier(new NodeIdentifier(LEAF_SET_QNAME))
231                 .withChild(Builders.leafSetEntryBuilder()
232                         .withNodeIdentifier(new NodeWithValue<>(LEAF_SET_QNAME, "data1"))
233                         .withValue("data1")
234                         .build())
235                 .build();
236         final var returnValue = xmlToPatchBodyReader.readFrom(null, null, null, mediaType, null, inputStream);
237         checkPatchContext(returnValue);
238         final var data = returnValue.getData().get(0).getNode();
239         assertEquals(LEAF_SET_QNAME, data.getIdentifier().getNodeType());
240         assertEquals(expectedData, data);
241     }
242
243     /**
244      * Test of Yang Patch on the top unkeyed list element.
245      */
246     @Test
247     public void moduleTargetUnkeyedListNodeTest() throws Exception {
248         mockBodyReader("", xmlToPatchBodyReader, false);
249         final var inputStream = new ByteArrayInputStream("""
250             <yang-patch xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
251                 <patch-id>list-patch</patch-id>
252                 <comment>YANG patch comment</comment>
253                 <edit>
254                     <edit-id>edit1</edit-id>
255                     <operation>replace</operation>
256                     <target>/list-model:cont-root/list-model:cont1/list-model:unkeyed-list</target>
257                     <value>
258                         <unkeyed-list xmlns="list:ns">
259                             <leaf1>data1</leaf1>
260                             <leaf2>data2</leaf2>
261                         </unkeyed-list>
262                     </value>
263                 </edit>
264             </yang-patch>
265             """.getBytes(StandardCharsets.UTF_8));
266         final var expectedData = Builders.unkeyedListBuilder()
267                 .withNodeIdentifier(new NodeIdentifier(LIST_QNAME))
268                 .withChild(Builders.unkeyedListEntryBuilder()
269                         .withNodeIdentifier(new NodeIdentifier(LIST_QNAME))
270                         .withChild(ImmutableNodes.leafNode(LIST_LEAF1_QNAME, "data1"))
271                         .withChild(ImmutableNodes.leafNode(LIST_LEAF2_QNAME, "data2"))
272                         .build())
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(LIST_QNAME, data.getIdentifier().getNodeType());
278         assertEquals(expectedData, data);
279     }
280
281     /**
282      * Test of Yang Patch on the top case node element.
283      */
284     @Test
285     public void moduleTargetCaseNodeTest() throws Exception {
286         mockBodyReader("", xmlToPatchBodyReader, false);
287         final var inputStream = new ByteArrayInputStream("""
288             <yang-patch xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
289                 <patch-id>choice-patch</patch-id>
290                 <comment>YANG patch comment</comment>
291                 <edit>
292                     <edit-id>edit1</edit-id>
293                     <operation>replace</operation>
294                     <target>/choice-model:cont-root/choice-model:cont1/choice-model:case-cont1</target>
295                     <value>
296                         <case-cont1 xmlns="choice:ns">
297                             <case-leaf1>data</case-leaf1>
298                         </case-cont1>
299                     </value>
300                 </edit>
301             </yang-patch>
302             """.getBytes(StandardCharsets.UTF_8));
303         final var expectedData = Builders.containerBuilder()
304                 .withNodeIdentifier(new NodeIdentifier(CHOICE_CONT_QNAME))
305                 .withChild(ImmutableNodes.leafNode(CASE_LEAF1_QNAME, "data"))
306                 .build();
307         final var returnValue = xmlToPatchBodyReader.readFrom(null, null, null, mediaType, null, inputStream);
308         checkPatchContext(returnValue);
309         final var data = returnValue.getData().get(0).getNode();
310         assertEquals(CHOICE_CONT_QNAME, data.getIdentifier().getNodeType());
311         assertEquals(expectedData, data);
312     }
313
314     /**
315      * Test reading simple leaf value.
316      */
317     @Test
318     public void modulePatchSimpleLeafValueTest() throws Exception {
319         mockBodyReader("instance-identifier-patch-module:patch-cont/my-list1=leaf1", xmlToPatchBodyReader, false);
320         final var inputStream = new ByteArrayInputStream("""
321                 <yang-patch xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
322                     <patch-id>test-patch</patch-id>
323                     <comment>this is test patch</comment>
324                     <edit>
325                         <edit-id>edit1</edit-id>
326                         <operation>replace</operation>
327                         <target>/my-list2=my-leaf20/name</target>
328                         <value>
329                             <name xmlns="instance:identifier:patch:module">my-leaf20</name>
330                         </value>
331                     </edit>
332                 </yang-patch>
333                 """.getBytes(StandardCharsets.UTF_8));
334         final var returnValue = xmlToPatchBodyReader.readFrom(null, null, null, mediaType, null, inputStream);
335         checkPatchContext(returnValue);
336         final var data = returnValue.getData().get(0).getNode();
337         assertEquals(LEAF_NAME_QNAME, data.getIdentifier().getNodeType());
338         assertEquals(ImmutableNodes.leafNode(LEAF_NAME_QNAME, "my-leaf20"), data);
339     }
340 }