Introduce restconf.nb.jaxrs package
[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.List;
19 import java.util.Optional;
20 import javax.ws.rs.core.MultivaluedHashMap;
21 import org.junit.jupiter.api.BeforeEach;
22 import org.junit.jupiter.api.Test;
23 import org.junit.jupiter.api.extension.ExtendWith;
24 import org.mockito.Mock;
25 import org.mockito.junit.jupiter.MockitoExtension;
26 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
27 import org.opendaylight.mdsal.dom.api.DOMDataBroker;
28 import org.opendaylight.mdsal.dom.api.DOMDataTreeReadTransaction;
29 import org.opendaylight.mdsal.dom.api.DOMRpcService;
30 import org.opendaylight.mdsal.dom.api.DOMSchemaService;
31 import org.opendaylight.mdsal.dom.spi.FixedDOMSchemaService;
32 import org.opendaylight.netconf.dom.api.NetconfDataTreeService;
33 import org.opendaylight.restconf.nb.rfc8040.legacy.NormalizedNodePayload;
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.impl.schema.Builders;
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 = Builders.containerBuilder()
50             .withNodeIdentifier(new NodeIdentifier(JUKEBOX_QNAME))
51             .withChild(CONT_PLAYER)
52             .withChild(Builders.containerBuilder().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 = Builders.containerBuilder()
56             .withNodeIdentifier(new NodeIdentifier(JUKEBOX_QNAME))
57             .withChild(CONT_PLAYER)
58             .withChild(Builders.mapBuilder().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         doReturn(true).when(asyncResponse).resume(responseCaptor.capture());
78         restconf.dataGET("example-jukebox:jukebox", uriInfo, asyncResponse);
79         final var response = responseCaptor.getValue();
80         assertEquals(200, response.getStatus());
81         assertEquals(EMPTY_JUKEBOX, ((NormalizedNodePayload) response.getEntity()).data());
82     }
83
84     @Test
85     void testReadRootData() {
86         doReturn(new MultivaluedHashMap<>()).when(uriInfo).getQueryParameters();
87         doReturn(immediateFluentFuture(Optional.of(wrapNodeByDataRootContainer(CONFIG_JUKEBOX))))
88                 .when(tx)
89                 .read(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.of());
90         doReturn(immediateFluentFuture(Optional.of(wrapNodeByDataRootContainer(OPER_JUKEBOX))))
91                 .when(tx)
92                 .read(LogicalDatastoreType.OPERATIONAL, YangInstanceIdentifier.of());
93
94         doReturn(true).when(asyncResponse).resume(responseCaptor.capture());
95         restconf.dataGET(uriInfo, asyncResponse);
96         final var response = responseCaptor.getValue();
97         assertEquals(200, response.getStatus());
98
99         final var data = assertInstanceOf(ContainerNode.class, ((NormalizedNodePayload) response.getEntity()).data());
100         final var rootNodes = data.body();
101         assertEquals(1, rootNodes.size());
102         final var allDataChildren = assertInstanceOf(ContainerNode.class, rootNodes.iterator().next()).body();
103         assertEquals(3, allDataChildren.size());
104     }
105
106     private static ContainerNode wrapNodeByDataRootContainer(final DataContainerChild data) {
107         return Builders.containerBuilder()
108             .withNodeIdentifier(NodeIdentifier.create(SchemaContext.NAME))
109             .withChild(data)
110             .build();
111     }
112
113     /**
114      * Test read data from mount point when both {@link LogicalDatastoreType#CONFIGURATION} and
115      * {@link LogicalDatastoreType#OPERATIONAL} contains the same data and some additional data to be merged.
116      */
117     @Test
118     void testReadDataMountPoint() {
119         doReturn(new MultivaluedHashMap<>()).when(uriInfo).getQueryParameters();
120         doReturn(immediateFluentFuture(Optional.of(CONFIG_JUKEBOX))).when(tx)
121                 .read(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
122         doReturn(immediateFluentFuture(Optional.of(OPER_JUKEBOX))).when(tx)
123                 .read(LogicalDatastoreType.OPERATIONAL, JUKEBOX_IID);
124
125         doReturn(Optional.of(mountPoint)).when(mountPointService)
126             .getMountPoint(any(YangInstanceIdentifier.class));
127         doReturn(Optional.of(FixedDOMSchemaService.of(JUKEBOX_SCHEMA))).when(mountPoint)
128             .getService(DOMSchemaService.class);
129         doReturn(Optional.of(dataBroker)).when(mountPoint).getService(DOMDataBroker.class);
130         doReturn(Optional.of(rpcService)).when(mountPoint).getService(DOMRpcService.class);
131         doReturn(Optional.empty()).when(mountPoint).getService(NetconfDataTreeService.class);
132
133         doReturn(true).when(asyncResponse).resume(responseCaptor.capture());
134         restconf.dataGET("example-jukebox:jukebox/yang-ext:mount/example-jukebox:jukebox", uriInfo, asyncResponse);
135         final var response = responseCaptor.getValue();
136         assertEquals(200, response.getStatus());
137
138         // response must contain all child nodes from config and operational containers merged in one container
139         final var data = assertInstanceOf(ContainerNode.class, ((NormalizedNodePayload) response.getEntity()).data());
140         assertEquals(3, data.size());
141         assertNotNull(data.childByArg(CONT_PLAYER.name()));
142         assertNotNull(data.childByArg(LIBRARY_NID));
143         assertNotNull(data.childByArg(PLAYLIST_NID));
144     }
145
146     @Test
147     void testReadDataNoData() {
148         doReturn(new MultivaluedHashMap<>()).when(uriInfo).getQueryParameters();
149         doReturn(immediateFluentFuture(Optional.empty()))
150                 .when(tx).read(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
151         doReturn(immediateFluentFuture(Optional.empty()))
152                 .when(tx).read(LogicalDatastoreType.OPERATIONAL, JUKEBOX_IID);
153
154         doReturn(true).when(asyncResponse).resume(exceptionCaptor.capture());
155         restconf.dataGET("example-jukebox:jukebox", uriInfo, asyncResponse);
156
157         final var errors = exceptionCaptor.getValue().getErrors();
158         assertEquals(1, errors.size());
159         final var error = errors.get(0);
160         assertEquals(ErrorType.PROTOCOL, error.getErrorType());
161         assertEquals(ErrorTag.DATA_MISSING, error.getErrorTag());
162         assertEquals("Request could not be completed because the relevant data model content does not exist",
163             error.getErrorMessage());
164     }
165
166     /**
167      * Read data from config datastore according to content parameter.
168      */
169     @Test
170     void testReadDataConfigTest() {
171         final MultivaluedHashMap<String, String> parameters = new MultivaluedHashMap<>();
172         parameters.put("content", List.of("config"));
173
174         doReturn(parameters).when(uriInfo).getQueryParameters();
175         doReturn(immediateFluentFuture(Optional.of(CONFIG_JUKEBOX))).when(tx)
176                 .read(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
177
178         doReturn(true).when(asyncResponse).resume(responseCaptor.capture());
179         restconf.dataGET("example-jukebox:jukebox", uriInfo, asyncResponse);
180         final var response = responseCaptor.getValue();
181         assertEquals(200, response.getStatus());
182
183         // response must contain only config data
184         final var data = assertInstanceOf(ContainerNode.class, ((NormalizedNodePayload) response.getEntity()).data());
185
186         // config data present
187         assertNotNull(data.childByArg(CONT_PLAYER.name()));
188         assertNotNull(data.childByArg(LIBRARY_NID));
189
190         // state data absent
191         assertNull(data.childByArg(PLAYLIST_NID));
192     }
193
194     /**
195      * Read data from operational datastore according to content parameter.
196      */
197     @Test
198     void testReadDataOperationalTest() {
199         final var parameters = new MultivaluedHashMap<String, String>();
200         parameters.putSingle("content", "nonconfig");
201
202         doReturn(parameters).when(uriInfo).getQueryParameters();
203         doReturn(immediateFluentFuture(Optional.of(OPER_JUKEBOX))).when(tx)
204                 .read(LogicalDatastoreType.OPERATIONAL, JUKEBOX_IID);
205
206         doReturn(true).when(asyncResponse).resume(responseCaptor.capture());
207         restconf.dataGET("example-jukebox:jukebox", uriInfo, asyncResponse);
208         final var response = responseCaptor.getValue();
209
210         assertEquals(200, response.getStatus());
211
212         // response must contain only operational data
213         final var data = assertInstanceOf(ContainerNode.class, ((NormalizedNodePayload) response.getEntity()).data());
214
215         // state data present
216         assertNotNull(data.childByArg(CONT_PLAYER.name()));
217         assertNotNull(data.childByArg(PLAYLIST_NID));
218
219         // config data absent
220         assertNull(data.childByArg(LIBRARY_NID));
221     }
222 }