2 * Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved.
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
8 package org.opendaylight.restconf.nb.rfc8040.databind;
10 import static org.junit.Assert.assertEquals;
11 import static org.junit.Assert.assertThrows;
13 import org.junit.Test;
14 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
15 import org.opendaylight.restconf.server.api.XmlPatchBody;
16 import org.opendaylight.yangtools.yang.common.ErrorTag;
17 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
18 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
19 import org.opendaylight.yangtools.yang.data.spi.node.ImmutableNodes;
21 public class XmlPatchBodyTest extends AbstractPatchBodyTest {
22 public XmlPatchBodyTest() {
23 super(XmlPatchBody::new);
27 public final void moduleDataTest() throws Exception {
28 checkPatchContext(parse(mountPrefix(), "instance-identifier-patch-module:patch-cont/my-list1=leaf1", """
29 <yang-patch xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
30 <patch-id>test-patch</patch-id>
31 <comment>this is test patch</comment>
33 <edit-id>edit1</edit-id>
34 <operation>create</operation>
35 <target>/my-list2=my-leaf20</target>
37 <my-list2 xmlns="instance:identifier:patch:module">
38 <name>my-leaf20</name>
39 <my-leaf21>I am leaf21-0</my-leaf21>
40 <my-leaf22>I am leaf22-0</my-leaf22>
45 <edit-id>edit2</edit-id>
46 <operation>create</operation>
47 <target>/my-list2=my-leaf21</target>
49 <my-list2 xmlns="instance:identifier:patch:module">
50 <name>my-leaf21</name>
51 <my-leaf21>I am leaf21-1</my-leaf21>
52 <my-leaf22>I am leaf22-1</my-leaf22>
60 * Test trying to use Patch create operation which requires value without value. Error code 400 should be returned.
63 public final void moduleDataValueMissingNegativeTest() throws Exception {
64 final var ex = assertThrows(RestconfDocumentedException.class,
65 () -> parse(mountPrefix(), "instance-identifier-patch-module:patch-cont/my-list1=leaf1", """
66 <yang-patch xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
67 <patch-id>test-patch</patch-id>
68 <comment>Test patch with missing value node for create operation</comment>
70 <edit-id>edit1</edit-id>
71 <operation>create</operation>
72 <target>/my-list2</target>
75 assertEquals(ErrorTag.MALFORMED_MESSAGE, ex.getErrors().get(0).getErrorTag());
79 * Test trying to use value with Patch delete operation which does not support value. Error code 400 should be
83 public final void moduleDataNotValueNotSupportedNegativeTest() throws Exception {
84 final var ex = assertThrows(RestconfDocumentedException.class,
85 () -> parse(mountPrefix(), "instance-identifier-patch-module:patch-cont/my-list1=leaf1", """
86 <yang-patch xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
87 <patch-id>test-patch</patch-id>
88 <comment>Test patch with not allowed value node for delete operation</comment>
90 <edit-id>edit1</edit-id>
91 <operation>delete</operation>
92 <target>/my-list2/my-leaf21</target>
94 <my-list2 xmlns="instance:identifier:patch:module">
95 <name>my-leaf20</name>
96 <my-leaf21>I am leaf21-0</my-leaf21>
97 <my-leaf22>I am leaf22-0</my-leaf22>
102 assertEquals(ErrorTag.MALFORMED_MESSAGE, ex.getErrors().get(0).getErrorTag());
106 * Test of YANG Patch with absolute target path.
109 public final void moduleDataAbsoluteTargetPathTest() throws Exception {
110 checkPatchContext(parse(mountPrefix(), "", """
111 <yang-patch xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
112 <patch-id>test-patch</patch-id>
113 <comment>Test patch with absolute target path</comment>
115 <edit-id>edit1</edit-id>
116 <operation>create</operation>
117 <target>/instance-identifier-patch-module:patch-cont/my-list1=leaf1/my-list2=my-leaf20</target>
119 <my-list2 xmlns="instance:identifier:patch:module">
120 <name>my-leaf20</name>
121 <my-leaf21>I am leaf21-0</my-leaf21>
122 <my-leaf22>I am leaf22-0</my-leaf22>
127 <edit-id>edit2</edit-id>
128 <operation>create</operation>
129 <target>/instance-identifier-patch-module:patch-cont/my-list1=leaf1/my-list2=my-leaf21</target>
131 <my-list2 xmlns="instance:identifier:patch:module">
132 <name>my-leaf21</name>
133 <my-leaf21>I am leaf21-1</my-leaf21>
134 <my-leaf22>I am leaf22-1</my-leaf22>
142 * Test using Patch when target is completely specified in request URI and thus target leaf contains only '/' sign.
145 public final void modulePatchCompleteTargetInURITest() throws Exception {
146 checkPatchContext(parse(mountPrefix(), "instance-identifier-patch-module:patch-cont", """
147 <yang-patch xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
148 <patch-id>test-patch</patch-id>
149 <comment>Test to create and replace data in container directly using / sign as a target</comment>
151 <edit-id>edit1</edit-id>
152 <operation>create</operation>
155 <patch-cont xmlns="instance:identifier:patch:module">
157 <name>my-list1 - A</name>
158 <my-leaf11>I am leaf11-0</my-leaf11>
159 <my-leaf12>I am leaf12-1</my-leaf12>
162 <name>my-list1 - B</name>
163 <my-leaf11>I am leaf11-0</my-leaf11>
164 <my-leaf12>I am leaf12-1</my-leaf12>
170 <edit-id>edit2</edit-id>
171 <operation>replace</operation>
174 <patch-cont xmlns="instance:identifier:patch:module">
176 <name>my-list1 - Replacing</name>
177 <my-leaf11>I am leaf11-0</my-leaf11>
178 <my-leaf12>I am leaf12-1</my-leaf12>
187 * Test of Yang Patch merge operation on list. Test consists of two edit operations - replace and merge.
190 public final void moduleDataMergeOperationOnListTest() throws Exception {
191 checkPatchContext(parse(mountPrefix(), "instance-identifier-patch-module:patch-cont/my-list1=leaf1", """
192 <yang-patch xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
193 <patch-id>Test merge operation</patch-id>
194 <comment>This is test patch for merge operation on list</comment>
196 <edit-id>edit1</edit-id>
197 <operation>replace</operation>
198 <target>/my-list2=my-leaf20</target>
200 <my-list2 xmlns="instance:identifier:patch:module">
201 <name>my-leaf20</name>
202 <my-leaf21>I am leaf21-0</my-leaf21>
203 <my-leaf22>I am leaf22-0</my-leaf22>
208 <edit-id>edit2</edit-id>
209 <operation>merge</operation>
210 <target>/my-list2=my-leaf21</target>
212 <my-list2 xmlns="instance:identifier:patch:module">
213 <name>my-leaf21</name>
214 <my-leaf21>I am leaf21-1</my-leaf21>
215 <my-leaf22>I am leaf22-1</my-leaf22>
223 * Test of Yang Patch merge operation on container. Test consists of two edit operations - create and merge.
226 public final void moduleDataMergeOperationOnContainerTest() throws Exception {
227 checkPatchContext(parse(mountPrefix(), "instance-identifier-patch-module:patch-cont", """
228 <yang-patch xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
229 <patch-id>Test merge operation</patch-id>
230 <comment>This is test patch for merge operation on container</comment>
232 <edit-id>edit1</edit-id>
233 <operation>create</operation>
236 <patch-cont xmlns="instance:identifier:patch:module">
238 <name>my-list1 - A</name>
239 <my-leaf11>I am leaf11-0</my-leaf11>
240 <my-leaf12>I am leaf12-1</my-leaf12>
243 <name>my-list1 - B</name>
244 <my-leaf11>I am leaf11-0</my-leaf11>
245 <my-leaf12>I am leaf12-1</my-leaf12>
251 <edit-id>edit2</edit-id>
252 <operation>merge</operation>
255 <patch-cont xmlns="instance:identifier:patch:module">
257 <name>my-list1 - Merged</name>
258 <my-leaf11>I am leaf11-0</my-leaf11>
259 <my-leaf12>I am leaf12-1</my-leaf12>
268 * Test of Yang Patch on the top-level container with empty URI for data root.
271 public final void modulePatchTargetTopLevelContainerWithEmptyURITest() throws Exception {
272 checkPatchContext(parse(mountPrefix(), "", """
273 <yang-patch xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
274 <patch-id>test-patch</patch-id>
275 <comment>Test patch applied to the top-level container with empty URI</comment>
277 <edit-id>edit1</edit-id>
278 <operation>replace</operation>
279 <target>/instance-identifier-patch-module:patch-cont</target>
281 <patch-cont xmlns="instance:identifier:patch:module">
283 <name>my-leaf10</name>
292 * Test of YANG Patch on the top-level container with the full path in the URI and "/" in 'target'.
295 public final void modulePatchTargetTopLevelContainerWithFullPathURITest() throws Exception {
296 final var returnValue = parse(mountPrefix(), "instance-identifier-patch-module:patch-cont", """
297 <yang-patch xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
298 <patch-id>test-patch</patch-id>
299 <comment>Test patch applied to the top-level container with '/' in target</comment>
301 <edit-id>edit1</edit-id>
302 <operation>replace</operation>
305 <patch-cont xmlns="instance:identifier:patch:module">
307 <name>my-leaf-set</name>
308 <my-leaf11>leaf-a</my-leaf11>
309 <my-leaf12>leaf-b</my-leaf12>
315 checkPatchContext(returnValue);
316 assertEquals(ImmutableNodes.newContainerBuilder()
317 .withNodeIdentifier(new NodeIdentifier(PATCH_CONT_QNAME))
318 .withChild(ImmutableNodes.newSystemMapBuilder()
319 .withNodeIdentifier(new NodeIdentifier(MY_LIST1_QNAME))
320 .withChild(ImmutableNodes.newMapEntryBuilder()
321 .withNodeIdentifier(NodeIdentifierWithPredicates.of(MY_LIST1_QNAME, LEAF_NAME_QNAME, "my-leaf-set"))
322 .withChild(ImmutableNodes.leafNode(LEAF_NAME_QNAME, "my-leaf-set"))
323 .withChild(ImmutableNodes.leafNode(MY_LEAF11_QNAME, "leaf-a"))
324 .withChild(ImmutableNodes.leafNode(MY_LEAF12_QNAME, "leaf-b"))
327 .build(), returnValue.entities().get(0).getNode());
331 * Test of YANG Patch on the second-level list with the full path in the URI and "/" in 'target'.
334 public final void modulePatchTargetSecondLevelListWithFullPathURITest() throws Exception {
335 final var returnValue = parse(mountPrefix(), "instance-identifier-patch-module:patch-cont/my-list1=my-leaf-set",
337 <yang-patch xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
338 <patch-id>test-patch</patch-id>
339 <comment>Test patch applied to the second-level list with '/' in target</comment>
341 <edit-id>edit1</edit-id>
342 <operation>replace</operation>
345 <my-list1 xmlns="instance:identifier:patch:module">
346 <name>my-leaf-set</name>
347 <my-leaf11>leaf-a</my-leaf11>
348 <my-leaf12>leaf-b</my-leaf12>
353 checkPatchContext(returnValue);
354 assertEquals(ImmutableNodes.newSystemMapBuilder()
355 .withNodeIdentifier(new NodeIdentifier(MY_LIST1_QNAME))
356 .withChild(ImmutableNodes.newMapEntryBuilder()
357 .withNodeIdentifier(NodeIdentifierWithPredicates.of(MY_LIST1_QNAME, LEAF_NAME_QNAME, "my-leaf-set"))
358 .withChild(ImmutableNodes.leafNode(LEAF_NAME_QNAME, "my-leaf-set"))
359 .withChild(ImmutableNodes.leafNode(MY_LEAF11_QNAME, "leaf-a"))
360 .withChild(ImmutableNodes.leafNode(MY_LEAF12_QNAME, "leaf-b"))
362 .build(), returnValue.entities().get(0).getNode());
366 * Test of Yang Patch on the top augmented element.
369 public final void moduleTargetTopLevelAugmentedContainerTest() throws Exception {
370 final var returnValue = parse(mountPrefix(), "", """
371 <yang-patch xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
372 <patch-id>test-patch</patch-id>
373 <comment>This test patch for augmented element</comment>
375 <edit-id>edit1</edit-id>
376 <operation>replace</operation>
377 <target>/test-m:container-root/test-m:container-lvl1/test-m-aug:container-aug</target>
379 <container-aug xmlns="test-ns-aug">
380 <leaf-aug>data</leaf-aug>
385 checkPatchContext(returnValue);
386 assertEquals(ImmutableNodes.newContainerBuilder()
387 .withNodeIdentifier(new NodeIdentifier(CONT_AUG_QNAME))
388 .withChild(ImmutableNodes.leafNode(LEAF_AUG_QNAME, "data"))
389 .build(), returnValue.entities().get(0).getNode());
393 * Test of YANG Patch on the top system map node element.
396 public final void moduleTargetMapNodeTest() throws Exception {
397 final var returnValue = parse(mountPrefix(), "", """
398 <yang-patch xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
399 <patch-id>map-patch</patch-id>
400 <comment>YANG patch comment</comment>
402 <edit-id>edit1</edit-id>
403 <operation>replace</operation>
404 <target>/map-model:cont-root/map-model:cont1/map-model:my-map=key</target>
406 <my-map xmlns="map:ns">
407 <key-leaf>key</key-leaf>
408 <data-leaf>data</data-leaf>
413 checkPatchContext(returnValue);
414 assertEquals(ImmutableNodes.newSystemMapBuilder()
415 .withNodeIdentifier(new NodeIdentifier(MAP_CONT_QNAME))
416 .withChild(ImmutableNodes.newMapEntryBuilder()
417 .withNodeIdentifier(NodeIdentifierWithPredicates.of(MAP_CONT_QNAME, KEY_LEAF_QNAME, "key"))
418 .withChild(ImmutableNodes.leafNode(KEY_LEAF_QNAME, "key"))
419 .withChild(ImmutableNodes.leafNode(DATA_LEAF_QNAME, "data"))
421 .build(), returnValue.entities().get(0).getNode());
425 * Test of YANG Patch on the leaf set node element.
428 public final void modulePatchTargetLeafSetNodeTest() throws Exception {
429 final var returnValue = parse(mountPrefix(), "", """
430 <yang-patch xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
431 <patch-id>set-patch</patch-id>
432 <comment>YANG patch comment</comment>
434 <edit-id>edit1</edit-id>
435 <operation>replace</operation>
436 <target>/set-model:cont-root/set-model:cont1/set-model:my-set="data1"</target>
438 <my-set xmlns="set:ns">data1</my-set>
442 checkPatchContext(returnValue);
443 assertEquals(ImmutableNodes.newSystemLeafSetBuilder()
444 .withNodeIdentifier(new NodeIdentifier(LEAF_SET_QNAME))
445 .withChildValue("data1")
446 .build(), returnValue.entities().get(0).getNode());
450 * Test of Yang Patch on the top unkeyed list element.
453 public final void moduleTargetUnkeyedListNodeTest() throws Exception {
454 final var returnValue = parse(mountPrefix(), "", """
455 <yang-patch xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
456 <patch-id>list-patch</patch-id>
457 <comment>YANG patch comment</comment>
459 <edit-id>edit1</edit-id>
460 <operation>replace</operation>
461 <target>/list-model:cont-root/list-model:cont1/list-model:unkeyed-list</target>
463 <unkeyed-list xmlns="list:ns">
470 checkPatchContext(returnValue);
471 assertEquals(ImmutableNodes.newUnkeyedListBuilder()
472 .withNodeIdentifier(new NodeIdentifier(LIST_QNAME))
473 .withChild(ImmutableNodes.newUnkeyedListEntryBuilder()
474 .withNodeIdentifier(new NodeIdentifier(LIST_QNAME))
475 .withChild(ImmutableNodes.leafNode(LIST_LEAF1_QNAME, "data1"))
476 .withChild(ImmutableNodes.leafNode(LIST_LEAF2_QNAME, "data2"))
478 .build(), returnValue.entities().get(0).getNode());
482 * Test of Yang Patch on the top case node element.
485 public final void moduleTargetCaseNodeTest() throws Exception {
486 final var returnValue = parse(mountPrefix(), "", """
487 <yang-patch xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
488 <patch-id>choice-patch</patch-id>
489 <comment>YANG patch comment</comment>
491 <edit-id>edit1</edit-id>
492 <operation>replace</operation>
493 <target>/choice-model:cont-root/choice-model:cont1/choice-model:case-cont1</target>
495 <case-cont1 xmlns="choice:ns">
496 <case-leaf1>data</case-leaf1>
501 checkPatchContext(returnValue);
502 assertEquals(ImmutableNodes.newContainerBuilder()
503 .withNodeIdentifier(new NodeIdentifier(CHOICE_CONT_QNAME))
504 .withChild(ImmutableNodes.leafNode(CASE_LEAF1_QNAME, "data"))
505 .build(), returnValue.entities().get(0).getNode());
509 * Test reading simple leaf value.
512 public final void modulePatchSimpleLeafValueTest() throws Exception {
513 final var returnValue = parse(mountPrefix(), "instance-identifier-patch-module:patch-cont/my-list1=leaf1", """
514 <yang-patch xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
515 <patch-id>test-patch</patch-id>
516 <comment>this is test patch</comment>
518 <edit-id>edit1</edit-id>
519 <operation>replace</operation>
520 <target>/my-list2=my-leaf20/name</target>
522 <name xmlns="instance:identifier:patch:module">my-leaf20</name>
526 checkPatchContext(returnValue);
527 assertEquals(ImmutableNodes.leafNode(LEAF_NAME_QNAME, "my-leaf20"), returnValue.entities().get(0).getNode());