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.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;
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);
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())
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())
62 private DOMDataTreeReadTransaction tx;
66 doReturn(tx).when(dataBroker).newReadOnlyTransaction();
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);
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());
85 void testReadRootData() {
86 doReturn(new MultivaluedHashMap<>()).when(uriInfo).getQueryParameters();
87 doReturn(immediateFluentFuture(Optional.of(wrapNodeByDataRootContainer(CONFIG_JUKEBOX))))
89 .read(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.of());
90 doReturn(immediateFluentFuture(Optional.of(wrapNodeByDataRootContainer(OPER_JUKEBOX))))
92 .read(LogicalDatastoreType.OPERATIONAL, YangInstanceIdentifier.of());
94 doReturn(true).when(asyncResponse).resume(responseCaptor.capture());
95 restconf.dataGET(uriInfo, asyncResponse);
96 final var response = responseCaptor.getValue();
97 assertEquals(200, response.getStatus());
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());
106 private static ContainerNode wrapNodeByDataRootContainer(final DataContainerChild data) {
107 return Builders.containerBuilder()
108 .withNodeIdentifier(NodeIdentifier.create(SchemaContext.NAME))
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.
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);
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);
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());
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));
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);
154 doReturn(true).when(asyncResponse).resume(exceptionCaptor.capture());
155 restconf.dataGET("example-jukebox:jukebox", uriInfo, asyncResponse);
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());
167 * Read data from config datastore according to content parameter.
170 void testReadDataConfigTest() {
171 final MultivaluedHashMap<String, String> parameters = new MultivaluedHashMap<>();
172 parameters.put("content", List.of("config"));
174 doReturn(parameters).when(uriInfo).getQueryParameters();
175 doReturn(immediateFluentFuture(Optional.of(CONFIG_JUKEBOX))).when(tx)
176 .read(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
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());
183 // response must contain only config data
184 final var data = assertInstanceOf(ContainerNode.class, ((NormalizedNodePayload) response.getEntity()).data());
186 // config data present
187 assertNotNull(data.childByArg(CONT_PLAYER.name()));
188 assertNotNull(data.childByArg(LIBRARY_NID));
191 assertNull(data.childByArg(PLAYLIST_NID));
195 * Read data from operational datastore according to content parameter.
198 void testReadDataOperationalTest() {
199 final var parameters = new MultivaluedHashMap<String, String>();
200 parameters.putSingle("content", "nonconfig");
202 doReturn(parameters).when(uriInfo).getQueryParameters();
203 doReturn(immediateFluentFuture(Optional.of(OPER_JUKEBOX))).when(tx)
204 .read(LogicalDatastoreType.OPERATIONAL, JUKEBOX_IID);
206 doReturn(true).when(asyncResponse).resume(responseCaptor.capture());
207 restconf.dataGET("example-jukebox:jukebox", uriInfo, asyncResponse);
208 final var response = responseCaptor.getValue();
210 assertEquals(200, response.getStatus());
212 // response must contain only operational data
213 final var data = assertInstanceOf(ContainerNode.class, ((NormalizedNodePayload) response.getEntity()).data());
215 // state data present
216 assertNotNull(data.childByArg(CONT_PLAYER.name()));
217 assertNotNull(data.childByArg(PLAYLIST_NID));
219 // config data absent
220 assertNull(data.childByArg(LIBRARY_NID));