Introduce AbstractBody
[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 inputStream = JsonPatchBodyTest.class.getResourceAsStream(
136             "/instanceidentifier/json/jsonPATCHdataValueNotSupported.json");
137         final var ex = assertThrows(RestconfDocumentedException.class,
138             () -> parse(mountPrefix() + "instance-identifier-patch-module:patch-cont/my-list1=leaf1", inputStream));
139         assertEquals(ErrorTag.MALFORMED_MESSAGE, ex.getErrors().get(0).getErrorTag());
140     }
141
142     /**
143      * Test using Patch when target is completely specified in request URI and thus target leaf contains only '/' sign.
144      */
145     @Test
146     public final void modulePatchCompleteTargetInURITest() throws Exception {
147         checkPatchContext(parse(mountPrefix() + "instance-identifier-patch-module:patch-cont",
148             JsonPatchBodyTest.class.getResourceAsStream(
149                 "/instanceidentifier/json/jsonPATCHdataCompleteTargetInURI.json")));
150     }
151
152     /**
153      * Test of YANG Patch merge operation on list. Test consists of two edit operations - replace and merge.
154      */
155     @Test
156     public final void modulePatchMergeOperationOnListTest() throws Exception {
157         checkPatchContext(parse(mountPrefix() + "instance-identifier-patch-module:patch-cont/my-list1=leaf1",
158             JsonPatchBodyTest.class.getResourceAsStream(
159                 "/instanceidentifier/json/jsonPATCHMergeOperationOnList.json")));
160     }
161
162     /**
163      * Test of YANG Patch merge operation on container. Test consists of two edit operations - create and merge.
164      */
165     @Test
166     public final void modulePatchMergeOperationOnContainerTest() throws Exception {
167         checkPatchContext(parse(mountPrefix() + "instance-identifier-patch-module:patch-cont", """
168             {
169               "ietf-yang-patch:yang-patch" : {
170                 "patch-id" : "Test merge operation",
171                 "comment" : "This is test patch for merge operation on container",
172                 "edit" : [
173                   {
174                     "edit-id": "edit1",
175                     "operation": "create",
176                     "target": "/",
177                     "value": {
178                       "patch-cont": {
179                         "my-list1": [
180                           {
181                             "name": "my-list1 - A",
182                             "my-leaf11": "I am leaf11-0",
183                             "my-leaf12": "I am leaf12-1"
184                           },
185                           {
186                             "name": "my-list1 - B",
187                             "my-leaf11": "I am leaf11-0",
188                             "my-leaf12": "I am leaf12-1"
189                           }
190                         ]
191                       }
192                     }
193                   },
194                   {
195                     "edit-id": "edit2",
196                     "operation": "merge",
197                     "target": "/",
198                     "value": {
199                       "patch-cont": {
200                         "my-list1": {
201                           "name": "my-list1 - Merged",
202                           "my-leaf11": "I am leaf11-0",
203                           "my-leaf12": "I am leaf12-1"
204                         }
205                       }
206                     }
207                   }
208                 ]
209               }
210             }"""));
211     }
212
213     /**
214      * Test reading simple leaf value.
215      */
216     @Test
217     public final void modulePatchSimpleLeafValueTest() throws Exception {
218         final var returnValue = parse(mountPrefix() + "instance-identifier-patch-module:patch-cont/my-list1=leaf1",
219             JsonPatchBodyTest.class.getResourceAsStream("/instanceidentifier/json/jsonPATCHSimpleLeafValue.json"));
220         checkPatchContext(returnValue);
221         assertEquals(ImmutableNodes.leafNode(LEAF_NAME_QNAME, "my-leaf20"), returnValue.getData().get(0).getNode());
222     }
223
224     /**
225      * Test of YANG Patch on the top-level container with empty URI for data root.
226      */
227     @Test
228     public final void modulePatchTargetTopLevelContainerWithEmptyURITest() throws Exception {
229         checkPatchContext(parse(mountPrefix(),
230             JsonPatchBodyTest.class.getResourceAsStream(
231                 "/instanceidentifier/json/jsonPATCHTargetTopLevelContainerWithEmptyURI.json")));
232     }
233
234     /**
235      * Test of YANG Patch on the top-level container with the full path in the URI and "/" in 'target'.
236      */
237     @Test
238     public final void modulePatchTargetTopLevelContainerWithFullPathURITest() throws Exception {
239         final var returnValue = parse(mountPrefix() + "instance-identifier-patch-module:patch-cont", """
240             {
241               "ietf-yang-patch:yang-patch": {
242                 "patch-id": "test-patch",
243                 "comment": "Test patch applied to the top-level container with '/' in target",
244                 "edit": [
245                   {
246                     "edit-id": "edit1",
247                     "operation": "replace",
248                     "target": "/",
249                     "value": {
250                       "patch-cont": {
251                         "my-list1": [
252                           {
253                             "name": "my-leaf-set",
254                             "my-leaf11": "leaf-a",
255                             "my-leaf12": "leaf-b"
256                           }
257                         ]
258                       }
259                     }
260                   }
261                 ]
262               }
263             }""");
264         checkPatchContext(returnValue);
265         assertEquals(Builders.containerBuilder()
266             .withNodeIdentifier(new NodeIdentifier(PATCH_CONT_QNAME))
267             .withChild(Builders.mapBuilder()
268                 .withNodeIdentifier(new NodeIdentifier(MY_LIST1_QNAME))
269                 .withChild(Builders.mapEntryBuilder()
270                     .withNodeIdentifier(NodeIdentifierWithPredicates.of(MY_LIST1_QNAME, LEAF_NAME_QNAME, "my-leaf-set"))
271                     .withChild(ImmutableNodes.leafNode(LEAF_NAME_QNAME, "my-leaf-set"))
272                     .withChild(ImmutableNodes.leafNode(MY_LEAF11_QNAME, "leaf-a"))
273                     .withChild(ImmutableNodes.leafNode(MY_LEAF12_QNAME, "leaf-b"))
274                     .build())
275                 .build())
276             .build(), returnValue.getData().get(0).getNode());
277     }
278
279     /**
280      * Test of YANG Patch on the second-level list with the full path in the URI and "/" in 'target'.
281      */
282     @Test
283     public final void modulePatchTargetSecondLevelListWithFullPathURITest() throws Exception {
284         final var returnValue = parse(
285             mountPrefix() + "instance-identifier-patch-module:patch-cont/my-list1=my-leaf-set", """
286             {
287               "ietf-yang-patch:yang-patch": {
288                 "patch-id": "test-patch",
289                 "comment": "Test patch applied to the second-level list with '/' in target",
290                 "edit": [
291                   {
292                     "edit-id": "edit1",
293                     "operation": "replace",
294                     "target": "/",
295                     "value": {
296                       "my-list1": [
297                         {
298                           "name": "my-leaf-set",
299                           "my-leaf11": "leaf-a",
300                           "my-leaf12": "leaf-b"
301                         }
302                       ]
303                     }
304                   }
305                 ]
306               }
307             }""");
308         checkPatchContext(returnValue);
309         assertEquals(Builders.mapBuilder()
310             .withNodeIdentifier(new NodeIdentifier(MY_LIST1_QNAME))
311             .withChild(Builders.mapEntryBuilder()
312                     .withNodeIdentifier(NodeIdentifierWithPredicates.of(
313                             MY_LIST1_QNAME, LEAF_NAME_QNAME, "my-leaf-set"))
314                     .withChild(ImmutableNodes.leafNode(LEAF_NAME_QNAME, "my-leaf-set"))
315                     .withChild(ImmutableNodes.leafNode(MY_LEAF11_QNAME, "leaf-a"))
316                     .withChild(ImmutableNodes.leafNode(MY_LEAF12_QNAME, "leaf-b"))
317                     .build())
318             .build(), returnValue.getData().get(0).getNode());
319     }
320
321     /**
322      * Test of Yang Patch on the top augmented element.
323      */
324     @Test
325     public final void modulePatchTargetTopLevelAugmentedContainerTest() throws Exception {
326         final var returnValue = parse(mountPrefix(), """
327             {
328                 "ietf-yang-patch:yang-patch": {
329                     "patch-id": "test-patch",
330                     "comment": "comment",
331                     "edit": [
332                         {
333                             "edit-id": "edit1",
334                             "operation": "replace",
335                             "target": "/test-m:container-root/test-m:container-lvl1/test-m-aug:container-aug",
336                             "value": {
337                                 "container-aug": {
338                                     "leaf-aug": "data"
339                                 }
340                             }
341                         }
342                     ]
343                 }
344             }""");
345         checkPatchContext(returnValue);
346         assertEquals(Builders.containerBuilder()
347             .withNodeIdentifier(new NodeIdentifier(CONT_AUG_QNAME))
348             .withChild(ImmutableNodes.leafNode(LEAF_AUG_QNAME, "data"))
349             .build(), returnValue.getData().get(0).getNode());
350     }
351
352     /**
353      * Test of YANG Patch on the system map node element.
354      */
355     @Test
356     public final void modulePatchTargetMapNodeTest() throws Exception {
357         final var returnValue = parse(mountPrefix(), """
358             {
359                 "ietf-yang-patch:yang-patch": {
360                     "patch-id": "map-patch",
361                     "comment": "comment",
362                     "edit": [
363                         {
364                             "edit-id": "edit1",
365                             "operation": "replace",
366                             "target": "/map-model:cont-root/map-model:cont1/map-model:my-map=key",
367                             "value": {
368                                 "my-map": {
369                                     "key-leaf": "key",
370                                     "data-leaf": "data"
371                                 }
372                             }
373                         }
374                     ]
375                 }
376             }""");
377         checkPatchContext(returnValue);
378         assertEquals(Builders.mapBuilder()
379             .withNodeIdentifier(new NodeIdentifier(MAP_CONT_QNAME))
380             .withChild(Builders.mapEntryBuilder()
381                 .withNodeIdentifier(NodeIdentifierWithPredicates.of(MAP_CONT_QNAME, KEY_LEAF_QNAME, "key"))
382                 .withChild(ImmutableNodes.leafNode(KEY_LEAF_QNAME, "key"))
383                 .withChild(ImmutableNodes.leafNode(DATA_LEAF_QNAME, "data"))
384                 .build())
385             .build(), returnValue.getData().get(0).getNode());
386     }
387
388     /**
389      * Test of Yang Patch on the leaf set node element.
390      */
391     @Test
392     public final void modulePatchTargetLeafSetNodeTest() throws Exception {
393         final var returnValue = parse(mountPrefix(), """
394             {
395                 "ietf-yang-patch:yang-patch": {
396                     "patch-id": "set-patch",
397                     "comment": "comment",
398                     "edit": [
399                         {
400                             "edit-id": "edit1",
401                             "operation": "replace",
402                             "target": "/set-model:cont-root/set-model:cont1/set-model:my-set=data1",
403                             "value": {
404                                 "my-set": [ "data1" ]
405                             }
406                         }
407                     ]
408                 }
409             }""");
410         checkPatchContext(returnValue);
411         assertEquals(Builders.leafSetBuilder()
412             .withNodeIdentifier(new NodeIdentifier(LEAF_SET_QNAME))
413             .withChild(Builders.leafSetEntryBuilder()
414                 .withNodeIdentifier(new NodeWithValue<>(LEAF_SET_QNAME, "data1"))
415                 .withValue("data1")
416                 .build())
417             .build(), returnValue.getData().get(0).getNode());
418     }
419
420     /**
421      * Test of Yang Patch on the unkeyed list node element.
422      */
423     @Test
424     public final void modulePatchTargetUnkeyedListNodeTest() throws Exception {
425         final var returnValue = parse(mountPrefix(), """
426             {
427                 "ietf-yang-patch:yang-patch": {
428                     "patch-id": "list-patch",
429                     "comment": "comment",
430                     "edit": [
431                         {
432                             "edit-id": "edit1",
433                             "operation": "replace",
434                             "target": "/list-model:cont-root/list-model:cont1/list-model:unkeyed-list",
435                             "value": {
436                                 "unkeyed-list": {
437                                     "leaf1": "data1",
438                                     "leaf2": "data2"
439                                 }
440                             }
441                         }
442                     ]
443                 }
444             }""");
445         checkPatchContext(returnValue);
446         assertEquals(Builders.unkeyedListBuilder()
447             .withNodeIdentifier(new NodeIdentifier(LIST_QNAME))
448             .withChild(Builders.unkeyedListEntryBuilder()
449                 .withNodeIdentifier(new NodeIdentifier(LIST_QNAME))
450                 .withChild(ImmutableNodes.leafNode(LIST_LEAF1_QNAME, "data1"))
451                 .withChild(ImmutableNodes.leafNode(LIST_LEAF2_QNAME, "data2"))
452                 .build())
453             .build(), returnValue.getData().get(0).getNode());
454     }
455
456     /**
457      * Test of Yang Patch on the case node element.
458      */
459     @Test
460     public final void modulePatchTargetCaseNodeTest() throws Exception {
461         final var returnValue = parse(mountPrefix(), """
462             {
463                 "ietf-yang-patch:yang-patch": {
464                     "patch-id": "choice-patch",
465                     "comment": "comment",
466                     "edit": [
467                         {
468                             "edit-id": "edit1",
469                             "operation": "replace",
470                             "target": "/choice-model:cont-root/choice-model:cont1/choice-model:case-cont1",
471                             "value": {
472                                 "case-cont1": {
473                                     "case-leaf1": "data"
474                                 }
475                             }
476                         }
477                     ]
478                 }
479             }""");
480         checkPatchContext(returnValue);
481         assertEquals(Builders.containerBuilder()
482             .withNodeIdentifier(new NodeIdentifier(CHOICE_CONT_QNAME))
483             .withChild(ImmutableNodes.leafNode(CASE_LEAF1_QNAME, "data"))
484             .build(), returnValue.getData().get(0).getNode());
485     }
486 }