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.jaxrs;
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.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.YangInstanceIdentifier.NodeIdentifierWithPredicates;
39 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
40 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
41 import org.opendaylight.yangtools.yang.data.spi.node.ImmutableNodes;
42 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
44 // FIXME: this test suite should be refactored as an AbstractDataBrokerTest without mocks:
45 // - AbstractRestconfTest.restconf should be replaced with an instance wired to
46 // AbstractDataBrokerTest.getDomBroker() et al.
47 // - then each test case should initialize the datastores with test data
48 // - then each test case should execute the request
49 // if you are doing this, please structure it so that the infra can be brought down to AbstractRestconfTest and
50 // reused in Netconf822Test and the like
51 @ExtendWith(MockitoExtension.class)
52 class RestconfDataGetTest extends AbstractRestconfTest {
53 private static final NodeIdentifier PLAYLIST_NID = new NodeIdentifier(PLAYLIST_QNAME);
54 private static final NodeIdentifier LIBRARY_NID = new NodeIdentifier(LIBRARY_QNAME);
56 // config contains one child the same as in operational and one additional
57 private static final ContainerNode CONFIG_JUKEBOX = ImmutableNodes.newContainerBuilder()
58 .withNodeIdentifier(new NodeIdentifier(JUKEBOX_QNAME))
59 .withChild(CONT_PLAYER)
60 .withChild(ImmutableNodes.newContainerBuilder().withNodeIdentifier(LIBRARY_NID).build())
62 // operational contains one child the same as in config and one additional
63 private static final ContainerNode OPER_JUKEBOX = ImmutableNodes.newContainerBuilder()
64 .withNodeIdentifier(new NodeIdentifier(JUKEBOX_QNAME))
65 .withChild(CONT_PLAYER)
66 .withChild(ImmutableNodes.newSystemMapBuilder().withNodeIdentifier(PLAYLIST_NID).build())
70 private DOMDataTreeReadTransaction tx;
74 doReturn(tx).when(dataBroker).newReadOnlyTransaction();
79 doReturn(new MultivaluedHashMap<>()).when(uriInfo).getQueryParameters();
80 doReturn(immediateFluentFuture(Optional.of(EMPTY_JUKEBOX))).when(tx)
81 .read(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
82 doReturn(immediateFluentFuture(Optional.empty()))
83 .when(tx).read(LogicalDatastoreType.OPERATIONAL, JUKEBOX_IID);
85 final var body = assertNormalizedBody(200, ar -> restconf.dataGET(JUKEBOX_API_PATH, uriInfo, ar));
86 assertEquals(EMPTY_JUKEBOX, body.data());
89 "example-jukebox:jukebox": {
94 }""", body::formatToJSON, true);
96 <jukebox xmlns="http://example.com/ns/example-jukebox">
100 </jukebox>""", body::formatToXML, true);
104 void testReadRootData() {
105 doReturn(new MultivaluedHashMap<>()).when(uriInfo).getQueryParameters();
106 doReturn(immediateFluentFuture(Optional.of(wrapNodeByDataRootContainer(CONFIG_JUKEBOX))))
108 .read(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.of());
109 doReturn(immediateFluentFuture(Optional.of(wrapNodeByDataRootContainer(OPER_JUKEBOX))))
111 .read(LogicalDatastoreType.OPERATIONAL, YangInstanceIdentifier.of());
113 final var body = assertNormalizedBody(200, ar -> restconf.dataGET(uriInfo, ar));
114 final var data = assertInstanceOf(ContainerNode.class, body.data());
115 final var rootNodes = data.body();
116 assertEquals(1, rootNodes.size());
117 final var allDataChildren = assertInstanceOf(ContainerNode.class, rootNodes.iterator().next()).body();
118 assertEquals(3, allDataChildren.size());
122 "example-jukebox:jukebox": {
127 }""", body::formatToJSON, true);
129 <data xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
130 <jukebox xmlns="http://example.com/ns/example-jukebox">
136 </data>""", body::formatToXML, true);
139 private static ContainerNode wrapNodeByDataRootContainer(final DataContainerChild data) {
140 return ImmutableNodes.newContainerBuilder()
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(new FixedDOMSchemaService(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(DOMActionService.class);
165 doReturn(Optional.empty()).when(mountPoint).getService(DOMMountPointService.class);
166 doReturn(Optional.empty()).when(mountPoint).getService(NetconfDataTreeService.class);
168 // response must contain all child nodes from config and operational containers merged in one container
169 final var body = assertNormalizedBody(200, ar -> restconf.dataGET(
170 apiPath("example-jukebox:jukebox/yang-ext:mount/example-jukebox:jukebox"), uriInfo, ar));
171 final var data = assertInstanceOf(ContainerNode.class, body.data());
172 assertEquals(3, data.size());
173 assertNotNull(data.childByArg(CONT_PLAYER.name()));
174 assertNotNull(data.childByArg(LIBRARY_NID));
175 assertNotNull(data.childByArg(PLAYLIST_NID));
179 "example-jukebox:jukebox": {
184 }""", body::formatToJSON, true);
186 <jukebox xmlns="http://example.com/ns/example-jukebox">
191 </jukebox>""", body::formatToXML, true);
195 void testReadDataNoData() {
196 doReturn(new MultivaluedHashMap<>()).when(uriInfo).getQueryParameters();
197 doReturn(immediateFluentFuture(Optional.empty()))
198 .when(tx).read(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
199 doReturn(immediateFluentFuture(Optional.empty()))
200 .when(tx).read(LogicalDatastoreType.OPERATIONAL, JUKEBOX_IID);
202 final var error = assertError(ar -> restconf.dataGET(JUKEBOX_API_PATH, uriInfo, ar));
203 assertEquals(ErrorType.PROTOCOL, error.getErrorType());
204 assertEquals(ErrorTag.DATA_MISSING, error.getErrorTag());
205 assertEquals("Request could not be completed because the relevant data model content does not exist",
206 error.getErrorMessage());
210 * Read data from config datastore according to content parameter.
213 void testReadDataConfigTest() {
214 final var parameters = new MultivaluedHashMap<String, String>();
215 parameters.putSingle("content", "config");
217 doReturn(parameters).when(uriInfo).getQueryParameters();
218 doReturn(immediateFluentFuture(Optional.of(CONFIG_JUKEBOX))).when(tx)
219 .read(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
221 // response must contain only config data
222 final var body = assertNormalizedBody(200, ar -> restconf.dataGET(JUKEBOX_API_PATH, uriInfo, ar));
223 final var data = assertInstanceOf(ContainerNode.class, body.data());
224 // config data present
225 assertNotNull(data.childByArg(CONT_PLAYER.name()));
226 assertNotNull(data.childByArg(LIBRARY_NID));
228 assertNull(data.childByArg(PLAYLIST_NID));
232 "example-jukebox:jukebox": {
237 }""", body::formatToJSON, true);
239 <jukebox xmlns="http://example.com/ns/example-jukebox">
244 </jukebox>""", body::formatToXML, true);
248 * Read data from operational datastore according to content parameter.
251 void testReadDataOperationalTest() {
252 final var parameters = new MultivaluedHashMap<String, String>();
253 parameters.putSingle("content", "nonconfig");
255 doReturn(parameters).when(uriInfo).getQueryParameters();
256 doReturn(immediateFluentFuture(Optional.of(OPER_JUKEBOX))).when(tx)
257 .read(LogicalDatastoreType.OPERATIONAL, JUKEBOX_IID);
259 // response must contain only operational data
260 final var body = assertNormalizedBody(200, ar -> restconf.dataGET(JUKEBOX_API_PATH, uriInfo, ar));
261 final var data = assertInstanceOf(ContainerNode.class, body.data());
262 // state data present
263 assertNotNull(data.childByArg(CONT_PLAYER.name()));
264 assertNotNull(data.childByArg(PLAYLIST_NID));
266 // config data absent
267 assertNull(data.childByArg(LIBRARY_NID));
271 "example-jukebox:jukebox": {
276 }""", body::formatToJSON, true);
278 <jukebox xmlns="http://example.com/ns/example-jukebox">
282 </jukebox>""", body::formatToXML, true);
286 void readListEntry() {
287 final var parameters = new MultivaluedHashMap<String, String>();
288 parameters.putSingle("content", "nonconfig");
290 doReturn(parameters).when(uriInfo).getQueryParameters();
291 doReturn(immediateFluentFuture(Optional.of(ImmutableNodes.newMapEntryBuilder()
292 .withNodeIdentifier(NodeIdentifierWithPredicates.of(ARTIST_QNAME, NAME_QNAME, "IAmN0t"))
293 .withChild(ImmutableNodes.leafNode(NAME_QNAME, "IAmN0t"))
294 .build()))).when(tx).read(LogicalDatastoreType.OPERATIONAL, YangInstanceIdentifier.builder()
298 .nodeWithKey(ARTIST_QNAME, NAME_QNAME, "IAmN0t")
301 final var body = assertNormalizedBody(200,
302 ar -> restconf.dataGET(apiPath("example-jukebox:jukebox/library/artist=IAmN0t"), uriInfo, ar));
305 "example-jukebox:artist": [
310 }""", body::formatToJSON, true);
312 <artist xmlns="http://example.com/ns/example-jukebox">
314 </artist>""", body::formatToXML, true);