RestconfServer is strictly asynchronous
[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.DOMDataBroker;
27 import org.opendaylight.mdsal.dom.api.DOMDataTreeReadTransaction;
28 import org.opendaylight.mdsal.dom.api.DOMRpcService;
29 import org.opendaylight.mdsal.dom.api.DOMSchemaService;
30 import org.opendaylight.mdsal.dom.spi.FixedDOMSchemaService;
31 import org.opendaylight.netconf.dom.api.NetconfDataTreeService;
32 import org.opendaylight.yangtools.yang.common.ErrorTag;
33 import org.opendaylight.yangtools.yang.common.ErrorType;
34 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
35 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
36 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
37 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
38 import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
39 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
40
41 @ExtendWith(MockitoExtension.class)
42 class RestconfDataGetTest extends AbstractRestconfTest {
43     private static final NodeIdentifier PLAYLIST_NID = new NodeIdentifier(PLAYLIST_QNAME);
44     private static final NodeIdentifier LIBRARY_NID = new NodeIdentifier(LIBRARY_QNAME);
45
46     // config contains one child the same as in operational and one additional
47     private static final ContainerNode CONFIG_JUKEBOX = Builders.containerBuilder()
48             .withNodeIdentifier(new NodeIdentifier(JUKEBOX_QNAME))
49             .withChild(CONT_PLAYER)
50             .withChild(Builders.containerBuilder().withNodeIdentifier(LIBRARY_NID).build())
51             .build();
52     // operational contains one child the same as in config and one additional
53     private static final ContainerNode OPER_JUKEBOX = Builders.containerBuilder()
54             .withNodeIdentifier(new NodeIdentifier(JUKEBOX_QNAME))
55             .withChild(CONT_PLAYER)
56             .withChild(Builders.mapBuilder().withNodeIdentifier(PLAYLIST_NID).build())
57             .build();
58
59     @Mock
60     private DOMDataTreeReadTransaction tx;
61
62     @BeforeEach
63     void beforeEach() {
64         doReturn(tx).when(dataBroker).newReadOnlyTransaction();
65     }
66
67     @Test
68     void testReadData() {
69         doReturn(new MultivaluedHashMap<>()).when(uriInfo).getQueryParameters();
70         doReturn(immediateFluentFuture(Optional.of(EMPTY_JUKEBOX))).when(tx)
71                 .read(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
72         doReturn(immediateFluentFuture(Optional.empty()))
73                 .when(tx).read(LogicalDatastoreType.OPERATIONAL, JUKEBOX_IID);
74
75         assertEquals(EMPTY_JUKEBOX,
76             assertNormalizedNode(200, ar -> restconf.dataGET("example-jukebox:jukebox", uriInfo, ar)));
77     }
78
79     @Test
80     void testReadRootData() {
81         doReturn(new MultivaluedHashMap<>()).when(uriInfo).getQueryParameters();
82         doReturn(immediateFluentFuture(Optional.of(wrapNodeByDataRootContainer(CONFIG_JUKEBOX))))
83                 .when(tx)
84                 .read(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.of());
85         doReturn(immediateFluentFuture(Optional.of(wrapNodeByDataRootContainer(OPER_JUKEBOX))))
86                 .when(tx)
87                 .read(LogicalDatastoreType.OPERATIONAL, YangInstanceIdentifier.of());
88
89         final var data = assertInstanceOf(ContainerNode.class,
90             assertNormalizedNode(200, ar -> restconf.dataGET(uriInfo, ar)));
91         final var rootNodes = data.body();
92         assertEquals(1, rootNodes.size());
93         final var allDataChildren = assertInstanceOf(ContainerNode.class, rootNodes.iterator().next()).body();
94         assertEquals(3, allDataChildren.size());
95     }
96
97     private static ContainerNode wrapNodeByDataRootContainer(final DataContainerChild data) {
98         return Builders.containerBuilder()
99             .withNodeIdentifier(NodeIdentifier.create(SchemaContext.NAME))
100             .withChild(data)
101             .build();
102     }
103
104     /**
105      * Test read data from mount point when both {@link LogicalDatastoreType#CONFIGURATION} and
106      * {@link LogicalDatastoreType#OPERATIONAL} contains the same data and some additional data to be merged.
107      */
108     @Test
109     void testReadDataMountPoint() {
110         doReturn(new MultivaluedHashMap<>()).when(uriInfo).getQueryParameters();
111         doReturn(immediateFluentFuture(Optional.of(CONFIG_JUKEBOX))).when(tx)
112                 .read(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
113         doReturn(immediateFluentFuture(Optional.of(OPER_JUKEBOX))).when(tx)
114                 .read(LogicalDatastoreType.OPERATIONAL, JUKEBOX_IID);
115
116         doReturn(Optional.of(mountPoint)).when(mountPointService)
117             .getMountPoint(any(YangInstanceIdentifier.class));
118         doReturn(Optional.of(FixedDOMSchemaService.of(JUKEBOX_SCHEMA))).when(mountPoint)
119             .getService(DOMSchemaService.class);
120         doReturn(Optional.of(dataBroker)).when(mountPoint).getService(DOMDataBroker.class);
121         doReturn(Optional.of(rpcService)).when(mountPoint).getService(DOMRpcService.class);
122         doReturn(Optional.empty()).when(mountPoint).getService(NetconfDataTreeService.class);
123
124         // response must contain all child nodes from config and operational containers merged in one container
125         final var data = assertInstanceOf(ContainerNode.class,
126             assertNormalizedNode(200, ar ->
127                 restconf.dataGET("example-jukebox:jukebox/yang-ext:mount/example-jukebox:jukebox", uriInfo, ar)));
128         assertEquals(3, data.size());
129         assertNotNull(data.childByArg(CONT_PLAYER.name()));
130         assertNotNull(data.childByArg(LIBRARY_NID));
131         assertNotNull(data.childByArg(PLAYLIST_NID));
132     }
133
134     @Test
135     void testReadDataNoData() {
136         doReturn(new MultivaluedHashMap<>()).when(uriInfo).getQueryParameters();
137         doReturn(immediateFluentFuture(Optional.empty()))
138                 .when(tx).read(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
139         doReturn(immediateFluentFuture(Optional.empty()))
140                 .when(tx).read(LogicalDatastoreType.OPERATIONAL, JUKEBOX_IID);
141
142         final var error = assertError(ar -> restconf.dataGET("example-jukebox:jukebox", uriInfo, ar));
143         assertEquals(ErrorType.PROTOCOL, error.getErrorType());
144         assertEquals(ErrorTag.DATA_MISSING, error.getErrorTag());
145         assertEquals("Request could not be completed because the relevant data model content does not exist",
146             error.getErrorMessage());
147     }
148
149     /**
150      * Read data from config datastore according to content parameter.
151      */
152     @Test
153     void testReadDataConfigTest() {
154         final var parameters = new MultivaluedHashMap<String, String>();
155         parameters.putSingle("content", "config");
156
157         doReturn(parameters).when(uriInfo).getQueryParameters();
158         doReturn(immediateFluentFuture(Optional.of(CONFIG_JUKEBOX))).when(tx)
159                 .read(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
160
161         // response must contain only config data
162         final var data = assertInstanceOf(ContainerNode.class,
163             assertNormalizedNode(200, ar -> restconf.dataGET("example-jukebox:jukebox", uriInfo, ar)));
164         // config data present
165         assertNotNull(data.childByArg(CONT_PLAYER.name()));
166         assertNotNull(data.childByArg(LIBRARY_NID));
167         // state data absent
168         assertNull(data.childByArg(PLAYLIST_NID));
169     }
170
171     /**
172      * Read data from operational datastore according to content parameter.
173      */
174     @Test
175     void testReadDataOperationalTest() {
176         final var parameters = new MultivaluedHashMap<String, String>();
177         parameters.putSingle("content", "nonconfig");
178
179         doReturn(parameters).when(uriInfo).getQueryParameters();
180         doReturn(immediateFluentFuture(Optional.of(OPER_JUKEBOX))).when(tx)
181                 .read(LogicalDatastoreType.OPERATIONAL, JUKEBOX_IID);
182
183         // response must contain only operational data
184         final var data = assertInstanceOf(ContainerNode.class,
185             assertNormalizedNode(200, ar -> restconf.dataGET("example-jukebox:jukebox", uriInfo, ar)));
186         // state data present
187         assertNotNull(data.childByArg(CONT_PLAYER.name()));
188         assertNotNull(data.childByArg(PLAYLIST_NID));
189
190         // config data absent
191         assertNull(data.childByArg(LIBRARY_NID));
192     }
193 }