Move AbstractBody et al.
[netconf.git] / restconf / restconf-nb / src / test / java / org / opendaylight / restconf / nb / rfc8040 / databind / XmlPatchBodyTest.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.databind;
9
10 import static org.junit.Assert.assertEquals;
11 import static org.junit.Assert.assertThrows;
12
13 import org.junit.Test;
14 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
15 import org.opendaylight.restconf.server.api.XmlPatchBody;
16 import org.opendaylight.yangtools.yang.common.ErrorTag;
17 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
18 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
19 import org.opendaylight.yangtools.yang.data.spi.node.ImmutableNodes;
20
21 public class XmlPatchBodyTest extends AbstractPatchBodyTest {
22     public XmlPatchBodyTest() {
23         super(XmlPatchBody::new);
24     }
25
26     @Test
27     public final void moduleDataTest() throws Exception {
28         checkPatchContext(parse(mountPrefix(), "instance-identifier-patch-module:patch-cont/my-list1=leaf1", """
29             <yang-patch xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
30                 <patch-id>test-patch</patch-id>
31                 <comment>this is test patch</comment>
32                 <edit>
33                     <edit-id>edit1</edit-id>
34                     <operation>create</operation>
35                     <target>/my-list2=my-leaf20</target>
36                     <value>
37                         <my-list2 xmlns="instance:identifier:patch:module">
38                             <name>my-leaf20</name>
39                             <my-leaf21>I am leaf21-0</my-leaf21>
40                             <my-leaf22>I am leaf22-0</my-leaf22>
41                         </my-list2>
42                     </value>
43                 </edit>
44                 <edit>
45                     <edit-id>edit2</edit-id>
46                     <operation>create</operation>
47                     <target>/my-list2=my-leaf21</target>
48                     <value>
49                         <my-list2 xmlns="instance:identifier:patch:module">
50                             <name>my-leaf21</name>
51                             <my-leaf21>I am leaf21-1</my-leaf21>
52                             <my-leaf22>I am leaf22-1</my-leaf22>
53                         </my-list2>
54                     </value>
55                 </edit>
56             </yang-patch>"""));
57     }
58
59     /**
60      * Test trying to use Patch create operation which requires value without value. Error code 400 should be returned.
61      */
62     @Test
63     public final void moduleDataValueMissingNegativeTest() throws Exception {
64         final var ex = assertThrows(RestconfDocumentedException.class,
65             () -> parse(mountPrefix(), "instance-identifier-patch-module:patch-cont/my-list1=leaf1", """
66                 <yang-patch xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
67                     <patch-id>test-patch</patch-id>
68                     <comment>Test patch with missing value node for create operation</comment>
69                     <edit>
70                         <edit-id>edit1</edit-id>
71                         <operation>create</operation>
72                         <target>/my-list2</target>
73                     </edit>
74                 </yang-patch>"""));
75         assertEquals(ErrorTag.MALFORMED_MESSAGE, ex.getErrors().get(0).getErrorTag());
76     }
77
78     /**
79      * Test trying to use value with Patch delete operation which does not support value. Error code 400 should be
80      * returned.
81      */
82     @Test
83     public final void moduleDataNotValueNotSupportedNegativeTest() throws Exception {
84         final var ex = assertThrows(RestconfDocumentedException.class,
85             () -> parse(mountPrefix(), "instance-identifier-patch-module:patch-cont/my-list1=leaf1", """
86                 <yang-patch xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
87                     <patch-id>test-patch</patch-id>
88                     <comment>Test patch with not allowed value node for delete operation</comment>
89                     <edit>
90                         <edit-id>edit1</edit-id>
91                         <operation>delete</operation>
92                         <target>/my-list2/my-leaf21</target>
93                         <value>
94                             <my-list2 xmlns="instance:identifier:patch:module">
95                                 <name>my-leaf20</name>
96                                 <my-leaf21>I am leaf21-0</my-leaf21>
97                                 <my-leaf22>I am leaf22-0</my-leaf22>
98                             </my-list2>
99                         </value>
100                     </edit>
101                 </yang-patch>"""));
102         assertEquals(ErrorTag.MALFORMED_MESSAGE, ex.getErrors().get(0).getErrorTag());
103     }
104
105     /**
106      * Test of YANG Patch with absolute target path.
107      */
108     @Test
109     public final void moduleDataAbsoluteTargetPathTest() throws Exception {
110         checkPatchContext(parse(mountPrefix(), "", """
111             <yang-patch xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
112                 <patch-id>test-patch</patch-id>
113                 <comment>Test patch with absolute target path</comment>
114                 <edit>
115                     <edit-id>edit1</edit-id>
116                     <operation>create</operation>
117                     <target>/instance-identifier-patch-module:patch-cont/my-list1=leaf1/my-list2=my-leaf20</target>
118                     <value>
119                         <my-list2 xmlns="instance:identifier:patch:module">
120                             <name>my-leaf20</name>
121                             <my-leaf21>I am leaf21-0</my-leaf21>
122                             <my-leaf22>I am leaf22-0</my-leaf22>
123                         </my-list2>
124                     </value>
125                 </edit>
126                 <edit>
127                     <edit-id>edit2</edit-id>
128                     <operation>create</operation>
129                     <target>/instance-identifier-patch-module:patch-cont/my-list1=leaf1/my-list2=my-leaf21</target>
130                     <value>
131                         <my-list2 xmlns="instance:identifier:patch:module">
132                             <name>my-leaf21</name>
133                             <my-leaf21>I am leaf21-1</my-leaf21>
134                             <my-leaf22>I am leaf22-1</my-leaf22>
135                         </my-list2>
136                     </value>
137                 </edit>
138             </yang-patch>"""));
139     }
140
141     /**
142      * Test using Patch when target is completely specified in request URI and thus target leaf contains only '/' sign.
143      */
144     @Test
145     public final void modulePatchCompleteTargetInURITest() throws Exception {
146         checkPatchContext(parse(mountPrefix(), "instance-identifier-patch-module:patch-cont", """
147             <yang-patch xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
148                 <patch-id>test-patch</patch-id>
149                 <comment>Test to create and replace data in container directly using / sign as a target</comment>
150                 <edit>
151                     <edit-id>edit1</edit-id>
152                     <operation>create</operation>
153                     <target>/</target>
154                     <value>
155                         <patch-cont xmlns="instance:identifier:patch:module">
156                             <my-list1>
157                                 <name>my-list1 - A</name>
158                                 <my-leaf11>I am leaf11-0</my-leaf11>
159                                 <my-leaf12>I am leaf12-1</my-leaf12>
160                             </my-list1>
161                             <my-list1>
162                                 <name>my-list1 - B</name>
163                                 <my-leaf11>I am leaf11-0</my-leaf11>
164                                 <my-leaf12>I am leaf12-1</my-leaf12>
165                             </my-list1>
166                         </patch-cont>
167                     </value>
168                 </edit>
169                 <edit>
170                     <edit-id>edit2</edit-id>
171                     <operation>replace</operation>
172                     <target>/</target>
173                     <value>
174                         <patch-cont xmlns="instance:identifier:patch:module">
175                             <my-list1>
176                                 <name>my-list1 - Replacing</name>
177                                 <my-leaf11>I am leaf11-0</my-leaf11>
178                                 <my-leaf12>I am leaf12-1</my-leaf12>
179                             </my-list1>
180                         </patch-cont>
181                     </value>
182                 </edit>
183             </yang-patch>"""));
184     }
185
186     /**
187      * Test of Yang Patch merge operation on list. Test consists of two edit operations - replace and merge.
188      */
189     @Test
190     public final void moduleDataMergeOperationOnListTest() throws Exception {
191         checkPatchContext(parse(mountPrefix(), "instance-identifier-patch-module:patch-cont/my-list1=leaf1", """
192             <yang-patch xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
193                 <patch-id>Test merge operation</patch-id>
194                 <comment>This is test patch for merge operation on list</comment>
195                 <edit>
196                     <edit-id>edit1</edit-id>
197                     <operation>replace</operation>
198                     <target>/my-list2=my-leaf20</target>
199                     <value>
200                         <my-list2 xmlns="instance:identifier:patch:module">
201                             <name>my-leaf20</name>
202                             <my-leaf21>I am leaf21-0</my-leaf21>
203                             <my-leaf22>I am leaf22-0</my-leaf22>
204                         </my-list2>
205                     </value>
206                 </edit>
207                 <edit>
208                     <edit-id>edit2</edit-id>
209                     <operation>merge</operation>
210                     <target>/my-list2=my-leaf21</target>
211                     <value>
212                         <my-list2 xmlns="instance:identifier:patch:module">
213                             <name>my-leaf21</name>
214                             <my-leaf21>I am leaf21-1</my-leaf21>
215                             <my-leaf22>I am leaf22-1</my-leaf22>
216                         </my-list2>
217                     </value>
218                 </edit>
219             </yang-patch>"""));
220     }
221
222     /**
223      * Test of Yang Patch merge operation on container. Test consists of two edit operations - create and merge.
224      */
225     @Test
226     public final void moduleDataMergeOperationOnContainerTest() throws Exception {
227         checkPatchContext(parse(mountPrefix(), "instance-identifier-patch-module:patch-cont", """
228             <yang-patch xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
229                 <patch-id>Test merge operation</patch-id>
230                 <comment>This is test patch for merge operation on container</comment>
231                 <edit>
232                     <edit-id>edit1</edit-id>
233                     <operation>create</operation>
234                     <target>/</target>
235                     <value>
236                         <patch-cont xmlns="instance:identifier:patch:module">
237                             <my-list1>
238                                 <name>my-list1 - A</name>
239                                 <my-leaf11>I am leaf11-0</my-leaf11>
240                                 <my-leaf12>I am leaf12-1</my-leaf12>
241                             </my-list1>
242                             <my-list1>
243                                 <name>my-list1 - B</name>
244                                 <my-leaf11>I am leaf11-0</my-leaf11>
245                                 <my-leaf12>I am leaf12-1</my-leaf12>
246                             </my-list1>
247                         </patch-cont>
248                     </value>
249                 </edit>
250                 <edit>
251                     <edit-id>edit2</edit-id>
252                     <operation>merge</operation>
253                     <target>/</target>
254                     <value>
255                         <patch-cont xmlns="instance:identifier:patch:module">
256                             <my-list1>
257                                 <name>my-list1 - Merged</name>
258                                 <my-leaf11>I am leaf11-0</my-leaf11>
259                                 <my-leaf12>I am leaf12-1</my-leaf12>
260                             </my-list1>
261                         </patch-cont>
262                     </value>
263                 </edit>
264             </yang-patch>"""));
265     }
266
267     /**
268      * Test of Yang Patch on the top-level container with empty URI for data root.
269      */
270     @Test
271     public final void modulePatchTargetTopLevelContainerWithEmptyURITest() throws Exception {
272         checkPatchContext(parse(mountPrefix(), "", """
273             <yang-patch xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
274                 <patch-id>test-patch</patch-id>
275                 <comment>Test patch applied to the top-level container with empty URI</comment>
276                 <edit>
277                     <edit-id>edit1</edit-id>
278                     <operation>replace</operation>
279                     <target>/instance-identifier-patch-module:patch-cont</target>
280                     <value>
281                         <patch-cont xmlns="instance:identifier:patch:module">
282                             <my-list1>
283                                 <name>my-leaf10</name>
284                             </my-list1>
285                         </patch-cont>
286                     </value>
287                 </edit>
288             </yang-patch>"""));
289     }
290
291     /**
292      * Test of YANG Patch on the top-level container with the full path in the URI and "/" in 'target'.
293      */
294     @Test
295     public final void modulePatchTargetTopLevelContainerWithFullPathURITest() throws Exception {
296         final var returnValue = parse(mountPrefix(), "instance-identifier-patch-module:patch-cont", """
297             <yang-patch xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
298                 <patch-id>test-patch</patch-id>
299                 <comment>Test patch applied to the top-level container with '/' in target</comment>
300                 <edit>
301                     <edit-id>edit1</edit-id>
302                     <operation>replace</operation>
303                     <target>/</target>
304                     <value>
305                         <patch-cont xmlns="instance:identifier:patch:module">
306                             <my-list1>
307                                 <name>my-leaf-set</name>
308                                 <my-leaf11>leaf-a</my-leaf11>
309                                 <my-leaf12>leaf-b</my-leaf12>
310                             </my-list1>
311                         </patch-cont>
312                     </value>
313                 </edit>
314             </yang-patch>""");
315         checkPatchContext(returnValue);
316         assertEquals(ImmutableNodes.newContainerBuilder()
317             .withNodeIdentifier(new NodeIdentifier(PATCH_CONT_QNAME))
318             .withChild(ImmutableNodes.newSystemMapBuilder()
319                 .withNodeIdentifier(new NodeIdentifier(MY_LIST1_QNAME))
320                 .withChild(ImmutableNodes.newMapEntryBuilder()
321                     .withNodeIdentifier(NodeIdentifierWithPredicates.of(MY_LIST1_QNAME, LEAF_NAME_QNAME, "my-leaf-set"))
322                     .withChild(ImmutableNodes.leafNode(LEAF_NAME_QNAME, "my-leaf-set"))
323                     .withChild(ImmutableNodes.leafNode(MY_LEAF11_QNAME, "leaf-a"))
324                     .withChild(ImmutableNodes.leafNode(MY_LEAF12_QNAME, "leaf-b"))
325                     .build())
326                 .build())
327             .build(), returnValue.entities().get(0).getNode());
328     }
329
330     /**
331      * Test of YANG Patch on the second-level list with the full path in the URI and "/" in 'target'.
332      */
333     @Test
334     public final void modulePatchTargetSecondLevelListWithFullPathURITest() throws Exception {
335         final var returnValue = parse(mountPrefix(), "instance-identifier-patch-module:patch-cont/my-list1=my-leaf-set",
336             """
337                 <yang-patch xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
338                     <patch-id>test-patch</patch-id>
339                     <comment>Test patch applied to the second-level list with '/' in target</comment>
340                     <edit>
341                         <edit-id>edit1</edit-id>
342                         <operation>replace</operation>
343                         <target>/</target>
344                         <value>
345                             <my-list1 xmlns="instance:identifier:patch:module">
346                                 <name>my-leaf-set</name>
347                                 <my-leaf11>leaf-a</my-leaf11>
348                                 <my-leaf12>leaf-b</my-leaf12>
349                             </my-list1>
350                         </value>
351                     </edit>
352                 </yang-patch>""");
353         checkPatchContext(returnValue);
354         assertEquals(ImmutableNodes.newSystemMapBuilder()
355             .withNodeIdentifier(new NodeIdentifier(MY_LIST1_QNAME))
356             .withChild(ImmutableNodes.newMapEntryBuilder()
357                 .withNodeIdentifier(NodeIdentifierWithPredicates.of(MY_LIST1_QNAME, LEAF_NAME_QNAME, "my-leaf-set"))
358                 .withChild(ImmutableNodes.leafNode(LEAF_NAME_QNAME, "my-leaf-set"))
359                 .withChild(ImmutableNodes.leafNode(MY_LEAF11_QNAME, "leaf-a"))
360                 .withChild(ImmutableNodes.leafNode(MY_LEAF12_QNAME, "leaf-b"))
361                 .build())
362             .build(), returnValue.entities().get(0).getNode());
363     }
364
365     /**
366      * Test of Yang Patch on the top augmented element.
367      */
368     @Test
369     public final void moduleTargetTopLevelAugmentedContainerTest() throws Exception {
370         final var returnValue = parse(mountPrefix(), "", """
371             <yang-patch xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
372                 <patch-id>test-patch</patch-id>
373                 <comment>This test patch for augmented element</comment>
374                 <edit>
375                     <edit-id>edit1</edit-id>
376                     <operation>replace</operation>
377                     <target>/test-m:container-root/test-m:container-lvl1/test-m-aug:container-aug</target>
378                     <value>
379                         <container-aug xmlns="test-ns-aug">
380                             <leaf-aug>data</leaf-aug>
381                         </container-aug>
382                     </value>
383                 </edit>
384             </yang-patch>""");
385         checkPatchContext(returnValue);
386         assertEquals(ImmutableNodes.newContainerBuilder()
387             .withNodeIdentifier(new NodeIdentifier(CONT_AUG_QNAME))
388             .withChild(ImmutableNodes.leafNode(LEAF_AUG_QNAME, "data"))
389             .build(), returnValue.entities().get(0).getNode());
390     }
391
392     /**
393      * Test of YANG Patch on the top system map node element.
394      */
395     @Test
396     public final void moduleTargetMapNodeTest() throws Exception {
397         final var returnValue = parse(mountPrefix(), "", """
398             <yang-patch xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
399                 <patch-id>map-patch</patch-id>
400                 <comment>YANG patch comment</comment>
401                 <edit>
402                     <edit-id>edit1</edit-id>
403                     <operation>replace</operation>
404                     <target>/map-model:cont-root/map-model:cont1/map-model:my-map=key</target>
405                     <value>
406                         <my-map xmlns="map:ns">
407                             <key-leaf>key</key-leaf>
408                             <data-leaf>data</data-leaf>
409                         </my-map>
410                     </value>
411                 </edit>
412             </yang-patch>""");
413         checkPatchContext(returnValue);
414         assertEquals(ImmutableNodes.newSystemMapBuilder()
415             .withNodeIdentifier(new NodeIdentifier(MAP_CONT_QNAME))
416             .withChild(ImmutableNodes.newMapEntryBuilder()
417                 .withNodeIdentifier(NodeIdentifierWithPredicates.of(MAP_CONT_QNAME, KEY_LEAF_QNAME, "key"))
418                 .withChild(ImmutableNodes.leafNode(KEY_LEAF_QNAME, "key"))
419                 .withChild(ImmutableNodes.leafNode(DATA_LEAF_QNAME, "data"))
420                 .build())
421             .build(), returnValue.entities().get(0).getNode());
422     }
423
424     /**
425      * Test of YANG Patch on the leaf set node element.
426      */
427     @Test
428     public final void modulePatchTargetLeafSetNodeTest() throws Exception {
429         final var returnValue = parse(mountPrefix(), "", """
430             <yang-patch xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
431                 <patch-id>set-patch</patch-id>
432                 <comment>YANG patch comment</comment>
433                 <edit>
434                     <edit-id>edit1</edit-id>
435                     <operation>replace</operation>
436                     <target>/set-model:cont-root/set-model:cont1/set-model:my-set="data1"</target>
437                     <value>
438                         <my-set xmlns="set:ns">data1</my-set>
439                     </value>
440                 </edit>
441             </yang-patch>""");
442         checkPatchContext(returnValue);
443         assertEquals(ImmutableNodes.newSystemLeafSetBuilder()
444             .withNodeIdentifier(new NodeIdentifier(LEAF_SET_QNAME))
445             .withChildValue("data1")
446             .build(), returnValue.entities().get(0).getNode());
447     }
448
449     /**
450      * Test of Yang Patch on the top unkeyed list element.
451      */
452     @Test
453     public final void moduleTargetUnkeyedListNodeTest() throws Exception {
454         final var returnValue = parse(mountPrefix(), "", """
455             <yang-patch xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
456                 <patch-id>list-patch</patch-id>
457                 <comment>YANG patch comment</comment>
458                 <edit>
459                     <edit-id>edit1</edit-id>
460                     <operation>replace</operation>
461                     <target>/list-model:cont-root/list-model:cont1/list-model:unkeyed-list</target>
462                     <value>
463                         <unkeyed-list xmlns="list:ns">
464                             <leaf1>data1</leaf1>
465                             <leaf2>data2</leaf2>
466                         </unkeyed-list>
467                     </value>
468                 </edit>
469             </yang-patch>""");
470         checkPatchContext(returnValue);
471         assertEquals(ImmutableNodes.newUnkeyedListBuilder()
472             .withNodeIdentifier(new NodeIdentifier(LIST_QNAME))
473             .withChild(ImmutableNodes.newUnkeyedListEntryBuilder()
474                 .withNodeIdentifier(new NodeIdentifier(LIST_QNAME))
475                 .withChild(ImmutableNodes.leafNode(LIST_LEAF1_QNAME, "data1"))
476                 .withChild(ImmutableNodes.leafNode(LIST_LEAF2_QNAME, "data2"))
477                 .build())
478             .build(), returnValue.entities().get(0).getNode());
479     }
480
481     /**
482      * Test of Yang Patch on the top case node element.
483      */
484     @Test
485     public final void moduleTargetCaseNodeTest() throws Exception {
486         final var returnValue = parse(mountPrefix(), "", """
487             <yang-patch xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
488                 <patch-id>choice-patch</patch-id>
489                 <comment>YANG patch comment</comment>
490                 <edit>
491                     <edit-id>edit1</edit-id>
492                     <operation>replace</operation>
493                     <target>/choice-model:cont-root/choice-model:cont1/choice-model:case-cont1</target>
494                     <value>
495                         <case-cont1 xmlns="choice:ns">
496                             <case-leaf1>data</case-leaf1>
497                         </case-cont1>
498                     </value>
499                 </edit>
500             </yang-patch>""");
501         checkPatchContext(returnValue);
502         assertEquals(ImmutableNodes.newContainerBuilder()
503             .withNodeIdentifier(new NodeIdentifier(CHOICE_CONT_QNAME))
504             .withChild(ImmutableNodes.leafNode(CASE_LEAF1_QNAME, "data"))
505             .build(), returnValue.entities().get(0).getNode());
506     }
507
508     /**
509      * Test reading simple leaf value.
510      */
511     @Test
512     public final void modulePatchSimpleLeafValueTest() throws Exception {
513         final var returnValue = parse(mountPrefix(), "instance-identifier-patch-module:patch-cont/my-list1=leaf1", """
514             <yang-patch xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
515                 <patch-id>test-patch</patch-id>
516                 <comment>this is test patch</comment>
517                 <edit>
518                     <edit-id>edit1</edit-id>
519                     <operation>replace</operation>
520                     <target>/my-list2=my-leaf20/name</target>
521                     <value>
522                         <name xmlns="instance:identifier:patch:module">my-leaf20</name>
523                     </value>
524                 </edit>
525             </yang-patch>""");
526         checkPatchContext(returnValue);
527         assertEquals(ImmutableNodes.leafNode(LEAF_NAME_QNAME, "my-leaf20"), returnValue.entities().get(0).getNode());
528     }
529 }