Fix YANG patch request for augmented element
[netconf.git] / restconf / restconf-nb / src / test / java / org / opendaylight / restconf / nb / rfc8040 / jersey / providers / patch / JsonPatchBodyReaderTest.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.jersey.providers.patch;
9
10 import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
11 import static org.junit.Assert.assertEquals;
12 import static org.junit.Assert.assertThrows;
13
14 import java.io.ByteArrayInputStream;
15 import java.io.InputStream;
16 import java.nio.charset.StandardCharsets;
17 import javax.ws.rs.core.MediaType;
18 import org.junit.BeforeClass;
19 import org.junit.Test;
20 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
21 import org.opendaylight.restconf.common.patch.PatchContext;
22 import org.opendaylight.restconf.nb.rfc8040.jersey.providers.test.AbstractBodyReaderTest;
23 import org.opendaylight.restconf.nb.rfc8040.jersey.providers.test.JsonBodyReaderTest;
24 import org.opendaylight.yangtools.yang.common.ErrorTag;
25 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
26 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
27 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
28 import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
29 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
30 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
31
32 public class JsonPatchBodyReaderTest extends AbstractBodyReaderTest {
33
34     private final JsonPatchBodyReader jsonToPatchBodyReader;
35     private static EffectiveModelContext schemaContext;
36
37     public JsonPatchBodyReaderTest() throws Exception {
38         super(schemaContext);
39         jsonToPatchBodyReader = new JsonPatchBodyReader(databindProvider, mountPointService);
40     }
41
42     @Override
43     protected MediaType getMediaType() {
44         return new MediaType(APPLICATION_JSON, null);
45     }
46
47     @BeforeClass
48     public static void initialization() {
49         schemaContext = schemaContextLoader("/instanceidentifier/yang", schemaContext);
50     }
51
52     @Test
53     public void modulePatchDataTest() throws Exception {
54         final String uri = "instance-identifier-patch-module:patch-cont/my-list1=leaf1";
55         mockBodyReader(uri, jsonToPatchBodyReader, false);
56
57         final InputStream inputStream = JsonBodyReaderTest.class.getResourceAsStream(
58             "/instanceidentifier/json/jsonPATCHdata.json");
59
60         final PatchContext returnValue = jsonToPatchBodyReader.readFrom(null, null, null, mediaType, null, inputStream);
61         checkPatchContext(returnValue);
62     }
63
64     /**
65      * Test of successful Patch consisting of create and delete Patch operations.
66      */
67     @Test
68     public void modulePatchCreateAndDeleteTest() throws Exception {
69         final String uri = "instance-identifier-patch-module:patch-cont/my-list1=leaf1";
70         mockBodyReader(uri, jsonToPatchBodyReader, false);
71
72         final InputStream inputStream = JsonBodyReaderTest.class.getResourceAsStream(
73             "/instanceidentifier/json/jsonPATCHdataCreateAndDelete.json");
74
75         final PatchContext returnValue = jsonToPatchBodyReader.readFrom(null, null, null, mediaType, null, inputStream);
76         checkPatchContext(returnValue);
77     }
78
79     /**
80      * Test trying to use Patch create operation which requires value without value. Test should fail with
81      * {@link RestconfDocumentedException} with error code 400.
82      */
83     @Test
84     public void modulePatchValueMissingNegativeTest() throws Exception {
85         final String uri = "instance-identifier-patch-module:patch-cont/my-list1=leaf1";
86         mockBodyReader(uri, jsonToPatchBodyReader, false);
87
88         final InputStream inputStream = JsonBodyReaderTest.class.getResourceAsStream(
89             "/instanceidentifier/json/jsonPATCHdataValueMissing.json");
90
91         final RestconfDocumentedException ex = assertThrows(RestconfDocumentedException.class,
92             () -> jsonToPatchBodyReader.readFrom(null, null, null, mediaType, null, inputStream));
93         assertEquals(ErrorTag.MALFORMED_MESSAGE, ex.getErrors().get(0).getErrorTag());
94     }
95
96     /**
97      * Test trying to use value with Patch delete operation which does not support value. Test should fail with
98      * {@link RestconfDocumentedException} with error code 400.
99      */
100     @Test
101     public void modulePatchValueNotSupportedNegativeTest() throws Exception {
102         final String uri = "instance-identifier-patch-module:patch-cont/my-list1=leaf1";
103         mockBodyReader(uri, jsonToPatchBodyReader, false);
104
105         final InputStream inputStream = JsonBodyReaderTest.class.getResourceAsStream(
106             "/instanceidentifier/json/jsonPATCHdataValueNotSupported.json");
107
108         final RestconfDocumentedException ex = assertThrows(RestconfDocumentedException.class,
109             () -> jsonToPatchBodyReader.readFrom(null, null, null, mediaType, null, inputStream));
110         assertEquals(ErrorTag.MALFORMED_MESSAGE, ex.getErrors().get(0).getErrorTag());
111     }
112
113     /**
114      * Test using Patch when target is completely specified in request URI and thus target leaf contains only '/' sign.
115      */
116     @Test
117     public void modulePatchCompleteTargetInURITest() throws Exception {
118         final String uri = "instance-identifier-patch-module:patch-cont";
119         mockBodyReader(uri, jsonToPatchBodyReader, false);
120
121         final InputStream inputStream = JsonBodyReaderTest.class.getResourceAsStream(
122             "/instanceidentifier/json/jsonPATCHdataCompleteTargetInURI.json");
123
124         final PatchContext returnValue = jsonToPatchBodyReader.readFrom(null, null, null, mediaType, null, inputStream);
125         checkPatchContext(returnValue);
126     }
127
128     /**
129      * Test of Yang Patch merge operation on list. Test consists of two edit operations - replace and merge.
130      */
131     @Test
132     public void modulePatchMergeOperationOnListTest() throws Exception {
133         final String uri = "instance-identifier-patch-module:patch-cont/my-list1=leaf1";
134         mockBodyReader(uri, jsonToPatchBodyReader, false);
135
136         final InputStream inputStream = JsonBodyReaderTest.class.getResourceAsStream(
137             "/instanceidentifier/json/jsonPATCHMergeOperationOnList.json");
138
139         final PatchContext returnValue = jsonToPatchBodyReader.readFrom(null, null, null, mediaType, null, inputStream);
140         checkPatchContext(returnValue);
141     }
142
143     /**
144      * Test of Yang Patch merge operation on container. Test consists of two edit operations - create and merge.
145      */
146     @Test
147     public void modulePatchMergeOperationOnContainerTest() throws Exception {
148         final String uri = "instance-identifier-patch-module:patch-cont";
149         mockBodyReader(uri, jsonToPatchBodyReader, false);
150
151         final InputStream inputStream = JsonBodyReaderTest.class.getResourceAsStream(
152             "/instanceidentifier/json/jsonPATCHMergeOperationOnContainer.json");
153
154         final PatchContext returnValue = jsonToPatchBodyReader.readFrom(null, null, null, mediaType, null, inputStream);
155         checkPatchContext(returnValue);
156     }
157
158     /**
159      * Test reading simple leaf value.
160      */
161     @Test
162     public void modulePatchSimpleLeafValueTest() throws Exception {
163         final String uri = "instance-identifier-patch-module:patch-cont/my-list1=leaf1";
164         mockBodyReader(uri, jsonToPatchBodyReader, false);
165
166         final InputStream inputStream = JsonBodyReaderTest.class.getResourceAsStream(
167             "/instanceidentifier/json/jsonPATCHSimpleLeafValue.json");
168
169         final PatchContext returnValue = jsonToPatchBodyReader.readFrom(null, null, null, mediaType, null, inputStream);
170         checkPatchContext(returnValue);
171         final var data = returnValue.getData().get(0).getNode();
172         assertEquals(LEAF_NAME_QNAME, data.getIdentifier().getNodeType());
173         assertEquals(ImmutableNodes.leafNode(LEAF_NAME_QNAME, "my-leaf20"), data);
174     }
175
176     /**
177      * Test of Yang Patch on the top-level container with empty URI for data root.
178      */
179     @Test
180     public void modulePatchTargetTopLevelContainerWithEmptyURITest() throws Exception {
181         final String uri = "";
182         mockBodyReader(uri, jsonToPatchBodyReader, false);
183
184         final InputStream inputStream = JsonBodyReaderTest.class.getResourceAsStream(
185                 "/instanceidentifier/json/jsonPATCHTargetTopLevelContainerWithEmptyURI.json");
186
187         final PatchContext returnValue = jsonToPatchBodyReader.readFrom(null, null, null, mediaType, null, inputStream);
188         checkPatchContext(returnValue);
189     }
190
191     /**
192      * Test of Yang Patch on the top augmented element.
193      */
194     @Test
195     public void modulePatchTargetTopLevelAugmentedContainerTest() throws Exception {
196         mockBodyReader("", jsonToPatchBodyReader, false);
197         final var inputStream = new ByteArrayInputStream("""
198             {
199                 "ietf-yang-patch:yang-patch": {
200                     "patch-id": "test-patch",
201                     "comment": "comment",
202                     "edit": [
203                         {
204                             "edit-id": "edit1",
205                             "operation": "replace",
206                             "target": "/test-m:container-root/test-m:container-lvl1/test-m-aug:container-aug",
207                             "value": {
208                                 "container-aug": {
209                                     "leaf-aug": "data"
210                                 }
211                             }
212                         }
213                     ]
214                 }
215             }
216             """.getBytes(StandardCharsets.UTF_8));
217         final var expectedData = Builders.containerBuilder()
218                 .withNodeIdentifier(new NodeIdentifier(CONT_AUG_QNAME))
219                 .withChild(ImmutableNodes.leafNode(LEAF_AUG_QNAME, "data"))
220                 .build();
221         final var returnValue = jsonToPatchBodyReader.readFrom(null, null, null, mediaType, null, inputStream);
222         checkPatchContext(returnValue);
223         final var data = returnValue.getData().get(0).getNode();
224         assertEquals(CONT_AUG_QNAME, data.getIdentifier().getNodeType());
225         assertEquals(expectedData, data);
226     }
227
228     /**
229      * Test of Yang Patch on the system map node element.
230      */
231     @Test
232     public void modulePatchTargetMapNodeTest() throws Exception {
233         mockBodyReader("", jsonToPatchBodyReader, false);
234         final var inputStream = new ByteArrayInputStream("""
235             {
236                 "ietf-yang-patch:yang-patch": {
237                     "patch-id": "map-patch",
238                     "comment": "comment",
239                     "edit": [
240                         {
241                             "edit-id": "edit1",
242                             "operation": "replace",
243                             "target": "/map-model:cont-root/map-model:cont1/map-model:my-map=key",
244                             "value": {
245                                 "my-map": {
246                                     "key-leaf": "key",
247                                     "data-leaf": "data"
248                                 }
249                             }
250                         }
251                     ]
252                 }
253             }
254             """.getBytes(StandardCharsets.UTF_8));
255         final var expectedData = Builders.mapBuilder()
256                 .withNodeIdentifier(new NodeIdentifier(MAP_CONT_QNAME))
257                 .withChild(Builders.mapEntryBuilder()
258                         .withNodeIdentifier(NodeIdentifierWithPredicates.of(MAP_CONT_QNAME, KEY_LEAF_QNAME, "key"))
259                         .withChild(ImmutableNodes.leafNode(KEY_LEAF_QNAME, "key"))
260                         .withChild(ImmutableNodes.leafNode(DATA_LEAF_QNAME, "data"))
261                         .build())
262                 .build();
263         final var returnValue = jsonToPatchBodyReader.readFrom(null, null, null, mediaType, null, inputStream);
264         checkPatchContext(returnValue);
265         final var data = returnValue.getData().get(0).getNode();
266         assertEquals(MAP_CONT_QNAME, data.getIdentifier().getNodeType());
267         assertEquals(expectedData, data);
268     }
269
270     /**
271      * Test of Yang Patch on the leaf set node element.
272      */
273     @Test
274     public void modulePatchTargetLeafSetNodeTest() throws Exception {
275         mockBodyReader("", jsonToPatchBodyReader, false);
276         final var inputStream = new ByteArrayInputStream("""
277             {
278                 "ietf-yang-patch:yang-patch": {
279                     "patch-id": "set-patch",
280                     "comment": "comment",
281                     "edit": [
282                         {
283                             "edit-id": "edit1",
284                             "operation": "replace",
285                             "target": "/set-model:cont-root/set-model:cont1/set-model:my-set=data1",
286                             "value": {
287                                 "my-set": [ "data1" ]
288                             }
289                         }
290                     ]
291                 }
292             }
293             """.getBytes(StandardCharsets.UTF_8));
294         final var expectedData = Builders.leafSetBuilder()
295                 .withNodeIdentifier(new NodeIdentifier(LEAF_SET_QNAME))
296                 .withChild(Builders.leafSetEntryBuilder()
297                         .withNodeIdentifier(new NodeWithValue<>(LEAF_SET_QNAME, "data1"))
298                         .withValue("data1")
299                         .build())
300                 .build();
301         final var returnValue = jsonToPatchBodyReader.readFrom(null, null, null, mediaType, null, inputStream);
302         checkPatchContext(returnValue);
303         final var data = returnValue.getData().get(0).getNode();
304         assertEquals(LEAF_SET_QNAME, data.getIdentifier().getNodeType());
305         assertEquals(expectedData, data);
306     }
307
308     /**
309      * Test of Yang Patch on the unkeyed list node element.
310      */
311     @Test
312     public void modulePatchTargetUnkeyedListNodeTest() throws Exception {
313         mockBodyReader("", jsonToPatchBodyReader, false);
314         final var inputStream = new ByteArrayInputStream("""
315             {
316                 "ietf-yang-patch:yang-patch": {
317                     "patch-id": "list-patch",
318                     "comment": "comment",
319                     "edit": [
320                         {
321                             "edit-id": "edit1",
322                             "operation": "replace",
323                             "target": "/list-model:cont-root/list-model:cont1/list-model:unkeyed-list",
324                             "value": {
325                                 "unkeyed-list": {
326                                     "leaf1": "data1",
327                                     "leaf2": "data2"
328                                 }
329                             }
330                         }
331                     ]
332                 }
333             }
334             """.getBytes(StandardCharsets.UTF_8));
335         final var expectedData = Builders.unkeyedListBuilder()
336                 .withNodeIdentifier(new NodeIdentifier(LIST_QNAME))
337                 .withChild(Builders.unkeyedListEntryBuilder()
338                         .withNodeIdentifier(new NodeIdentifier(LIST_QNAME))
339                         .withChild(ImmutableNodes.leafNode(LIST_LEAF1_QNAME, "data1"))
340                         .withChild(ImmutableNodes.leafNode(LIST_LEAF2_QNAME, "data2"))
341                         .build())
342                 .build();
343         final var returnValue = jsonToPatchBodyReader.readFrom(null, null, null, mediaType, null, inputStream);
344         checkPatchContext(returnValue);
345         final var data = returnValue.getData().get(0).getNode();
346         assertEquals(LIST_QNAME, data.getIdentifier().getNodeType());
347         assertEquals(expectedData, data);
348     }
349
350     /**
351      * Test of Yang Patch on the case node element.
352      */
353     @Test
354     public void modulePatchTargetCaseNodeTest() throws Exception {
355         mockBodyReader("", jsonToPatchBodyReader, false);
356         final var inputStream = new ByteArrayInputStream("""
357             {
358                 "ietf-yang-patch:yang-patch": {
359                     "patch-id": "choice-patch",
360                     "comment": "comment",
361                     "edit": [
362                         {
363                             "edit-id": "edit1",
364                             "operation": "replace",
365                             "target": "/choice-model:cont-root/choice-model:cont1/choice-model:case-cont1",
366                             "value": {
367                                 "case-cont1": {
368                                     "case-leaf1": "data"
369                                 }
370                             }
371                         }
372                     ]
373                 }
374             }
375             """.getBytes(StandardCharsets.UTF_8));
376         final var expectedData = Builders.containerBuilder()
377                 .withNodeIdentifier(new NodeIdentifier(CHOICE_CONT_QNAME))
378                 .withChild(ImmutableNodes.leafNode(CASE_LEAF1_QNAME, "data"))
379                 .build();
380         final var returnValue = jsonToPatchBodyReader.readFrom(null, null, null, mediaType, null, inputStream);
381         checkPatchContext(returnValue);
382         final var data = returnValue.getData().get(0).getNode();
383         assertEquals(CHOICE_CONT_QNAME, data.getIdentifier().getNodeType());
384         assertEquals(expectedData, data);
385     }
386 }