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