Introduce RestconfServer methods for YANG Patch
[netconf.git] / restconf / restconf-nb / src / test / java / org / opendaylight / restconf / nb / rfc8040 / rests / services / impl / RestconfDataPatchTest.java
1 /*
2  * Copyright (c) 2023 PANTHEON.tech, s.r.o. 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.rests.services.impl;
9
10 import static org.junit.jupiter.api.Assertions.assertEquals;
11 import static org.junit.jupiter.api.Assertions.assertFalse;
12 import static org.junit.jupiter.api.Assertions.assertInstanceOf;
13 import static org.junit.jupiter.api.Assertions.assertNull;
14 import static org.junit.jupiter.api.Assertions.assertTrue;
15 import static org.mockito.Mockito.doNothing;
16 import static org.mockito.Mockito.doReturn;
17 import static org.opendaylight.yangtools.util.concurrent.FluentFutures.immediateFalseFluentFuture;
18 import static org.opendaylight.yangtools.util.concurrent.FluentFutures.immediateTrueFluentFuture;
19
20 import org.junit.jupiter.api.BeforeEach;
21 import org.junit.jupiter.api.Test;
22 import org.junit.jupiter.api.extension.ExtendWith;
23 import org.mockito.Mock;
24 import org.mockito.junit.jupiter.MockitoExtension;
25 import org.opendaylight.mdsal.common.api.CommitInfo;
26 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
27 import org.opendaylight.mdsal.dom.api.DOMDataTreeReadWriteTransaction;
28 import org.opendaylight.restconf.common.patch.PatchStatusContext;
29 import org.opendaylight.yangtools.yang.common.ErrorTag;
30 import org.opendaylight.yangtools.yang.common.ErrorType;
31
32 @ExtendWith(MockitoExtension.class)
33 class RestconfDataPatchTest extends AbstractRestconfTest {
34     @Mock
35     private DOMDataTreeReadWriteTransaction tx;
36
37     @BeforeEach
38     void beforeEach() {
39         doReturn(tx).when(dataBroker).newReadWriteTransaction();
40     }
41
42     @Test
43     void testPatchData() {
44         doNothing().when(tx).delete(LogicalDatastoreType.CONFIGURATION, GAP_IID);
45         doReturn(immediateFalseFluentFuture()).when(tx).exists(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
46         doReturn(immediateTrueFluentFuture()).when(tx).exists(LogicalDatastoreType.CONFIGURATION, GAP_IID);
47         doReturn(CommitInfo.emptyFluentFuture()).when(tx).commit();
48         doReturn(true).when(asyncResponse).resume(responseCaptor.capture());
49         restconf.dataYangJsonPATCH(stringInputStream("""
50             {
51               "ietf-yang-patch:yang-patch" : {
52                 "patch-id" : "test patch id",
53                 "edit" : [
54                   {
55                     "edit-id" : "create data",
56                     "operation" : "create",
57                     "target" : "/example-jukebox:jukebox",
58                     "value" : {
59                       "jukebox" : {
60                         "player" : {
61                           "gap" : "0.2"
62                         }
63                       }
64                     }
65                   },
66                   {
67                     "edit-id" : "replace data",
68                     "operation" : "replace",
69                     "target" : "/example-jukebox:jukebox",
70                     "value" : {
71                       "jukebox" : {
72                         "player" : {
73                           "gap" : "0.3"
74                         }
75                       }
76                     }
77                   },
78                   {
79                     "edit-id" : "delete data",
80                     "operation" : "delete",
81                     "target" : "/example-jukebox:jukebox/player/gap"
82                   }
83                 ]
84               }"""), asyncResponse);
85         final var response = responseCaptor.getValue();
86         assertEquals(200, response.getStatus());
87         final var status = assertInstanceOf(PatchStatusContext.class, response.getEntity());
88         assertTrue(status.ok());
89         final var edits = status.editCollection();
90         assertEquals(3, edits.size());
91         assertTrue(edits.get(0).isOk());
92         assertTrue(edits.get(1).isOk());
93         assertTrue(edits.get(2).isOk());
94     }
95
96     @Test
97     void testPatchDataDeleteNotExist() {
98         doNothing().when(tx).delete(LogicalDatastoreType.CONFIGURATION, GAP_IID);
99         doReturn(immediateFalseFluentFuture()).when(tx).exists(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
100         doReturn(immediateFalseFluentFuture()).when(tx).exists(LogicalDatastoreType.CONFIGURATION, GAP_IID);
101         doReturn(true).when(tx).cancel();
102
103         doReturn(true).when(asyncResponse).resume(responseCaptor.capture());
104         restconf.dataYangJsonPATCH(stringInputStream("""
105             {
106               "ietf-yang-patch:yang-patch" : {
107                 "patch-id" : "test patch id",
108                 "edit" : [
109                   {
110                     "edit-id" : "create data",
111                     "operation" : "create",
112                     "target" : "/example-jukebox:jukebox",
113                     "value" : {
114                       "jukebox" : {
115                         "player" : {
116                           "gap" : "0.2"
117                         }
118                       }
119                     }
120                   },
121                   {
122                     "edit-id" : "remove data",
123                     "operation" : "remove",
124                     "target" : "/example-jukebox:jukebox/player/gap"
125                   },
126                   {
127                     "edit-id" : "delete data",
128                     "operation" : "delete",
129                     "target" : "/example-jukebox:jukebox/player/gap"
130                   }
131                 ]
132               }
133             }"""), asyncResponse);
134         final var response = responseCaptor.getValue();
135         assertEquals(409, response.getStatus());
136         final var status = assertInstanceOf(PatchStatusContext.class, response.getEntity());
137         assertFalse(status.ok());
138         final var edits = status.editCollection();
139         assertEquals(3, edits.size());
140         assertTrue(edits.get(0).isOk());
141         assertTrue(edits.get(1).isOk());
142         final var edit = edits.get(2);
143         assertFalse(edit.isOk());
144         final var errors = edit.getEditErrors();
145         assertEquals(1, errors.size());
146         final var error = errors.get(0);
147         assertEquals("Data does not exist", error.getErrorMessage());
148         assertEquals(ErrorType.PROTOCOL, error.getErrorType());
149         assertEquals(ErrorTag.DATA_MISSING, error.getErrorTag());
150         assertEquals(GAP_IID, error.getErrorPath());
151     }
152
153     @Test
154     void testPatchDataMountPoint() throws Exception {
155         doNothing().when(tx).delete(LogicalDatastoreType.CONFIGURATION, GAP_IID);
156         doReturn(immediateFalseFluentFuture()).when(tx).exists(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
157         doReturn(immediateTrueFluentFuture()).when(tx).exists(LogicalDatastoreType.CONFIGURATION, GAP_IID);
158         doReturn(CommitInfo.emptyFluentFuture()).when(tx).commit();
159
160         doReturn(true).when(asyncResponse).resume(responseCaptor.capture());
161         restconf.dataYangXmlPATCH(stringInputStream("""
162             <yang-patch xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
163               <patch-id>test patch id</patch-id>
164               <edit>
165                 <edit-id>create data</edit-id>
166                 <operation>create</operation>
167                 <target>/example-jukebox:jukebox</target>
168                 <value>
169                   <jukebox xmlns="http://example.com/ns/example-jukebox">
170                     <player>
171                       <gap>0.2</gap>
172                     </player>
173                   </jukebox>
174                 </value>
175               </edit>
176               <edit>
177                 <edit-id>replace data</edit-id>
178                 <operation>replace</operation>
179                 <target>/example-jukebox:jukebox</target>
180                 <value>
181                   <jukebox xmlns="http://example.com/ns/example-jukebox">
182                     <player>
183                       <gap>0.3</gap>
184                     </player>
185                   </jukebox>
186                 </value>
187               </edit>
188               <edit>
189                 <edit-id>delete data</edit-id>
190                 <operation>delete</operation>
191                 <target>/example-jukebox:jukebox/player/gap</target>
192               </edit>
193             </yang-patch>"""), asyncResponse);
194         final var response = responseCaptor.getValue();
195         assertEquals(200, response.getStatus());
196         final var status = assertInstanceOf(PatchStatusContext.class, response.getEntity());
197         assertTrue(status.ok());
198         assertNull(status.globalErrors());
199         final var edits = status.editCollection();
200         assertEquals(3, edits.size());
201         assertTrue(edits.get(0).isOk());
202         assertTrue(edits.get(1).isOk());
203         assertTrue(edits.get(2).isOk());
204     }
205 }