2 * Copyright (c) 2023 PANTHEON.tech, s.r.o. 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.rests.services.impl;
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;
18 import java.util.List;
19 import java.util.Optional;
20 import javax.ws.rs.container.AsyncResponse;
21 import javax.ws.rs.core.MultivaluedHashMap;
22 import javax.ws.rs.core.Response;
23 import javax.ws.rs.core.UriInfo;
24 import org.junit.jupiter.api.BeforeEach;
25 import org.junit.jupiter.api.Test;
26 import org.junit.jupiter.api.extension.ExtendWith;
27 import org.mockito.ArgumentCaptor;
28 import org.mockito.Captor;
29 import org.mockito.Mock;
30 import org.mockito.junit.jupiter.MockitoExtension;
31 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
32 import org.opendaylight.mdsal.dom.api.DOMActionService;
33 import org.opendaylight.mdsal.dom.api.DOMDataBroker;
34 import org.opendaylight.mdsal.dom.api.DOMDataTreeReadTransaction;
35 import org.opendaylight.mdsal.dom.api.DOMMountPoint;
36 import org.opendaylight.mdsal.dom.api.DOMMountPointService;
37 import org.opendaylight.mdsal.dom.api.DOMRpcService;
38 import org.opendaylight.mdsal.dom.api.DOMSchemaService;
39 import org.opendaylight.mdsal.dom.spi.FixedDOMSchemaService;
40 import org.opendaylight.netconf.dom.api.NetconfDataTreeService;
41 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
42 import org.opendaylight.restconf.nb.rfc8040.AbstractJukeboxTest;
43 import org.opendaylight.restconf.nb.rfc8040.databind.DatabindContext;
44 import org.opendaylight.restconf.nb.rfc8040.legacy.NormalizedNodePayload;
45 import org.opendaylight.yangtools.yang.common.ErrorTag;
46 import org.opendaylight.yangtools.yang.common.ErrorType;
47 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
48 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
49 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
50 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
51 import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
52 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
54 @ExtendWith(MockitoExtension.class)
55 class RestconfDataGetTest extends AbstractJukeboxTest {
56 private static final NodeIdentifier PLAYLIST_NID = new NodeIdentifier(PLAYLIST_QNAME);
57 private static final NodeIdentifier LIBRARY_NID = new NodeIdentifier(LIBRARY_QNAME);
59 // config contains one child the same as in operational and one additional
60 private static final ContainerNode CONFIG_JUKEBOX = Builders.containerBuilder()
61 .withNodeIdentifier(new NodeIdentifier(JUKEBOX_QNAME))
62 .withChild(CONT_PLAYER)
63 .withChild(Builders.containerBuilder().withNodeIdentifier(LIBRARY_NID).build())
65 // operational contains one child the same as in config and one additional
66 private static final ContainerNode OPER_JUKEBOX = Builders.containerBuilder()
67 .withNodeIdentifier(new NodeIdentifier(JUKEBOX_QNAME))
68 .withChild(CONT_PLAYER)
69 .withChild(Builders.mapBuilder().withNodeIdentifier(PLAYLIST_NID).build())
73 private UriInfo uriInfo;
75 private AsyncResponse asyncResponse;
77 private DOMDataBroker dataBroker;
79 private DOMActionService actionService;
81 private DOMRpcService rpcService;
83 private DOMMountPointService mountPointService;
85 private DOMDataTreeReadTransaction tx;
87 private DOMMountPoint mountPoint;
89 private ArgumentCaptor<Response> responseCaptor;
91 private ArgumentCaptor<RestconfDocumentedException> exceptionCaptor;
93 private RestconfImpl restconf;
97 restconf = new RestconfImpl(new MdsalRestconfServer(() -> DatabindContext.ofModel(JUKEBOX_SCHEMA), dataBroker,
98 rpcService, actionService, mountPointService));
99 doReturn(tx).when(dataBroker).newReadOnlyTransaction();
103 void testReadData() {
104 doReturn(new MultivaluedHashMap<>()).when(uriInfo).getQueryParameters();
105 doReturn(immediateFluentFuture(Optional.of(EMPTY_JUKEBOX))).when(tx)
106 .read(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
107 doReturn(immediateFluentFuture(Optional.empty()))
108 .when(tx).read(LogicalDatastoreType.OPERATIONAL, JUKEBOX_IID);
110 doReturn(true).when(asyncResponse).resume(responseCaptor.capture());
111 restconf.dataGET("example-jukebox:jukebox", uriInfo, asyncResponse);
112 final var response = responseCaptor.getValue();
113 assertEquals(200, response.getStatus());
114 assertEquals(EMPTY_JUKEBOX, ((NormalizedNodePayload) response.getEntity()).data());
118 void testReadRootData() {
119 doReturn(new MultivaluedHashMap<>()).when(uriInfo).getQueryParameters();
120 doReturn(immediateFluentFuture(Optional.of(wrapNodeByDataRootContainer(CONFIG_JUKEBOX))))
122 .read(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.of());
123 doReturn(immediateFluentFuture(Optional.of(wrapNodeByDataRootContainer(OPER_JUKEBOX))))
125 .read(LogicalDatastoreType.OPERATIONAL, YangInstanceIdentifier.of());
127 doReturn(true).when(asyncResponse).resume(responseCaptor.capture());
128 restconf.dataGET(uriInfo, asyncResponse);
129 final var response = responseCaptor.getValue();
130 assertEquals(200, response.getStatus());
132 final var data = assertInstanceOf(ContainerNode.class, ((NormalizedNodePayload) response.getEntity()).data());
133 final var rootNodes = data.body();
134 assertEquals(1, rootNodes.size());
135 final var allDataChildren = assertInstanceOf(ContainerNode.class, rootNodes.iterator().next()).body();
136 assertEquals(3, allDataChildren.size());
139 private static ContainerNode wrapNodeByDataRootContainer(final DataContainerChild data) {
140 return Builders.containerBuilder()
141 .withNodeIdentifier(NodeIdentifier.create(SchemaContext.NAME))
147 * Test read data from mount point when both {@link LogicalDatastoreType#CONFIGURATION} and
148 * {@link LogicalDatastoreType#OPERATIONAL} contains the same data and some additional data to be merged.
151 void testReadDataMountPoint() {
152 doReturn(new MultivaluedHashMap<>()).when(uriInfo).getQueryParameters();
153 doReturn(immediateFluentFuture(Optional.of(CONFIG_JUKEBOX))).when(tx)
154 .read(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
155 doReturn(immediateFluentFuture(Optional.of(OPER_JUKEBOX))).when(tx)
156 .read(LogicalDatastoreType.OPERATIONAL, JUKEBOX_IID);
158 doReturn(Optional.of(mountPoint)).when(mountPointService)
159 .getMountPoint(any(YangInstanceIdentifier.class));
160 doReturn(Optional.of(FixedDOMSchemaService.of(JUKEBOX_SCHEMA))).when(mountPoint)
161 .getService(DOMSchemaService.class);
162 doReturn(Optional.of(dataBroker)).when(mountPoint).getService(DOMDataBroker.class);
163 doReturn(Optional.of(rpcService)).when(mountPoint).getService(DOMRpcService.class);
164 doReturn(Optional.empty()).when(mountPoint).getService(NetconfDataTreeService.class);
166 doReturn(true).when(asyncResponse).resume(responseCaptor.capture());
167 restconf.dataGET("example-jukebox:jukebox/yang-ext:mount/example-jukebox:jukebox", uriInfo, asyncResponse);
168 final var response = responseCaptor.getValue();
169 assertEquals(200, response.getStatus());
171 // response must contain all child nodes from config and operational containers merged in one container
172 final var data = assertInstanceOf(ContainerNode.class, ((NormalizedNodePayload) response.getEntity()).data());
173 assertEquals(3, data.size());
174 assertNotNull(data.childByArg(CONT_PLAYER.name()));
175 assertNotNull(data.childByArg(LIBRARY_NID));
176 assertNotNull(data.childByArg(PLAYLIST_NID));
180 void testReadDataNoData() {
181 doReturn(new MultivaluedHashMap<>()).when(uriInfo).getQueryParameters();
182 doReturn(immediateFluentFuture(Optional.empty()))
183 .when(tx).read(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
184 doReturn(immediateFluentFuture(Optional.empty()))
185 .when(tx).read(LogicalDatastoreType.OPERATIONAL, JUKEBOX_IID);
187 doReturn(true).when(asyncResponse).resume(exceptionCaptor.capture());
188 restconf.dataGET("example-jukebox:jukebox", uriInfo, asyncResponse);
190 final var errors = exceptionCaptor.getValue().getErrors();
191 assertEquals(1, errors.size());
192 final var error = errors.get(0);
193 assertEquals(ErrorType.PROTOCOL, error.getErrorType());
194 assertEquals(ErrorTag.DATA_MISSING, error.getErrorTag());
195 assertEquals("Request could not be completed because the relevant data model content does not exist",
196 error.getErrorMessage());
200 * Read data from config datastore according to content parameter.
203 void testReadDataConfigTest() {
204 final MultivaluedHashMap<String, String> parameters = new MultivaluedHashMap<>();
205 parameters.put("content", List.of("config"));
207 doReturn(parameters).when(uriInfo).getQueryParameters();
208 doReturn(immediateFluentFuture(Optional.of(CONFIG_JUKEBOX))).when(tx)
209 .read(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
211 doReturn(true).when(asyncResponse).resume(responseCaptor.capture());
212 restconf.dataGET("example-jukebox:jukebox", uriInfo, asyncResponse);
213 final var response = responseCaptor.getValue();
214 assertEquals(200, response.getStatus());
216 // response must contain only config data
217 final var data = assertInstanceOf(ContainerNode.class, ((NormalizedNodePayload) response.getEntity()).data());
219 // config data present
220 assertNotNull(data.childByArg(CONT_PLAYER.name()));
221 assertNotNull(data.childByArg(LIBRARY_NID));
224 assertNull(data.childByArg(PLAYLIST_NID));
228 * Read data from operational datastore according to content parameter.
231 void testReadDataOperationalTest() {
232 final var parameters = new MultivaluedHashMap<String, String>();
233 parameters.putSingle("content", "nonconfig");
235 doReturn(parameters).when(uriInfo).getQueryParameters();
236 doReturn(immediateFluentFuture(Optional.of(OPER_JUKEBOX))).when(tx)
237 .read(LogicalDatastoreType.OPERATIONAL, JUKEBOX_IID);
239 doReturn(true).when(asyncResponse).resume(responseCaptor.capture());
240 restconf.dataGET("example-jukebox:jukebox", uriInfo, asyncResponse);
241 final var response = responseCaptor.getValue();
243 assertEquals(200, response.getStatus());
245 // response must contain only operational data
246 final var data = assertInstanceOf(ContainerNode.class, ((NormalizedNodePayload) response.getEntity()).data());
248 // state data present
249 assertNotNull(data.childByArg(CONT_PLAYER.name()));
250 assertNotNull(data.childByArg(PLAYLIST_NID));
252 // config data absent
253 assertNull(data.childByArg(LIBRARY_NID));