Eliminate NormalizedNodePayload
[netconf.git] / restconf / restconf-nb / src / test / java / org / opendaylight / restconf / nb / jaxrs / RestconfDataGetTest.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.jaxrs;
9
10 import static org.junit.jupiter.api.Assertions.assertEquals;
11 import static org.junit.jupiter.api.Assertions.assertInstanceOf;
12 import static org.junit.jupiter.api.Assertions.assertNotNull;
13 import static org.junit.jupiter.api.Assertions.assertNull;
14 import static org.mockito.ArgumentMatchers.any;
15 import static org.mockito.Mockito.doReturn;
16 import static org.opendaylight.yangtools.util.concurrent.FluentFutures.immediateFluentFuture;
17
18 import java.util.Optional;
19 import javax.ws.rs.core.MultivaluedHashMap;
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.LogicalDatastoreType;
26 import org.opendaylight.mdsal.dom.api.DOMActionService;
27 import org.opendaylight.mdsal.dom.api.DOMDataBroker;
28 import org.opendaylight.mdsal.dom.api.DOMDataTreeReadTransaction;
29 import org.opendaylight.mdsal.dom.api.DOMMountPointService;
30 import org.opendaylight.mdsal.dom.api.DOMRpcService;
31 import org.opendaylight.mdsal.dom.api.DOMSchemaService;
32 import org.opendaylight.mdsal.dom.spi.FixedDOMSchemaService;
33 import org.opendaylight.netconf.dom.api.NetconfDataTreeService;
34 import org.opendaylight.yangtools.yang.common.ErrorTag;
35 import org.opendaylight.yangtools.yang.common.ErrorType;
36 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
37 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
38 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
39 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
40 import org.opendaylight.yangtools.yang.data.spi.node.ImmutableNodes;
41 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
42
43 @ExtendWith(MockitoExtension.class)
44 class RestconfDataGetTest extends AbstractRestconfTest {
45     private static final NodeIdentifier PLAYLIST_NID = new NodeIdentifier(PLAYLIST_QNAME);
46     private static final NodeIdentifier LIBRARY_NID = new NodeIdentifier(LIBRARY_QNAME);
47
48     // config contains one child the same as in operational and one additional
49     private static final ContainerNode CONFIG_JUKEBOX = ImmutableNodes.newContainerBuilder()
50             .withNodeIdentifier(new NodeIdentifier(JUKEBOX_QNAME))
51             .withChild(CONT_PLAYER)
52             .withChild(ImmutableNodes.newContainerBuilder().withNodeIdentifier(LIBRARY_NID).build())
53             .build();
54     // operational contains one child the same as in config and one additional
55     private static final ContainerNode OPER_JUKEBOX = ImmutableNodes.newContainerBuilder()
56             .withNodeIdentifier(new NodeIdentifier(JUKEBOX_QNAME))
57             .withChild(CONT_PLAYER)
58             .withChild(ImmutableNodes.newSystemMapBuilder().withNodeIdentifier(PLAYLIST_NID).build())
59             .build();
60
61     @Mock
62     private DOMDataTreeReadTransaction tx;
63
64     @BeforeEach
65     void beforeEach() {
66         doReturn(tx).when(dataBroker).newReadOnlyTransaction();
67     }
68
69     @Test
70     void testReadData() {
71         doReturn(new MultivaluedHashMap<>()).when(uriInfo).getQueryParameters();
72         doReturn(immediateFluentFuture(Optional.of(EMPTY_JUKEBOX))).when(tx)
73                 .read(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
74         doReturn(immediateFluentFuture(Optional.empty()))
75                 .when(tx).read(LogicalDatastoreType.OPERATIONAL, JUKEBOX_IID);
76
77         final var body = assertNormalizedBody(200, ar -> restconf.dataGET(JUKEBOX_API_PATH, uriInfo, ar));
78         assertEquals(EMPTY_JUKEBOX, body.data());
79         assertFormat("""
80             {
81               "example-jukebox:jukebox": {
82                 "player": {
83                   "gap": "0.2"
84                 }
85               }
86             }""", body::formatToJSON, true);
87         assertFormat("""
88             <jukebox xmlns="http://example.com/ns/example-jukebox">
89               <player>
90                 <gap>0.2</gap>
91               </player>
92             </jukebox>""", body::formatToXML, true);
93     }
94
95     @Test
96     void testReadRootData() {
97         doReturn(new MultivaluedHashMap<>()).when(uriInfo).getQueryParameters();
98         doReturn(immediateFluentFuture(Optional.of(wrapNodeByDataRootContainer(CONFIG_JUKEBOX))))
99                 .when(tx)
100                 .read(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.of());
101         doReturn(immediateFluentFuture(Optional.of(wrapNodeByDataRootContainer(OPER_JUKEBOX))))
102                 .when(tx)
103                 .read(LogicalDatastoreType.OPERATIONAL, YangInstanceIdentifier.of());
104
105         final var body = assertNormalizedBody(200, ar -> restconf.dataGET(uriInfo, ar));
106         final var data = assertInstanceOf(ContainerNode.class, body.data());
107         final var rootNodes = data.body();
108         assertEquals(1, rootNodes.size());
109         final var allDataChildren = assertInstanceOf(ContainerNode.class, rootNodes.iterator().next()).body();
110         assertEquals(3, allDataChildren.size());
111
112         assertFormat("""
113             {
114               "example-jukebox:jukebox": {
115                 "player": {
116                   "gap": "0.2"
117                 }
118               }
119             }""", body::formatToJSON, true);
120         assertFormat("""
121             <data xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
122               <jukebox xmlns="http://example.com/ns/example-jukebox">
123                 <library/>
124                 <player>
125                   <gap>0.2</gap>
126                 </player>
127               </jukebox>
128             </data>""", body::formatToXML, true);
129     }
130
131     private static ContainerNode wrapNodeByDataRootContainer(final DataContainerChild data) {
132         return ImmutableNodes.newContainerBuilder()
133             .withNodeIdentifier(NodeIdentifier.create(SchemaContext.NAME))
134             .withChild(data)
135             .build();
136     }
137
138     /**
139      * Test read data from mount point when both {@link LogicalDatastoreType#CONFIGURATION} and
140      * {@link LogicalDatastoreType#OPERATIONAL} contains the same data and some additional data to be merged.
141      */
142     @Test
143     void testReadDataMountPoint() {
144         doReturn(new MultivaluedHashMap<>()).when(uriInfo).getQueryParameters();
145         doReturn(immediateFluentFuture(Optional.of(CONFIG_JUKEBOX))).when(tx)
146                 .read(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
147         doReturn(immediateFluentFuture(Optional.of(OPER_JUKEBOX))).when(tx)
148                 .read(LogicalDatastoreType.OPERATIONAL, JUKEBOX_IID);
149
150         doReturn(Optional.of(mountPoint)).when(mountPointService)
151             .getMountPoint(any(YangInstanceIdentifier.class));
152         doReturn(Optional.of(new FixedDOMSchemaService(JUKEBOX_SCHEMA))).when(mountPoint)
153             .getService(DOMSchemaService.class);
154         doReturn(Optional.of(dataBroker)).when(mountPoint).getService(DOMDataBroker.class);
155         doReturn(Optional.of(rpcService)).when(mountPoint).getService(DOMRpcService.class);
156         doReturn(Optional.empty()).when(mountPoint).getService(DOMActionService.class);
157         doReturn(Optional.empty()).when(mountPoint).getService(DOMMountPointService.class);
158         doReturn(Optional.empty()).when(mountPoint).getService(NetconfDataTreeService.class);
159
160         // response must contain all child nodes from config and operational containers merged in one container
161         final var body = assertNormalizedBody(200, ar -> restconf.dataGET(
162             apiPath("example-jukebox:jukebox/yang-ext:mount/example-jukebox:jukebox"), uriInfo, ar));
163         final var data = assertInstanceOf(ContainerNode.class, body.data());
164         assertEquals(3, data.size());
165         assertNotNull(data.childByArg(CONT_PLAYER.name()));
166         assertNotNull(data.childByArg(LIBRARY_NID));
167         assertNotNull(data.childByArg(PLAYLIST_NID));
168
169         assertFormat("""
170             {
171               "example-jukebox:jukebox": {
172                 "player": {
173                   "gap": "0.2"
174                 }
175               }
176             }""", body::formatToJSON, true);
177         assertFormat("""
178             <jukebox xmlns="http://example.com/ns/example-jukebox">
179               <library/>
180               <player>
181                 <gap>0.2</gap>
182               </player>
183             </jukebox>""", body::formatToXML, true);
184     }
185
186     @Test
187     void testReadDataNoData() {
188         doReturn(new MultivaluedHashMap<>()).when(uriInfo).getQueryParameters();
189         doReturn(immediateFluentFuture(Optional.empty()))
190                 .when(tx).read(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
191         doReturn(immediateFluentFuture(Optional.empty()))
192                 .when(tx).read(LogicalDatastoreType.OPERATIONAL, JUKEBOX_IID);
193
194         final var error = assertError(ar -> restconf.dataGET(JUKEBOX_API_PATH, uriInfo, ar));
195         assertEquals(ErrorType.PROTOCOL, error.getErrorType());
196         assertEquals(ErrorTag.DATA_MISSING, error.getErrorTag());
197         assertEquals("Request could not be completed because the relevant data model content does not exist",
198             error.getErrorMessage());
199     }
200
201     /**
202      * Read data from config datastore according to content parameter.
203      */
204     @Test
205     void testReadDataConfigTest() {
206         final var parameters = new MultivaluedHashMap<String, String>();
207         parameters.putSingle("content", "config");
208
209         doReturn(parameters).when(uriInfo).getQueryParameters();
210         doReturn(immediateFluentFuture(Optional.of(CONFIG_JUKEBOX))).when(tx)
211                 .read(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
212
213         // response must contain only config data
214         final var body = assertNormalizedBody(200, ar -> restconf.dataGET(JUKEBOX_API_PATH, uriInfo, ar));
215         final var data = assertInstanceOf(ContainerNode.class, body.data());
216         // config data present
217         assertNotNull(data.childByArg(CONT_PLAYER.name()));
218         assertNotNull(data.childByArg(LIBRARY_NID));
219         // state data absent
220         assertNull(data.childByArg(PLAYLIST_NID));
221
222         assertFormat("""
223             {
224               "example-jukebox:jukebox": {
225                 "player": {
226                   "gap": "0.2"
227                 }
228               }
229             }""", body::formatToJSON, true);
230         assertFormat("""
231             <jukebox xmlns="http://example.com/ns/example-jukebox">
232               <library/>
233               <player>
234                 <gap>0.2</gap>
235               </player>
236             </jukebox>""", body::formatToXML, true);
237     }
238
239     /**
240      * Read data from operational datastore according to content parameter.
241      */
242     @Test
243     void testReadDataOperationalTest() {
244         final var parameters = new MultivaluedHashMap<String, String>();
245         parameters.putSingle("content", "nonconfig");
246
247         doReturn(parameters).when(uriInfo).getQueryParameters();
248         doReturn(immediateFluentFuture(Optional.of(OPER_JUKEBOX))).when(tx)
249                 .read(LogicalDatastoreType.OPERATIONAL, JUKEBOX_IID);
250
251         // response must contain only operational data
252         final var body = assertNormalizedBody(200, ar -> restconf.dataGET(JUKEBOX_API_PATH, uriInfo, ar));
253         final var data = assertInstanceOf(ContainerNode.class, body.data());
254         // state data present
255         assertNotNull(data.childByArg(CONT_PLAYER.name()));
256         assertNotNull(data.childByArg(PLAYLIST_NID));
257
258         // config data absent
259         assertNull(data.childByArg(LIBRARY_NID));
260
261         assertFormat("""
262             {
263               "example-jukebox:jukebox": {
264                 "player": {
265                   "gap": "0.2"
266                 }
267               }
268             }""", body::formatToJSON, true);
269         assertFormat("""
270             <jukebox xmlns="http://example.com/ns/example-jukebox">
271               <player>
272                 <gap>0.2</gap>
273               </player>
274             </jukebox>""", body::formatToXML, true);
275     }
276 }