2 * Copyright (c) 2014, 2015 Cisco 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
9 package org.opendaylight.controller.sal.rest.doc.impl;
11 import static org.junit.Assert.assertEquals;
12 import static org.junit.Assert.assertNotNull;
13 import static org.junit.Assert.assertTrue;
14 import static org.junit.Assert.fail;
16 import com.google.common.base.Preconditions;
18 import java.util.Arrays;
19 import java.util.HashSet;
20 import java.util.List;
22 import java.util.TreeSet;
23 import javax.ws.rs.core.UriInfo;
24 import org.json.JSONException;
25 import org.json.JSONObject;
26 import org.junit.After;
27 import org.junit.Before;
28 import org.junit.Test;
29 import org.opendaylight.controller.sal.core.api.model.SchemaService;
30 import org.opendaylight.netconf.sal.rest.doc.impl.ApiDocGenerator;
31 import org.opendaylight.netconf.sal.rest.doc.swagger.Api;
32 import org.opendaylight.netconf.sal.rest.doc.swagger.ApiDeclaration;
33 import org.opendaylight.netconf.sal.rest.doc.swagger.Operation;
34 import org.opendaylight.netconf.sal.rest.doc.swagger.Parameter;
35 import org.opendaylight.netconf.sal.rest.doc.swagger.Resource;
36 import org.opendaylight.netconf.sal.rest.doc.swagger.ResourceList;
37 import org.opendaylight.yangtools.yang.model.api.Module;
38 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
40 public class ApiDocGeneratorTest {
42 public static final String HTTP_HOST = "http://host";
43 private static final String NAMESPACE = "http://netconfcentral.org/ns/toaster2";
44 private static final String STRING_DATE = "2009-11-20";
45 private static final Date DATE = Date.valueOf(STRING_DATE);
46 private static final String NAMESPACE_2 = "http://netconfcentral.org/ns/toaster";
47 private static final Date REVISION_2 = Date.valueOf(STRING_DATE);
48 private ApiDocGenerator generator;
49 private DocGenTestHelper helper;
50 private SchemaContext schemaContext;
53 public void setUp() throws Exception {
54 this.generator = new ApiDocGenerator();
55 this.helper = new DocGenTestHelper();
58 this.schemaContext = this.helper.getSchemaContext();
62 public void after() throws Exception {
66 * Method: getApiDeclaration(String module, String revision, UriInfo uriInfo)
69 public void testGetModuleDoc() throws Exception {
70 Preconditions.checkArgument(this.helper.getModules() != null, "No modules found");
72 for (final Module m : this.helper.getSchemaContext().getModules()) {
73 if (m.getQNameModule().getNamespace().toString().equals(NAMESPACE)
74 && m.getQNameModule().getRevision().equals(DATE)) {
75 final ApiDeclaration doc = this.generator.getSwaggerDocSpec(m, "http://localhost:8080/restconf", "",
78 validateTosterDocContainsModulePrefixes(doc);
79 validateSwaggerModules(doc);
80 validateSwaggerApisForPost(doc);
86 * Validate whether ApiDelcaration contains Apis with concrete path and whether this Apis contain specified POST
89 private void validateSwaggerApisForPost(final ApiDeclaration doc) {
90 // two POST URI with concrete schema name in summary
91 final Api lstApi = findApi("/config/toaster2:lst/", doc);
92 assertNotNull("Api /config/toaster2:lst/ wasn't found", lstApi);
93 assertTrue("POST for cont1 in lst is missing",
94 findOperation(lstApi.getOperations(), "POST", "(config)lstPOST", "toaster2/lst(config)lst1-TOP",
95 "toaster2/lst(config)cont1-TOP"));
97 final Api cont1Api = findApi("/config/toaster2:lst/cont1/", doc);
98 assertNotNull("Api /config/toaster2:lst/cont1/ wasn't found", cont1Api);
99 assertTrue("POST for cont11 in cont1 is missing",
100 findOperation(cont1Api.getOperations(), "POST", "(config)cont1POST", "toaster2/lst/cont1(config)cont11-TOP",
101 "toaster2/lst/cont1(config)lst11-TOP"));
104 final Api cont11Api = findApi("/config/toaster2:lst/cont1/cont11/", doc);
105 assertNotNull("Api /config/toaster2:lst/cont1/cont11/ wasn't found", cont11Api);
106 assertTrue("POST operation shouldn't be present.", findOperations(cont11Api.getOperations(), "POST").isEmpty());
111 * Tries to find operation with name {@code operationName} and with summary {@code summary}
113 private boolean findOperation(final List<Operation> operations, final String operationName, final String type,
114 final String... searchedParameters) {
115 final Set<Operation> filteredOperations = findOperations(operations, operationName);
116 for (final Operation operation : filteredOperations) {
117 if (operation.getType().equals(type)) {
118 final List<Parameter> parameters = operation.getParameters();
119 return containAllParameters(parameters, searchedParameters);
125 private Set<Operation> findOperations(final List<Operation> operations, final String operationName) {
126 final Set<Operation> filteredOperations = new HashSet<>();
127 for (final Operation operation : operations) {
128 if (operation.getMethod().equals(operationName)) {
129 filteredOperations.add(operation);
132 return filteredOperations;
135 private boolean containAllParameters(final List<Parameter> searchedIns, final String[] searchedWhats) {
136 for (final String searchedWhat : searchedWhats) {
137 boolean parameterFound = false;
138 for (final Parameter searchedIn : searchedIns) {
139 if (searchedIn.getType().equals(searchedWhat)) {
140 parameterFound = true;
143 if (!parameterFound) {
151 * Tries to find {@code Api} with path {@code path}
153 private Api findApi(final String path, final ApiDeclaration doc) {
154 for (final Api api : doc.getApis()) {
155 if (api.getPath().equals(path)) {
163 * Validates whether doc {@code doc} contains concrete specified models.
165 private void validateSwaggerModules(final ApiDeclaration doc) {
166 final JSONObject models = doc.getModels();
167 assertNotNull(models);
169 final JSONObject configLstTop = models.getJSONObject("toaster2(config)lst-TOP");
170 assertNotNull(configLstTop);
172 containsReferences(configLstTop, "toaster2:lst", "toaster2(config)");
174 final JSONObject configLst = models.getJSONObject("toaster2(config)lst");
175 assertNotNull(configLst);
177 containsReferences(configLst, "toaster2:lst1", "toaster2/lst(config)");
178 containsReferences(configLst, "toaster2:cont1", "toaster2/lst(config)");
180 final JSONObject configLst1Top = models.getJSONObject("toaster2/lst(config)lst1-TOP");
181 assertNotNull(configLst1Top);
183 containsReferences(configLst1Top, "toaster2:lst1", "toaster2/lst(config)");
185 final JSONObject configLst1 = models.getJSONObject("toaster2/lst(config)lst1");
186 assertNotNull(configLst1);
188 final JSONObject configCont1Top = models.getJSONObject("toaster2/lst(config)cont1-TOP");
189 assertNotNull(configCont1Top);
191 containsReferences(configCont1Top, "toaster2:cont1", "toaster2/lst(config)");
192 final JSONObject configCont1 = models.getJSONObject("toaster2/lst(config)cont1");
193 assertNotNull(configCont1);
195 containsReferences(configCont1, "toaster2:cont11", "toaster2/lst/cont1(config)");
196 containsReferences(configCont1, "toaster2:lst11", "toaster2/lst/cont1(config)");
198 final JSONObject configCont11Top = models.getJSONObject("toaster2/lst/cont1(config)cont11-TOP");
199 assertNotNull(configCont11Top);
201 containsReferences(configCont11Top, "toaster2:cont11", "toaster2/lst/cont1(config)");
202 final JSONObject configCont11 = models.getJSONObject("toaster2/lst/cont1(config)cont11");
203 assertNotNull(configCont11);
205 final JSONObject configlst11Top = models.getJSONObject("toaster2/lst/cont1(config)lst11-TOP");
206 assertNotNull(configlst11Top);
208 containsReferences(configlst11Top, "toaster2:lst11", "toaster2/lst/cont1(config)");
209 final JSONObject configLst11 = models.getJSONObject("toaster2/lst/cont1(config)lst11");
210 assertNotNull(configLst11);
211 } catch (final JSONException e) {
212 fail("JSONException wasn't expected");
218 * Checks whether object {@code mainObject} contains in properties/items key $ref with concrete value.
220 private void containsReferences(final JSONObject mainObject, final String childObject, final String prefix)
221 throws JSONException {
222 final JSONObject properties = mainObject.getJSONObject("properties");
223 assertNotNull(properties);
225 final JSONObject nodeInProperties = properties.getJSONObject(childObject);
226 assertNotNull(nodeInProperties);
228 final JSONObject itemsInNodeInProperties = nodeInProperties.getJSONObject("items");
229 assertNotNull(itemsInNodeInProperties);
231 final String itemRef = itemsInNodeInProperties.getString("$ref");
232 assertEquals(prefix + childObject.split(":")[1], itemRef);
236 public void testEdgeCases() throws Exception {
237 Preconditions.checkArgument(this.helper.getModules() != null, "No modules found");
239 for (final Module m : this.helper.getModules()) {
240 if (m.getQNameModule().getNamespace().toString().equals(NAMESPACE_2)
241 && m.getQNameModule().getRevision().equals(REVISION_2)) {
242 final ApiDeclaration doc = this.generator.getSwaggerDocSpec(m, "http://localhost:8080/restconf", "",
246 // testing bugs.opendaylight.org bug 1290. UnionType model type.
247 final String jsonString = doc.getModels().toString();
248 assertTrue(jsonString.contains(
249 "testUnion\":{\"type\":\"-2147483648\",\"required\":false,\"enum\":[\"-2147483648\",\"Some testUnion\"]}"));
255 public void testRPCsModel() throws Exception {
256 Preconditions.checkArgument(this.helper.getModules() != null, "No modules found");
258 for (final Module m : this.helper.getModules()) {
259 if (m.getQNameModule().getNamespace().toString().equals(NAMESPACE_2)
260 && m.getQNameModule().getRevision().equals(REVISION_2)) {
261 final ApiDeclaration doc = this.generator.getSwaggerDocSpec(m, "http://localhost:8080/restconf", "",
265 final JSONObject models = doc.getModels();
266 final JSONObject inputTop = models.getJSONObject("(make-toast)input-TOP");
267 final String testString = "{\"toaster:input\":{\"type\":\"object\",\"items\":{\"$ref\":\"(make-toast)input\"}}}";
268 assertEquals(testString, inputTop.getJSONObject("properties").toString());
269 final JSONObject input = models.getJSONObject("(make-toast)input");
270 final JSONObject properties = input.getJSONObject("properties");
271 assertTrue(properties.has("toaster:toasterDoneness"));
272 assertTrue(properties.has("toaster:toasterToastType"));
278 * Tests whether from yang files are generated all required paths for HTTP operations (GET, DELETE, PUT, POST)
280 * If container | list is augmented then in path there should be specified module name followed with collon (e. g.
281 * "/config/module1:element1/element2/module2:element3")
286 private void validateToaster(final ApiDeclaration doc) throws Exception {
287 final Set<String> expectedUrls = new TreeSet<>(Arrays.asList(new String[] { "/config/toaster2:toaster/",
288 "/operational/toaster2:toaster/", "/operations/toaster2:cancel-toast",
289 "/operations/toaster2:make-toast", "/operations/toaster2:restock-toaster",
290 "/config/toaster2:toaster/toasterSlot/{slotId}/toaster-augmented:slotInfo/" }));
292 final Set<String> actualUrls = new TreeSet<>();
294 Api configApi = null;
295 for (final Api api : doc.getApis()) {
296 actualUrls.add(api.getPath());
297 if (api.getPath().contains("/config/toaster2:toaster/")) {
302 boolean containsAll = actualUrls.containsAll(expectedUrls);
304 expectedUrls.removeAll(actualUrls);
305 fail("Missing expected urls: " + expectedUrls);
308 final Set<String> expectedConfigMethods = new TreeSet<>(Arrays.asList(new String[] { "GET", "PUT", "DELETE" }));
309 final Set<String> actualConfigMethods = new TreeSet<>();
310 for (final Operation oper : configApi.getOperations()) {
311 actualConfigMethods.add(oper.getMethod());
314 containsAll = actualConfigMethods.containsAll(expectedConfigMethods);
316 expectedConfigMethods.removeAll(actualConfigMethods);
317 fail("Missing expected method on config API: " + expectedConfigMethods);
320 // TODO: we should really do some more validation of the
323 * Missing validation: Explicit validation of URLs, and their methods Input / output models.
328 public void testGetResourceListing() throws Exception {
329 final UriInfo info = this.helper.createMockUriInfo(HTTP_HOST);
330 final SchemaService mockSchemaService = this.helper.createMockSchemaService(this.schemaContext);
332 this.generator.setSchemaService(mockSchemaService);
334 final ResourceList resourceListing = this.generator.getResourceListing(info);
336 Resource toaster = null;
337 Resource toaster2 = null;
338 for (final Resource r : resourceListing.getApis()) {
339 final String path = r.getPath();
340 if (path.contains("toaster2")) {
342 } else if (path.contains("toaster")) {
347 assertNotNull(toaster2);
348 assertNotNull(toaster);
350 assertEquals(HTTP_HOST + "/toaster(2009-11-20)", toaster.getPath());
351 assertEquals(HTTP_HOST + "/toaster2(2009-11-20)", toaster2.getPath());
354 private void validateTosterDocContainsModulePrefixes(final ApiDeclaration doc) {
355 final JSONObject topLevelJson = doc.getModels();
357 final JSONObject configToaster = topLevelJson.getJSONObject("toaster2(config)toaster");
358 assertNotNull("(config)toaster JSON object missing", configToaster);
359 // without module prefix
360 containsProperties(configToaster, "toaster2:toasterSlot");
362 final JSONObject toasterSlot = topLevelJson.getJSONObject("toaster2/toaster(config)toasterSlot");
363 assertNotNull("(config)toasterSlot JSON object missing", toasterSlot);
364 // with module prefix
365 containsProperties(toasterSlot, "toaster2:toaster-augmented:slotInfo");
367 } catch (final JSONException e) {
368 fail("Json exception while reading JSON object. Original message " + e.getMessage());
372 private void containsProperties(final JSONObject jsonObject, final String... properties) throws JSONException {
373 for (final String property : properties) {
374 final JSONObject propertiesObject = jsonObject.getJSONObject("properties");
375 assertNotNull("Properties object missing in ", propertiesObject);
376 final JSONObject concretePropertyObject = propertiesObject.getJSONObject(property);
377 assertNotNull(property + " is missing", concretePropertyObject);