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