OpenApi: Fix 'min-elements' for XML Leaf-List
[netconf.git] / restconf / sal-rest-docgen / src / test / java / org / opendaylight / netconf / sal / rest / doc / impl / MountPointSwaggerTest.java
1 /*
2  * Copyright (c) 2014 Brocade Communications Systems, Inc. and others.  All rights reserved.
3  *
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
7  */
8 package org.opendaylight.netconf.sal.rest.doc.impl;
9
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;
16
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;
24 import java.util.Map;
25 import java.util.Optional;
26 import java.util.Set;
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;
41
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/";
57
58     private MountPointSwagger swagger;
59     private UriInfo uriDeviceAll;
60     private UriInfo uriDeviceToaster;
61
62     @Before
63     public void before() throws Exception {
64         // We are sharing the global schema service and the mount schema service
65         // in our test.
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));
69
70         final DOMMountPointService service = mock(DOMMountPointService.class);
71         when(service.getMountPoint(INSTANCE_ID)).thenReturn(Optional.of(mountPoint));
72
73         swagger = new MountPointSwaggerGeneratorRFC8040(SCHEMA_SERVICE, service).getMountPointSwagger();
74
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());
79     }
80
81     @Test()
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()
87                 .getValue());
88         assertEquals(INSTANCE_URL, swagger.getInstanceIdentifiers().entrySet().iterator().next()
89                 .getKey());
90         swagger.onMountPointRemoved(INSTANCE_ID); // remove ID from list of mount points
91         assertEquals(0, swagger.getInstanceIdentifiers().size());
92     }
93
94     @Test
95     public void testGetDataStoreApi() {
96         swagger.onMountPointCreated(INSTANCE_ID); // add this ID into the list of mount points
97
98         final SwaggerObject mountPointApi = (SwaggerObject) swagger.getMountPointApi(URI_INFO, 1L, "Datastores", "-",
99             OAversion.V2_0);
100         assertNotNull("failed to find Datastore API", mountPointApi);
101
102         final ObjectNode pathsObject = mountPointApi.getPaths();
103         assertNotNull(pathsObject);
104
105         assertEquals("Unexpected api list size", 2, pathsObject.size());
106
107         final Set<String> actualUrls = new TreeSet<>();
108
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());
116
117             final JsonNode getOperation = operations.get("get");
118
119             assertNotNull("unexpected operation method on " + path, getOperation);
120
121             assertNotNull("expected non-null desc on " + path, getOperation.get("description"));
122         }
123
124         assertEquals(Set.of("/rests/data" + INSTANCE_URL + "yang-ext:mount",
125             "/rests/operations" + INSTANCE_URL + "yang-ext:mount"), actualUrls);
126     }
127
128     /**
129      * Test that request parameters are correctly numbered.
130      *
131      * <p>
132      * It means we should have name and name1, etc. when we have the same parameter in path multiple times.
133      */
134     @Test
135     public void testParametersNumberingForMountPointApi() {
136         swagger.onMountPointCreated(INSTANCE_ID);
137
138         final OpenApiObject mountPointApi = (OpenApiObject) swagger.getMountPointApi(URI_INFO, 1L, Optional.empty(),
139                 OAversion.V3_0);
140         assertNotNull("Failed to find Datastore API", mountPointApi);
141
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"), getPathParameters(mountPointApi.getPaths(), pathToList1));
145
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"), getPathParameters(mountPointApi.getPaths(), pathToList2));
149
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"), getPathParameters(mountPointApi.getPaths(), pathToList3));
153
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"), getPathParameters(mountPointApi.getPaths(), pathToList4));
157
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"), getPathParameters(mountPointApi.getPaths(), pathToList5));
161     }
162
163     /**
164      * Test that request for actions is correct and has parameters.
165      */
166     @Test
167     public void testActionPathsParamsForMountPointApi() {
168         swagger.onMountPointCreated(INSTANCE_ID);
169
170         final var mountPointApi = (OpenApiObject) swagger.getMountPointApi(URI_INFO, 1L, Optional.empty(),
171             OAversion.V3_0);
172         assertNotNull("Failed to find Datastore API", mountPointApi);
173
174         final var pathWithParameters =
175             "/rests/operations/nodes/node=123/yang-ext:mount/action-types:list={name}/list-action";
176         assertTrue(mountPointApi.getPaths().has(pathWithParameters));
177         assertEquals(List.of("name"), getPathParameters(mountPointApi.getPaths(), pathWithParameters));
178
179         final var pathWithoutParameters =
180             "/rests/operations/nodes/node=123/yang-ext:mount/action-types:multi-container/inner-container/action";
181         assertTrue(mountPointApi.getPaths().has(pathWithoutParameters));
182         assertEquals(List.of(), getPathParameters(mountPointApi.getPaths(), pathWithoutParameters));
183     }
184
185     @Test
186     public void testSummaryForAllModules() {
187         swagger.onMountPointCreated(INSTANCE_ID);
188         // get OpenApiObject for the device (all modules)
189         final OpenApiObject openApiAll = (OpenApiObject) swagger.getMountPointApi(uriDeviceAll, DEVICE_ID,
190             Optional.empty(), OAversion.V3_0);
191         final var paths = openApiAll.getPaths();
192         final String getToasterSummary = openApiAll.getPaths()
193             .get(TOASTER_NODE_PATH)
194             .get("get")
195             .get("summary")
196             .asText();
197         assertEquals(TOASTER_NODE_GET_SUMMARY, getToasterSummary);
198     }
199
200     @Test
201     public void testSummaryForSingleModule() {
202         swagger.onMountPointCreated(INSTANCE_ID);
203         // get OpenApiObject for a specific module (toaster) associated with the mounted device
204         final OpenApiObject openApiToaster = (OpenApiObject) swagger.getMountPointApi(uriDeviceToaster, DEVICE_ID,
205             TOASTER, TOASTER_REVISION, OAversion.V3_0);
206         final String getToasterSummary = openApiToaster.getPaths()
207             .get(TOASTER_NODE_PATH)
208             .get("get")
209             .get("summary")
210             .asText();
211         assertEquals(TOASTER_NODE_GET_SUMMARY, getToasterSummary);
212     }
213
214     @Test
215     public void testPathsForSpecificModuleOfMounted() {
216         swagger.onMountPointCreated(INSTANCE_ID);
217         // get OpenApiObject for the device (all modules)
218         final OpenApiObject openApiAll = (OpenApiObject) swagger.getMountPointApi(uriDeviceAll, DEVICE_ID,
219             Optional.empty(), OAversion.V3_0);
220         // get OpenApiObject for a specific module (toaster(2009-11-20))
221         final OpenApiObject openApiToaster = (OpenApiObject) swagger.getMountPointApi(uriDeviceToaster, DEVICE_ID,
222             TOASTER, TOASTER_REVISION, OAversion.V3_0);
223         /*
224             filter paths from openapi for all modules down to only those that are present in openapi for toaster.
225             The object for the path, that in this case ends with "yang-ext:mount" is constructed in a different way
226             when requesting OpenApiObject for a single module compared to requesting it for all modules.
227             We do not want to include it in this particular comparison, so filter it out
228          */
229         final Set<JsonNode> toasterPathsFromAll = new HashSet<>();
230         openApiAll.getPaths().fieldNames().forEachRemaining(path -> {
231             if (openApiToaster.getPaths().has(path) && !path.endsWith("yang-ext:mount")) {
232                 toasterPathsFromAll.add(openApiAll.getPaths().get(path));
233             }
234         });
235         final Set<JsonNode> toasterPathsFromToaster = new HashSet<>();
236         openApiToaster.getPaths().fieldNames().forEachRemaining(path -> {
237             if (!path.endsWith("yang-ext:mount")) {
238                 toasterPathsFromToaster.add(openApiToaster.getPaths().get(path));
239             }
240         });
241         // verify that the filtered set (from openapi for all modules) is the same as the set from openapi for toaster
242         assertEquals(toasterPathsFromToaster, toasterPathsFromAll);
243     }
244
245     /**
246      * Test that checks if namespace for rpc is present.
247      */
248     @Test
249     public void testRpcNamespace() {
250         swagger.onMountPointCreated(INSTANCE_ID);
251         final OpenApiObject openApiToaster = (OpenApiObject) swagger.getMountPointApi(uriDeviceToaster, DEVICE_ID,
252             TOASTER, TOASTER_REVISION, OAversion.V3_0);
253         final var path = openApiToaster
254             .getPaths().get("/rests/operations/nodes/node=123/yang-ext:mount/toaster:cancel-toast");
255         assertNotNull(path);
256         final var post = path.get("post");
257         assertNotNull(post);
258         final var requestBody = post.get("requestBody");
259         assertNotNull(requestBody);
260         final var content = requestBody.get("content");
261         assertNotNull(content);
262         final var application = content.get("application/xml");
263         assertNotNull(application);
264         final var schema = application.get("schema");
265         assertNotNull(schema);
266         final var xml = schema.get("xml");
267         assertNotNull(xml);
268         final var namespace = xml.get("namespace");
269         assertNotNull(namespace);
270         assertEquals("http://netconfcentral.org/ns/toaster", namespace.asText());
271     }
272
273     /**
274      * Test that checks if namespace for actions is present.
275      */
276     @Test
277     public void testActionsNamespace() {
278         swagger.onMountPointCreated(INSTANCE_ID);
279         final var openApiAll = (OpenApiObject) swagger.getMountPointApi(uriDeviceAll, DEVICE_ID,
280             Optional.empty(), OAversion.V3_0);
281         final var path = openApiAll.getPaths().get(
282             "/rests/operations/nodes/node=123/yang-ext:mount/action-types:multi-container/inner-container/action");
283         assertNotNull(path);
284         final var post = path.get("post");
285         assertNotNull(post);
286         final var requestBody = post.get("requestBody");
287         assertNotNull(requestBody);
288         final var content = requestBody.get("content");
289         assertNotNull(content);
290         final var application = content.get("application/xml");
291         assertNotNull(application);
292         final var schema = application.get("schema");
293         assertNotNull(schema);
294         final var xml = schema.get("xml");
295         assertNotNull(xml);
296         final var namespace = xml.get("namespace");
297         assertNotNull(namespace);
298         assertEquals("urn:ietf:params:xml:ns:yang:test:action:types", namespace.asText());
299     }
300
301     /**
302      * Test that number of elements in payload is correct.
303      */
304     @SuppressWarnings("unchecked")
305     @Test
306     public void testLeafListWithMinElementsPayloadOnMountPoint() {
307         swagger.onMountPointCreated(INSTANCE_ID);
308         final var mountPointApi = (OpenApiObject) swagger.getMountPointApi(uriDeviceAll, 1L, Optional.empty(),
309             OAversion.V3_0);
310         assertNotNull(mountPointApi);
311         final var paths = mountPointApi.getPaths();
312         final var path =
313             paths.path("/rests/data/nodes/node=123/yang-ext:mount/mandatory-test:root-container/mandatory-container");
314         final var requestBody = path.path("post").path("requestBody").path("content");
315         final var jsonRef = requestBody.path("application/json").path("schema").path("$ref");
316         final var xmlRef = requestBody.path("application/xml").path("schema").path("$ref");
317         final var schema =
318             mountPointApi.getComponents().getSchemas().path("mandatory-test_root-container_mandatory-container");
319         final var minItems = schema.path("properties").path("leaf-list-with-min-elements").path("minItems");
320         final var listOfExamples = ((ArrayNode) schema.path("properties").path("leaf-list-with-min-elements")
321             .path("example"));
322         final var expectedListOfExamples = JsonNodeFactory.instance.arrayNode()
323             .add("Some leaf-list-with-min-elements")
324             .add("Some leaf-list-with-min-elements");
325         assertFalse(listOfExamples.isMissingNode());
326         assertEquals(jsonRef, xmlRef);
327         assertEquals(2, minItems.intValue());
328         assertEquals(expectedListOfExamples, listOfExamples);
329     }
330 }