664ae5e79a1c3de20d5aa7bbdcbbde898b864796
[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     }
172
173     /**
174      * Test of Yang Patch on the top-level container with empty URI for data root.
175      */
176     @Test
177     public void modulePatchTargetTopLevelContainerWithEmptyURITest() throws Exception {
178         final String uri = "";
179         mockBodyReader(uri, jsonToPatchBodyReader, false);
180
181         final InputStream inputStream = JsonBodyReaderTest.class.getResourceAsStream(
182                 "/instanceidentifier/json/jsonPATCHTargetTopLevelContainerWithEmptyURI.json");
183
184         final PatchContext returnValue = jsonToPatchBodyReader.readFrom(null, null, null, mediaType, null, inputStream);
185         checkPatchContext(returnValue);
186     }
187
188     /**
189      * Test of Yang Patch on the system map node element.
190      */
191     @Test
192     public void modulePatchTargetMapNodeTest() throws Exception {
193         mockBodyReader("", jsonToPatchBodyReader, false);
194         final var inputStream = new ByteArrayInputStream("""
195             {
196                 "ietf-yang-patch:yang-patch": {
197                     "patch-id": "map-patch",
198                     "comment": "comment",
199                     "edit": [
200                         {
201                             "edit-id": "edit1",
202                             "operation": "replace",
203                             "target": "/map-model:cont-root/map-model:cont1/map-model:my-map=key",
204                             "value": {
205                                 "my-map": {
206                                     "key-leaf": "key",
207                                     "data-leaf": "data"
208                                 }
209                             }
210                         }
211                     ]
212                 }
213             }
214             """.getBytes(StandardCharsets.UTF_8));
215         final var expectedData = Builders.mapBuilder()
216                 .withNodeIdentifier(new NodeIdentifier(MAP_CONT_QNAME))
217                 .withChild(Builders.mapEntryBuilder()
218                         .withNodeIdentifier(NodeIdentifierWithPredicates.of(MAP_CONT_QNAME, KEY_LEAF_QNAME, "key"))
219                         .withChild(ImmutableNodes.leafNode(KEY_LEAF_QNAME, "key"))
220                         .withChild(ImmutableNodes.leafNode(DATA_LEAF_QNAME, "data"))
221                         .build())
222                 .build();
223         final var returnValue = jsonToPatchBodyReader.readFrom(null, null, null, mediaType, null, inputStream);
224         checkPatchContext(returnValue);
225         final var data = returnValue.getData().get(0).getNode();
226         assertEquals(MAP_CONT_QNAME, data.getIdentifier().getNodeType());
227         assertEquals(expectedData, data);
228     }
229
230     /**
231      * Test of Yang Patch on the leaf set node element.
232      */
233     @Test
234     public void modulePatchTargetLeafSetNodeTest() throws Exception {
235         mockBodyReader("", jsonToPatchBodyReader, false);
236         final var inputStream = new ByteArrayInputStream("""
237             {
238                 "ietf-yang-patch:yang-patch": {
239                     "patch-id": "set-patch",
240                     "comment": "comment",
241                     "edit": [
242                         {
243                             "edit-id": "edit1",
244                             "operation": "replace",
245                             "target": "/set-model:cont-root/set-model:cont1/set-model:my-set=data1",
246                             "value": {
247                                 "my-set": [ "data1" ]
248                             }
249                         }
250                     ]
251                 }
252             }
253             """.getBytes(StandardCharsets.UTF_8));
254         final var expectedData = Builders.leafSetBuilder()
255                 .withNodeIdentifier(new NodeIdentifier(LEAF_SET_QNAME))
256                 .withChild(Builders.leafSetEntryBuilder()
257                         .withNodeIdentifier(new NodeWithValue(LEAF_SET_QNAME, "data1"))
258                         .withValue("data1")
259                         .build())
260                 .build();
261         final var returnValue = jsonToPatchBodyReader.readFrom(null, null, null, mediaType, null, inputStream);
262         checkPatchContext(returnValue);
263         final var data = returnValue.getData().get(0).getNode();
264         assertEquals(LEAF_SET_QNAME, data.getIdentifier().getNodeType());
265         assertEquals(expectedData, data);
266     }
267
268     /**
269      * Test of Yang Patch on the unkeyed list node element.
270      */
271     @Test
272     public void modulePatchTargetUnkeyedListNodeTest() throws Exception {
273         mockBodyReader("", jsonToPatchBodyReader, false);
274         final var inputStream = new ByteArrayInputStream("""
275             {
276                 "ietf-yang-patch:yang-patch": {
277                     "patch-id": "list-patch",
278                     "comment": "comment",
279                     "edit": [
280                         {
281                             "edit-id": "edit1",
282                             "operation": "replace",
283                             "target": "/list-model:cont-root/list-model:cont1/list-model:unkeyed-list",
284                             "value": {
285                                 "unkeyed-list": {
286                                     "leaf1": "data1",
287                                     "leaf2": "data2"
288                                 }
289                             }
290                         }
291                     ]
292                 }
293             }
294             """.getBytes(StandardCharsets.UTF_8));
295         final var expectedData = Builders.unkeyedListBuilder()
296                 .withNodeIdentifier(new NodeIdentifier(LIST_QNAME))
297                 .withChild(Builders.unkeyedListEntryBuilder()
298                         .withNodeIdentifier(new NodeIdentifier(LIST_QNAME))
299                         .withChild(ImmutableNodes.leafNode(LIST_LEAF1_QNAME, "data1"))
300                         .withChild(ImmutableNodes.leafNode(LIST_LEAF2_QNAME, "data2"))
301                         .build())
302                 .build();
303         final var returnValue = jsonToPatchBodyReader.readFrom(null, null, null, mediaType, null, inputStream);
304         checkPatchContext(returnValue);
305         final var data = returnValue.getData().get(0).getNode();
306         assertEquals(LIST_QNAME, data.getIdentifier().getNodeType());
307         assertEquals(expectedData, data);
308     }
309
310     /**
311      * Test of Yang Patch on the case node element.
312      */
313     @Test
314     public void modulePatchTargetCaseNodeTest() throws Exception {
315         mockBodyReader("", jsonToPatchBodyReader, false);
316         final var inputStream = new ByteArrayInputStream("""
317             {
318                 "ietf-yang-patch:yang-patch": {
319                     "patch-id": "choice-patch",
320                     "comment": "comment",
321                     "edit": [
322                         {
323                             "edit-id": "edit1",
324                             "operation": "replace",
325                             "target": "/choice-model:cont-root/choice-model:cont1/choice-model:case-cont1",
326                             "value": {
327                                 "case-cont1": {
328                                     "case-leaf1": "data"
329                                 }
330                             }
331                         }
332                     ]
333                 }
334             }
335             """.getBytes(StandardCharsets.UTF_8));
336         final var expectedData = Builders.containerBuilder()
337                 .withNodeIdentifier(new NodeIdentifier(CHOICE_CONT_QNAME))
338                 .withChild(ImmutableNodes.leafNode(CASE_LEAF1_QNAME, "data"))
339                 .build();
340         final var returnValue = jsonToPatchBodyReader.readFrom(null, null, null, mediaType, null, inputStream);
341         checkPatchContext(returnValue);
342         final var data = returnValue.getData().get(0).getNode();
343         assertEquals(CHOICE_CONT_QNAME, data.getIdentifier().getNodeType());
344         assertEquals(expectedData, data);
345     }
346 }