2 * Copyright (c) 2014 Brocade Communications Systems, Inc. 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.openapi.mountpoints;
10 import static org.junit.Assert.assertEquals;
11 import static org.junit.Assert.assertNotNull;
12 import static org.junit.Assert.assertTrue;
13 import static org.mockito.Mockito.mock;
14 import static org.mockito.Mockito.when;
15 import static org.opendaylight.restconf.openapi.OpenApiTestUtils.getPathGetParameters;
16 import static org.opendaylight.restconf.openapi.OpenApiTestUtils.getPathPostParameters;
17 import static org.opendaylight.restconf.openapi.impl.BaseYangOpenApiGenerator.BASIC_AUTH_NAME;
19 import java.util.ArrayList;
20 import java.util.List;
22 import java.util.Optional;
24 import java.util.TreeSet;
25 import java.util.stream.Collectors;
26 import javax.ws.rs.core.UriInfo;
27 import org.glassfish.jersey.internal.util.collection.ImmutableMultivaluedMap;
28 import org.junit.Before;
29 import org.junit.BeforeClass;
30 import org.junit.Test;
31 import org.opendaylight.mdsal.dom.api.DOMMountPoint;
32 import org.opendaylight.mdsal.dom.api.DOMMountPointService;
33 import org.opendaylight.mdsal.dom.api.DOMSchemaService;
34 import org.opendaylight.restconf.openapi.DocGenTestHelper;
35 import org.opendaylight.restconf.openapi.impl.MountPointOpenApiGeneratorRFC8040;
36 import org.opendaylight.restconf.openapi.model.OpenApiObject;
37 import org.opendaylight.restconf.openapi.model.Operation;
38 import org.opendaylight.restconf.openapi.model.Path;
39 import org.opendaylight.yangtools.yang.common.QName;
40 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
41 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
42 import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils;
44 public final class MountPointOpenApiTest {
45 private static final String TOASTER = "toaster";
46 private static final String TOASTER_REVISION = "2009-11-20";
47 private static final Long DEVICE_ID = 1L;
48 private static final String DEVICE_NAME = "123";
49 private static final String TOASTER_NODE_PATH = "/rests/data/nodes/node=123/yang-ext:mount/toaster:toaster";
50 private static final String TOASTER_NODE_GET_SUMMARY = "GET - " + DEVICE_NAME + " - toaster - toaster";
51 private static final String GET_ALL = "http://localhost:8181/openapi/api/v3/mounts/" + DEVICE_ID;
52 private static final String GET_TOASTER = "http://localhost:8181/openapi/api/v3/mounts/%s/%s(%s)".formatted(
53 DEVICE_ID, TOASTER, TOASTER_REVISION);
54 private static final String HTTP_URL = "http://localhost/path";
55 private static final YangInstanceIdentifier INSTANCE_ID = YangInstanceIdentifier.builder()
56 .node(QName.create("", "nodes"))
57 .node(QName.create("", "node"))
58 .nodeWithKey(QName.create("", "node"), QName.create("", "id"), DEVICE_NAME).build();
59 private static final String INSTANCE_URL = "/nodes/node=123/";
61 private static EffectiveModelContext context;
62 private static DOMSchemaService schemaService;
64 private MountPointOpenApi openApi;
65 private UriInfo uriDeviceAll;
66 private UriInfo uriDeviceToaster;
69 public static void beforeClass() {
70 schemaService = mock(DOMSchemaService.class);
71 context = YangParserTestUtils.parseYangResourceDirectory("/yang");
72 when(schemaService.getGlobalContext()).thenReturn(context);
76 public void before() throws Exception {
77 // We are sharing the global schema service and the mount schema service
79 // OK for testing - real thing would have separate instances.
80 final DOMMountPoint mountPoint = mock(DOMMountPoint.class);
81 when(mountPoint.getService(DOMSchemaService.class)).thenReturn(Optional.of(schemaService));
83 final DOMMountPointService service = mock(DOMMountPointService.class);
84 when(service.getMountPoint(INSTANCE_ID)).thenReturn(Optional.of(mountPoint));
86 openApi = new MountPointOpenApiGeneratorRFC8040(schemaService, service).getMountPointOpenApi();
88 uriDeviceAll = DocGenTestHelper.createMockUriInfo(GET_ALL);
89 when(uriDeviceAll.getQueryParameters()).thenReturn(ImmutableMultivaluedMap.empty());
90 uriDeviceToaster = DocGenTestHelper.createMockUriInfo(GET_TOASTER);
91 when(uriDeviceToaster.getQueryParameters()).thenReturn(ImmutableMultivaluedMap.empty());
95 public void getInstanceIdentifiers() {
96 assertEquals(0, openApi.getInstanceIdentifiers().size());
97 openApi.onMountPointCreated(INSTANCE_ID); // add this ID into the list of mount points
98 assertEquals(1, openApi.getInstanceIdentifiers().size());
99 assertEquals((Long) 1L, openApi.getInstanceIdentifiers().entrySet().iterator().next()
101 assertEquals(INSTANCE_URL, openApi.getInstanceIdentifiers().entrySet().iterator().next()
103 openApi.onMountPointRemoved(INSTANCE_ID); // remove ID from list of mount points
104 assertEquals(0, openApi.getInstanceIdentifiers().size());
108 public void testGetDataStoreApi() throws Exception {
109 final UriInfo mockInfo = DocGenTestHelper.createMockUriInfo(HTTP_URL);
110 openApi.onMountPointCreated(INSTANCE_ID); // add this ID into the list of mount points
112 final OpenApiObject mountPointApi = openApi.getMountPointApi(mockInfo, 1L, "Datastores", "-");
113 assertNotNull("Failed to find Datastore API", mountPointApi);
115 final Map<String, Path> paths = mountPointApi.paths();
116 assertNotNull(paths);
118 assertEquals("Unexpected api list size", 2, paths.size());
120 final Set<String> actualUrls = new TreeSet<>();
122 for (final Map.Entry<String, Path> path : paths.entrySet()) {
123 actualUrls.add(path.getKey());
124 final Operation getOperation = path.getValue().get();
125 assertNotNull("Unexpected operation method on " + path, getOperation);
126 assertNotNull("Expected non-null desc on " + path, getOperation.description());
129 assertEquals(Set.of("/rests/data" + INSTANCE_URL + "yang-ext:mount",
130 "/rests/operations" + INSTANCE_URL + "yang-ext:mount"), actualUrls);
134 * Test that creates mount point api with all models from yang folder and checks operation paths for these models.
137 public void testGetMountPointApi() throws Exception {
138 final UriInfo mockInfo = DocGenTestHelper.createMockUriInfo(HTTP_URL);
139 openApi.onMountPointCreated(INSTANCE_ID);
141 final OpenApiObject mountPointApi = openApi.getMountPointApi(mockInfo, 1L, null);
142 assertNotNull("Failed to find Datastore API", mountPointApi);
144 final Map<String, Path> paths = mountPointApi.paths();
145 assertNotNull(paths);
147 assertEquals("Unexpected api list size", 78, paths.size());
149 final List<Operation> getOperations = new ArrayList<>();
150 final List<Operation> postOperations = new ArrayList<>();
151 final List<Operation> putOperations = new ArrayList<>();
152 final List<Operation> patchOperations = new ArrayList<>();
153 final List<Operation> deleteOperations = new ArrayList<>();
155 for (final Map.Entry<String, Path> path : paths.entrySet()) {
156 Optional.ofNullable(path.getValue().get()).ifPresent(getOperations::add);
157 Optional.ofNullable(path.getValue().post()).ifPresent(postOperations::add);
158 Optional.ofNullable(path.getValue().put()).ifPresent(putOperations::add);
159 Optional.ofNullable(path.getValue().patch()).ifPresent(patchOperations::add);
160 Optional.ofNullable(path.getValue().delete()).ifPresent(deleteOperations::add);
163 assertEquals("Unexpected GET paths size", 70, getOperations.size());
164 assertEquals("Unexpected POST paths size", 21, postOperations.size());
165 assertEquals("Unexpected PUT paths size", 68, putOperations.size());
166 assertEquals("Unexpected PATCH paths size", 68, patchOperations.size());
167 assertEquals("Unexpected DELETE paths size", 68, deleteOperations.size());
171 * Test that checks for correct amount of parameters in requests.
174 @SuppressWarnings("checkstyle:lineLength")
175 public void testMountPointRecursiveParameters() throws Exception {
176 final var configPaths = Map.of("/rests/data/nodes/node=123/yang-ext:mount/recursive:container-root", 0,
177 "/rests/data/nodes/node=123/yang-ext:mount/recursive:container-root/root-list={name}", 1,
178 "/rests/data/nodes/node=123/yang-ext:mount/recursive:container-root/root-list={name}/nested-list={name1}", 2,
179 "/rests/data/nodes/node=123/yang-ext:mount/recursive:container-root/root-list={name}/nested-list={name1}/super-nested-list={name2}", 3);
181 final var mockInfo = DocGenTestHelper.createMockUriInfo(HTTP_URL);
182 openApi.onMountPointCreated(INSTANCE_ID);
184 final var mountPointApi = openApi.getMountPointApi(mockInfo, 1L, "recursive", "2023-05-22");
185 assertNotNull("Failed to find Datastore API", mountPointApi);
187 final var paths = mountPointApi.paths();
188 assertEquals(5, paths.size());
190 for (final var expectedPath : configPaths.entrySet()) {
191 assertTrue(paths.containsKey(expectedPath.getKey()));
192 final int expectedSize = expectedPath.getValue();
194 final var path = paths.get(expectedPath.getKey());
196 final var get = path.get();
198 assertEquals(expectedSize + 1, get.parameters().size());
200 final var put = path.put();
202 assertEquals(expectedSize, put.parameters().size());
204 final var delete = path.delete();
205 assertNotNull(delete);
206 assertEquals(expectedSize, delete.parameters().size());
208 final var patch = path.patch();
209 assertNotNull(patch);
210 assertEquals(expectedSize, patch.parameters().size());
213 // POST request exists only for containers
214 final var post = paths.get("/rests/data/nodes/node=123/yang-ext:mount/recursive:container-root").post();
216 final int expectedSize = configPaths.get("/rests/data/nodes/node=123/yang-ext:mount/recursive:container-root");
217 assertEquals(expectedSize, post.parameters().size());
221 * Test that request parameters are correctly numbered.
224 * It means we should have name and name1, etc. when we have the same parameter in path multiple times.
227 public void testParametersNumberingForMountPointApi() throws Exception {
228 final UriInfo mockInfo = DocGenTestHelper.createMockUriInfo(HTTP_URL);
229 openApi.onMountPointCreated(INSTANCE_ID);
231 final OpenApiObject mountPointApi = openApi.getMountPointApi(mockInfo, 1L, null);
232 assertNotNull("Failed to find Datastore API", mountPointApi);
234 var pathToList1 = "/rests/data/nodes/node=123/yang-ext:mount/path-params-test:cont/list1={name}";
235 assertTrue(mountPointApi.paths().containsKey(pathToList1));
236 assertEquals(List.of("name"), getPathGetParameters(mountPointApi.paths(), pathToList1));
238 var pathToList2 = "/rests/data/nodes/node=123/yang-ext:mount/path-params-test:cont/list1={name}/list2={name1}";
239 assertTrue(mountPointApi.paths().containsKey(pathToList2));
240 assertEquals(List.of("name", "name1"), getPathGetParameters(mountPointApi.paths(), pathToList2));
242 var pathToList3 = "/rests/data/nodes/node=123/yang-ext:mount/path-params-test:cont/list3={name}";
243 assertTrue(mountPointApi.paths().containsKey(pathToList3));
244 assertEquals(List.of("name"), getPathGetParameters(mountPointApi.paths(), pathToList3));
246 var pathToList4 = "/rests/data/nodes/node=123/yang-ext:mount/path-params-test:cont/list1={name}/list4={name1}";
247 assertTrue(mountPointApi.paths().containsKey(pathToList4));
248 assertEquals(List.of("name", "name1"), getPathGetParameters(mountPointApi.paths(), pathToList4));
250 var pathToList5 = "/rests/data/nodes/node=123/yang-ext:mount/path-params-test:cont/list1={name}/cont2";
251 assertTrue(mountPointApi.paths().containsKey(pathToList5));
252 assertEquals(List.of("name"), getPathGetParameters(mountPointApi.paths(), pathToList5));
256 * Test that request parameters are correctly typed.
259 public void testParametersTypesForMountPointApi() throws Exception {
260 final var mockInfo = DocGenTestHelper.createMockUriInfo(HTTP_URL);
261 openApi.onMountPointCreated(INSTANCE_ID);
262 final var doc = openApi.getMountPointApi(mockInfo, 1L, null);
263 final var pathToContainer = "/rests/data/nodes/node=123/yang-ext:mount/typed-params:typed/";
264 final var integerTypes = List.of("uint64", "uint32", "uint16", "uint8", "int64", "int32", "int16", "int8");
265 for (final var type: integerTypes) {
266 final var typeKey = type + "-key";
267 final var path = pathToContainer + type + "={" + typeKey + "}";
268 assertTrue(doc.paths().containsKey(path));
269 assertEquals("integer", doc.paths().get(path).get().parameters().get(0).schema().type());
274 * Test that request for actions is correct and has parameters.
277 public void testActionPathsParamsForMountPointApi() throws Exception {
278 final var mockInfo = DocGenTestHelper.createMockUriInfo(HTTP_URL);
279 openApi.onMountPointCreated(INSTANCE_ID);
281 final var mountPointApi = openApi.getMountPointApi(mockInfo, 1L, null);
282 assertNotNull("Failed to find Datastore API", mountPointApi);
284 final var pathWithParameters =
285 "/rests/operations/nodes/node=123/yang-ext:mount/action-types:list={name}/list-action";
286 assertTrue(mountPointApi.paths().containsKey(pathWithParameters));
287 assertEquals(List.of("name"), getPathPostParameters(mountPointApi.paths(), pathWithParameters));
289 final var pathWithoutParameters =
290 "/rests/operations/nodes/node=123/yang-ext:mount/action-types:multi-container/inner-container/action";
291 assertTrue(mountPointApi.paths().containsKey(pathWithoutParameters));
292 assertEquals(List.of(), getPathPostParameters(mountPointApi.paths(), pathWithoutParameters));
296 * Test that checks if securitySchemes and security elements are present.
299 public void testAuthenticationFeature() throws Exception {
300 final var mockInfo = DocGenTestHelper.createMockUriInfo(HTTP_URL);
301 openApi.onMountPointCreated(INSTANCE_ID);
302 final var mountPointApi = openApi.getMountPointApi(mockInfo, 1L, null);
304 assertEquals("[{basicAuth=[]}]", mountPointApi.security().toString());
305 assertEquals("Http[type=http, scheme=basic, description=null, bearerFormat=null]",
306 mountPointApi.components().securitySchemes().get(BASIC_AUTH_NAME).toString());
310 public void testSummaryForAllModules() {
311 openApi.onMountPointCreated(INSTANCE_ID);
312 // get OpenApiObject for the device (all modules)
313 final OpenApiObject openApiAll = openApi.getMountPointApi(uriDeviceAll, DEVICE_ID, null);
314 final String getToasterSummary = openApiAll.paths()
315 .get(TOASTER_NODE_PATH)
318 assertEquals(TOASTER_NODE_GET_SUMMARY, getToasterSummary);
322 public void testSummaryForSingleModule() {
323 openApi.onMountPointCreated(INSTANCE_ID);
324 // get OpenApiObject for a specific module (toaster) associated with the mounted device
325 final OpenApiObject openApiToaster = openApi.getMountPointApi(uriDeviceToaster, DEVICE_ID, TOASTER,
327 final String getToasterSummary = openApiToaster.paths()
328 .get(TOASTER_NODE_PATH)
331 assertEquals(TOASTER_NODE_GET_SUMMARY, getToasterSummary);
335 public void testPathsForSpecificModuleOfMounted() {
336 openApi.onMountPointCreated(INSTANCE_ID);
338 // get OpenApiObject for the device (all modules)
339 final OpenApiObject openApiAll = openApi.getMountPointApi(uriDeviceAll, DEVICE_ID, null);
340 // get OpenApiObject for a specific module (toaster(2009-11-20))
341 final OpenApiObject openApiToaster = openApi.getMountPointApi(uriDeviceToaster, DEVICE_ID, TOASTER,
345 filter paths from openapi for all modules down to only those that are present in openapi for toaster.
346 The Path object for the path, that in this case ends with "yang-ext:mount" is constructed in a different way
347 when requesting OpenApiObject for a single module compared to requesting it for all modules.
348 We do not want to include it in this particular comparison, so filter it out
350 final Set<Map.Entry<String, Path>> toasterPathsFromAll = openApiAll.paths().entrySet()
352 .filter(e -> openApiToaster.paths().containsKey(e.getKey()) && !e.getKey().endsWith("yang-ext:mount"))
353 .collect(Collectors.toSet());
355 final Set<Map.Entry<String, Path>> toasterPathsFromToaster = openApiToaster.paths().entrySet()
357 .filter(e -> !e.getKey().endsWith("yang-ext:mount"))
358 .collect(Collectors.toSet());
360 // verify that the filtered set (from openapi for all modules) is the same as the set from openapi for toaster
361 assertEquals(toasterPathsFromToaster, toasterPathsFromAll);
365 * Test that checks if namespace for rpc is present.
368 public void testRpcNamespace() throws Exception {
369 final var mockInfo = DocGenTestHelper.createMockUriInfo(HTTP_URL);
370 openApi.onMountPointCreated(INSTANCE_ID);
372 final var mountPointApi = openApi.getMountPointApi(mockInfo, 1L, null);
373 assertNotNull("Failed to find Datastore API", mountPointApi);
374 final var paths = mountPointApi.paths();
375 final var path = paths.get("/rests/operations/nodes/node=123/yang-ext:mount/toaster:cancel-toast");
377 final var content = path.post().requestBody().content().get("application/xml");
378 assertNotNull(content);
379 final var schema = content.schema();
380 assertNotNull(schema);
381 final var xml = schema.xml();
383 final var namespace = xml.namespace();
384 assertNotNull(namespace);
385 assertEquals("http://netconfcentral.org/ns/toaster", namespace);
389 * Test that checks if namespace for actions is present.
392 public void testActionsNamespace() throws Exception {
393 final var mockInfo = DocGenTestHelper.createMockUriInfo(HTTP_URL);
394 openApi.onMountPointCreated(INSTANCE_ID);
395 final var mountPointApi = openApi.getMountPointApi(mockInfo, 1L, null);
396 assertNotNull("Failed to find Datastore API", mountPointApi);
397 final var paths = mountPointApi.paths();
398 final var path = paths.get(
399 "/rests/operations/nodes/node=123/yang-ext:mount/action-types:multi-container/inner-container/action");
401 final var content = path.post().requestBody().content().get("application/xml");
402 assertNotNull(content);
403 final var schema = content.schema();
404 assertNotNull(schema);
405 final var xml = schema.xml();
407 final var namespace = xml.namespace();
408 assertNotNull(namespace);
409 assertEquals("urn:ietf:params:xml:ns:yang:test:action:types", namespace);
413 * Test that number of elements in payload is correct.
415 @SuppressWarnings("unchecked")
417 public void testLeafListWithMinElementsPayloadOnMountPoint() throws Exception {
418 final var mockInfo = DocGenTestHelper.createMockUriInfo(HTTP_URL);
419 openApi.onMountPointCreated(INSTANCE_ID);
420 final var mountPointApi = openApi.getMountPointApi(mockInfo, 1L, null);
421 assertNotNull(mountPointApi);
422 final var paths = mountPointApi.paths();
424 paths.get("/rests/data/nodes/node=123/yang-ext:mount/mandatory-test:root-container/mandatory-container");
426 final var requestBody = path.put().requestBody().content();
427 assertNotNull(requestBody);
428 final var jsonRef = requestBody.get("application/json").schema().properties()
429 .get("mandatory-test:mandatory-container").ref();
430 assertNotNull(jsonRef);
431 final var xmlRef = requestBody.get("application/xml").schema().ref();
432 assertNotNull(xmlRef);
434 mountPointApi.components().schemas().get("mandatory-test_root-container_mandatory-container");
435 assertNotNull(schema);
436 final var minItems = schema.properties().get("leaf-list-with-min-elements").minItems();
437 assertNotNull(minItems);
438 final var listOfExamples = ((List<String>) schema.properties().get("leaf-list-with-min-elements").example());
439 assertNotNull(listOfExamples);
440 assertEquals(jsonRef, xmlRef);
441 assertEquals(listOfExamples.size(), minItems.intValue());