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.netconf.sal.rest.doc.impl;
10 import static org.junit.Assert.assertEquals;
11 import static org.junit.Assert.assertFalse;
12 import static org.junit.Assert.assertNotNull;
13 import static org.junit.Assert.assertTrue;
14 import static org.mockito.Mockito.mock;
15 import static org.mockito.Mockito.when;
17 import com.fasterxml.jackson.databind.JsonNode;
18 import com.fasterxml.jackson.databind.node.ArrayNode;
19 import com.fasterxml.jackson.databind.node.JsonNodeFactory;
20 import com.fasterxml.jackson.databind.node.ObjectNode;
21 import java.util.HashSet;
22 import java.util.Iterator;
23 import java.util.List;
25 import java.util.Optional;
27 import java.util.TreeSet;
28 import javax.ws.rs.core.UriInfo;
29 import org.glassfish.jersey.internal.util.collection.ImmutableMultivaluedMap;
30 import org.junit.Before;
31 import org.junit.Test;
32 import org.opendaylight.mdsal.dom.api.DOMMountPoint;
33 import org.opendaylight.mdsal.dom.api.DOMMountPointService;
34 import org.opendaylight.mdsal.dom.api.DOMSchemaService;
35 import org.opendaylight.netconf.sal.rest.doc.impl.ApiDocServiceImpl.OAversion;
36 import org.opendaylight.netconf.sal.rest.doc.mountpoints.MountPointSwagger;
37 import org.opendaylight.netconf.sal.rest.doc.swagger.OpenApiObject;
38 import org.opendaylight.netconf.sal.rest.doc.swagger.SwaggerObject;
39 import org.opendaylight.yangtools.yang.common.QName;
40 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
42 public final class MountPointSwaggerTest extends AbstractApiDocTest {
43 private static final String TOASTER = "toaster";
44 private static final String TOASTER_REVISION = "2009-11-20";
45 private static final Long DEVICE_ID = 1L;
46 private static final String DEVICE_NAME = "123";
47 private static final String TOASTER_NODE_PATH = "/rests/data/nodes/node=123/yang-ext:mount/toaster:toaster";
48 private static final String TOASTER_NODE_GET_SUMMARY = "GET - " + DEVICE_NAME + " - toaster - toaster";
49 private static final String GET_ALL = "http://localhost:8181/openapi/api/v3/mounts/" + DEVICE_ID;
50 private static final String GET_TOASTER = "http://localhost:8181/openapi/api/v3/mounts/%s/%s(%s)".formatted(
51 DEVICE_ID, TOASTER, TOASTER_REVISION);
52 private static final YangInstanceIdentifier INSTANCE_ID = YangInstanceIdentifier.builder()
53 .node(QName.create("", "nodes"))
54 .node(QName.create("", "node"))
55 .nodeWithKey(QName.create("", "node"), QName.create("", "id"), DEVICE_NAME).build();
56 private static final String INSTANCE_URL = "/nodes/node=123/";
58 private MountPointSwagger swagger;
59 private UriInfo uriDeviceAll;
60 private UriInfo uriDeviceToaster;
63 public void before() throws Exception {
64 // We are sharing the global schema service and the mount schema service
66 // OK for testing - real thing would have separate instances.
67 final DOMMountPoint mountPoint = mock(DOMMountPoint.class);
68 when(mountPoint.getService(DOMSchemaService.class)).thenReturn(Optional.of(SCHEMA_SERVICE));
70 final DOMMountPointService service = mock(DOMMountPointService.class);
71 when(service.getMountPoint(INSTANCE_ID)).thenReturn(Optional.of(mountPoint));
73 swagger = new MountPointSwaggerGeneratorRFC8040(SCHEMA_SERVICE, service).getMountPointSwagger();
75 uriDeviceAll = DocGenTestHelper.createMockUriInfo(GET_ALL);
76 when(uriDeviceAll.getQueryParameters()).thenReturn(ImmutableMultivaluedMap.empty());
77 uriDeviceToaster = DocGenTestHelper.createMockUriInfo(GET_TOASTER);
78 when(uriDeviceToaster.getQueryParameters()).thenReturn(ImmutableMultivaluedMap.empty());
82 public void getInstanceIdentifiers() {
83 assertEquals(0, swagger.getInstanceIdentifiers().size());
84 swagger.onMountPointCreated(INSTANCE_ID); // add this ID into the list of mount points
85 assertEquals(1, swagger.getInstanceIdentifiers().size());
86 assertEquals((Long) 1L, swagger.getInstanceIdentifiers().entrySet().iterator().next()
88 assertEquals(INSTANCE_URL, swagger.getInstanceIdentifiers().entrySet().iterator().next()
90 swagger.onMountPointRemoved(INSTANCE_ID); // remove ID from list of mount points
91 assertEquals(0, swagger.getInstanceIdentifiers().size());
95 public void testGetDataStoreApi() {
96 swagger.onMountPointCreated(INSTANCE_ID); // add this ID into the list of mount points
98 final SwaggerObject mountPointApi = (SwaggerObject) swagger.getMountPointApi(URI_INFO, 1L, "Datastores", "-",
100 assertNotNull("failed to find Datastore API", mountPointApi);
102 final ObjectNode pathsObject = mountPointApi.getPaths();
103 assertNotNull(pathsObject);
105 assertEquals("Unexpected api list size", 2, pathsObject.size());
107 final Set<String> actualUrls = new TreeSet<>();
109 final Iterator<Map.Entry<String, JsonNode>> fields = pathsObject.fields();
110 while (fields.hasNext()) {
111 final Map.Entry<String, JsonNode> field = fields.next();
112 final String path = field.getKey();
113 final JsonNode operations = field.getValue();
114 actualUrls.add(field.getKey());
115 assertEquals("unexpected operations size on " + path, 1, operations.size());
117 final JsonNode getOperation = operations.get("get");
119 assertNotNull("unexpected operation method on " + path, getOperation);
121 assertNotNull("expected non-null desc on " + path, getOperation.get("description"));
124 assertEquals(Set.of("/rests/data" + INSTANCE_URL + "yang-ext:mount",
125 "/rests/operations" + INSTANCE_URL + "yang-ext:mount"), actualUrls);
129 * Test that request parameters are correctly numbered.
132 * It means we should have name and name1, etc. when we have the same parameter in path multiple times.
135 public void testParametersNumberingForMountPointApi() {
136 swagger.onMountPointCreated(INSTANCE_ID);
138 final OpenApiObject mountPointApi = (OpenApiObject) swagger.getMountPointApi(URI_INFO, 1L, Optional.empty(),
140 assertNotNull("Failed to find Datastore API", mountPointApi);
142 var pathToList1 = "/rests/data/nodes/node=123/yang-ext:mount/path-params-test:cont/list1={name}";
143 assertTrue(mountPointApi.getPaths().has(pathToList1));
144 assertEquals(List.of("name"), getPathGetParameters(mountPointApi.getPaths(), pathToList1));
146 var pathToList2 = "/rests/data/nodes/node=123/yang-ext:mount/path-params-test:cont/list1={name}/list2={name1}";
147 assertTrue(mountPointApi.getPaths().has(pathToList2));
148 assertEquals(List.of("name", "name1"), getPathGetParameters(mountPointApi.getPaths(), pathToList2));
150 var pathToList3 = "/rests/data/nodes/node=123/yang-ext:mount/path-params-test:cont/list3={name}";
151 assertTrue(mountPointApi.getPaths().has(pathToList3));
152 assertEquals(List.of("name"), getPathGetParameters(mountPointApi.getPaths(), pathToList3));
154 var pathToList4 = "/rests/data/nodes/node=123/yang-ext:mount/path-params-test:cont/list1={name}/list4={name1}";
155 assertTrue(mountPointApi.getPaths().has(pathToList4));
156 assertEquals(List.of("name", "name1"), getPathGetParameters(mountPointApi.getPaths(), pathToList4));
158 var pathToList5 = "/rests/data/nodes/node=123/yang-ext:mount/path-params-test:cont/list1={name}/cont2";
159 assertTrue(mountPointApi.getPaths().has(pathToList5));
160 assertEquals(List.of("name"), getPathGetParameters(mountPointApi.getPaths(), pathToList5));
164 * Test that request parameters are correctly typed.
167 public void testParametersTypesForMountPointApi() throws Exception {
168 swagger.onMountPointCreated(INSTANCE_ID);
169 final var doc = (OpenApiObject) swagger.getMountPointApi(URI_INFO, 1L, Optional.empty(),
171 final var pathToContainer = "/rests/data/nodes/node=123/yang-ext:mount/typed-params:typed/";
172 final var integerTypes = List.of("uint64", "uint32", "uint16", "uint8", "int64", "int32", "int16", "int8");
173 for (final var type: integerTypes) {
174 final var typeKey = type + "-key";
175 final var path = pathToContainer + type + "={" + typeKey + "}";
176 assertTrue(doc.getPaths().has(path));
177 assertEquals("integer", doc.getPaths().get(path).get("get").get("parameters").get(0).get("schema")
178 .get("type").textValue());
183 * Test that request for actions is correct and has parameters.
186 public void testActionPathsParamsForMountPointApi() {
187 swagger.onMountPointCreated(INSTANCE_ID);
189 final var mountPointApi = (OpenApiObject) swagger.getMountPointApi(URI_INFO, 1L, Optional.empty(),
191 assertNotNull("Failed to find Datastore API", mountPointApi);
193 final var pathWithParameters =
194 "/rests/operations/nodes/node=123/yang-ext:mount/action-types:list={name}/list-action";
195 assertTrue(mountPointApi.getPaths().has(pathWithParameters));
196 assertEquals(List.of("name"), getPathPostParameters(mountPointApi.getPaths(), pathWithParameters));
198 final var pathWithoutParameters =
199 "/rests/operations/nodes/node=123/yang-ext:mount/action-types:multi-container/inner-container/action";
200 assertTrue(mountPointApi.getPaths().has(pathWithoutParameters));
201 assertEquals(List.of(), getPathPostParameters(mountPointApi.getPaths(), pathWithoutParameters));
205 public void testSummaryForAllModules() {
206 swagger.onMountPointCreated(INSTANCE_ID);
207 // get OpenApiObject for the device (all modules)
208 final OpenApiObject openApiAll = (OpenApiObject) swagger.getMountPointApi(uriDeviceAll, DEVICE_ID,
209 Optional.empty(), OAversion.V3_0);
210 final var paths = openApiAll.getPaths();
211 final String getToasterSummary = openApiAll.getPaths()
212 .get(TOASTER_NODE_PATH)
216 assertEquals(TOASTER_NODE_GET_SUMMARY, getToasterSummary);
220 public void testSummaryForSingleModule() {
221 swagger.onMountPointCreated(INSTANCE_ID);
222 // get OpenApiObject for a specific module (toaster) associated with the mounted device
223 final OpenApiObject openApiToaster = (OpenApiObject) swagger.getMountPointApi(uriDeviceToaster, DEVICE_ID,
224 TOASTER, TOASTER_REVISION, OAversion.V3_0);
225 final String getToasterSummary = openApiToaster.getPaths()
226 .get(TOASTER_NODE_PATH)
230 assertEquals(TOASTER_NODE_GET_SUMMARY, getToasterSummary);
234 public void testPathsForSpecificModuleOfMounted() {
235 swagger.onMountPointCreated(INSTANCE_ID);
236 // get OpenApiObject for the device (all modules)
237 final OpenApiObject openApiAll = (OpenApiObject) swagger.getMountPointApi(uriDeviceAll, DEVICE_ID,
238 Optional.empty(), OAversion.V3_0);
239 // get OpenApiObject for a specific module (toaster(2009-11-20))
240 final OpenApiObject openApiToaster = (OpenApiObject) swagger.getMountPointApi(uriDeviceToaster, DEVICE_ID,
241 TOASTER, TOASTER_REVISION, OAversion.V3_0);
243 filter paths from openapi for all modules down to only those that are present in openapi for toaster.
244 The object for the path, that in this case ends with "yang-ext:mount" is constructed in a different way
245 when requesting OpenApiObject for a single module compared to requesting it for all modules.
246 We do not want to include it in this particular comparison, so filter it out
248 final Set<JsonNode> toasterPathsFromAll = new HashSet<>();
249 openApiAll.getPaths().fieldNames().forEachRemaining(path -> {
250 if (openApiToaster.getPaths().has(path) && !path.endsWith("yang-ext:mount")) {
251 toasterPathsFromAll.add(openApiAll.getPaths().get(path));
254 final Set<JsonNode> toasterPathsFromToaster = new HashSet<>();
255 openApiToaster.getPaths().fieldNames().forEachRemaining(path -> {
256 if (!path.endsWith("yang-ext:mount")) {
257 toasterPathsFromToaster.add(openApiToaster.getPaths().get(path));
260 // verify that the filtered set (from openapi for all modules) is the same as the set from openapi for toaster
261 assertEquals(toasterPathsFromToaster, toasterPathsFromAll);
265 * Test that checks if namespace for rpc is present.
268 public void testRpcNamespace() {
269 swagger.onMountPointCreated(INSTANCE_ID);
270 final OpenApiObject openApiToaster = (OpenApiObject) swagger.getMountPointApi(uriDeviceToaster, DEVICE_ID,
271 TOASTER, TOASTER_REVISION, OAversion.V3_0);
272 final var path = openApiToaster
273 .getPaths().get("/rests/operations/nodes/node=123/yang-ext:mount/toaster:cancel-toast");
275 final var post = path.get("post");
277 final var requestBody = post.get("requestBody");
278 assertNotNull(requestBody);
279 final var content = requestBody.get("content");
280 assertNotNull(content);
281 final var application = content.get("application/xml");
282 assertNotNull(application);
283 final var schema = application.get("schema");
284 assertNotNull(schema);
285 final var xml = schema.get("xml");
287 final var namespace = xml.get("namespace");
288 assertNotNull(namespace);
289 assertEquals("http://netconfcentral.org/ns/toaster", namespace.asText());
293 * Test that checks if namespace for actions is present.
296 public void testActionsNamespace() {
297 swagger.onMountPointCreated(INSTANCE_ID);
298 final var openApiAll = (OpenApiObject) swagger.getMountPointApi(uriDeviceAll, DEVICE_ID,
299 Optional.empty(), OAversion.V3_0);
300 final var path = openApiAll.getPaths().get(
301 "/rests/operations/nodes/node=123/yang-ext:mount/action-types:multi-container/inner-container/action");
303 final var post = path.get("post");
305 final var requestBody = post.get("requestBody");
306 assertNotNull(requestBody);
307 final var content = requestBody.get("content");
308 assertNotNull(content);
309 final var application = content.get("application/xml");
310 assertNotNull(application);
311 final var schema = application.get("schema");
312 assertNotNull(schema);
313 final var xml = schema.get("xml");
315 final var namespace = xml.get("namespace");
316 assertNotNull(namespace);
317 assertEquals("urn:ietf:params:xml:ns:yang:test:action:types", namespace.asText());
321 * Test that number of elements in payload is correct.
323 @SuppressWarnings("unchecked")
325 public void testLeafListWithMinElementsPayloadOnMountPoint() {
326 swagger.onMountPointCreated(INSTANCE_ID);
327 final var mountPointApi = (OpenApiObject) swagger.getMountPointApi(uriDeviceAll, 1L, Optional.empty(),
329 assertNotNull(mountPointApi);
330 final var paths = mountPointApi.getPaths();
332 paths.path("/rests/data/nodes/node=123/yang-ext:mount/mandatory-test:root-container/mandatory-container");
333 final var requestBody = path.path("post").path("requestBody").path("content");
334 final var jsonRef = requestBody.path("application/json").path("schema").path("$ref");
335 final var xmlRef = requestBody.path("application/xml").path("schema").path("$ref");
337 mountPointApi.getComponents().getSchemas().path("mandatory-test_root-container_mandatory-container");
338 final var minItems = schema.path("properties").path("leaf-list-with-min-elements").path("minItems");
339 final var listOfExamples = ((ArrayNode) schema.path("properties").path("leaf-list-with-min-elements")
341 final var expectedListOfExamples = JsonNodeFactory.instance.arrayNode()
342 .add("Some leaf-list-with-min-elements")
343 .add("Some leaf-list-with-min-elements");
344 assertFalse(listOfExamples.isMissingNode());
345 assertEquals(jsonRef, xmlRef);
346 assertEquals(2, minItems.intValue());
347 assertEquals(expectedListOfExamples, listOfExamples);