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