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