Update swagger generator to OpenAPI 3.0
[netconf.git] / restconf / sal-rest-docgen / src / test / java / org / opendaylight / controller / sal / rest / doc / impl / ApiDocGeneratorTest.java
1 /*
2  * Copyright (c) 2014, 2015 Cisco 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
9 package org.opendaylight.controller.sal.rest.doc.impl;
10
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;
15
16 import com.fasterxml.jackson.databind.JsonNode;
17 import com.fasterxml.jackson.databind.node.ObjectNode;
18 import com.google.common.base.Preconditions;
19 import java.sql.Date;
20 import java.util.Arrays;
21 import java.util.HashSet;
22 import java.util.Iterator;
23 import java.util.Set;
24 import java.util.TreeSet;
25 import org.junit.After;
26 import org.junit.Before;
27 import org.junit.Test;
28 import org.opendaylight.netconf.sal.rest.doc.impl.ApiDocGeneratorDraftO2;
29 import org.opendaylight.netconf.sal.rest.doc.impl.ApiDocServiceImpl;
30 import org.opendaylight.netconf.sal.rest.doc.impl.ApiDocServiceImpl.URIType;
31 import org.opendaylight.netconf.sal.rest.doc.swagger.SwaggerObject;
32 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
33 import org.opendaylight.yangtools.yang.model.api.Module;
34
35 public class ApiDocGeneratorTest {
36
37     private static final String NAMESPACE = "http://netconfcentral.org/ns/toaster2";
38     private static final String STRING_DATE = "2009-11-20";
39     private static final Date DATE = Date.valueOf(STRING_DATE);
40     private static final String NAMESPACE_2 = "http://netconfcentral.org/ns/toaster";
41     private static final Date REVISION_2 = Date.valueOf(STRING_DATE);
42     private ApiDocGeneratorDraftO2 generator;
43     private DocGenTestHelper helper;
44     private EffectiveModelContext schemaContext;
45
46     @Before
47     public void setUp() throws Exception {
48         this.helper = new DocGenTestHelper();
49         this.helper.setUp();
50
51         this.schemaContext = this.helper.getSchemaContext();
52
53         this.generator = new ApiDocGeneratorDraftO2(this.helper.createMockSchemaService(this.schemaContext));
54     }
55
56     @After
57     public void after() throws Exception {
58     }
59
60     /**
61      * Method: getApiDeclaration(String module, String revision, UriInfo uriInfo).
62      */
63     @Test
64     public void testGetModuleDoc() throws Exception {
65         Preconditions.checkArgument(this.helper.getModules() != null, "No modules found");
66
67         for (final Module m : this.helper.getSchemaContext().getModules()) {
68             if (m.getQNameModule().getNamespace().toString().equals(NAMESPACE)
69                     && m.getQNameModule().getRevision().equals(DATE)) {
70                 final SwaggerObject doc = this.generator.getSwaggerDocSpec(m, "http","localhost:8181", "/", "",
71                         this.schemaContext, URIType.DRAFT02, ApiDocServiceImpl.OAversion.V2_0);
72                 validateToaster(doc);
73                 validateSwaggerModules(doc);
74             }
75         }
76     }
77
78     /**
79      * Validates whether doc {@code doc} contains concrete specified models.
80      */
81     private void validateSwaggerModules(final SwaggerObject doc) {
82         final ObjectNode definitions = doc.getDefinitions();
83         assertNotNull(definitions);
84
85         final JsonNode configLstTop = definitions.get("toaster2_config_lst_TOP");
86         assertNotNull(configLstTop);
87
88         containsReferences(configLstTop, "lst", "toaster2_config_lst");
89
90         final JsonNode configLst = definitions.get("toaster2_config_lst");
91         assertNotNull(configLst);
92
93         containsReferences(configLst, "lst1", "toaster2_lst_config_lst1");
94         containsReferences(configLst, "cont1", "toaster2_lst_config_cont1");
95
96         final JsonNode configLst1Top = definitions.get("toaster2_lst_config_lst1_TOP");
97         assertNotNull(configLst1Top);
98
99         containsReferences(configLst1Top, "lst1", "toaster2_config_lst");
100
101         final JsonNode configLst1 = definitions.get("toaster2_lst_config_lst1");
102         assertNotNull(configLst1);
103
104         final JsonNode configCont1Top = definitions.get("toaster2_lst_config_cont1_TOP");
105         assertNotNull(configCont1Top);
106
107         containsReferences(configCont1Top, "cont1", "toaster2_config_lst");
108         final JsonNode configCont1 = definitions.get("toaster2_lst_config_cont1");
109         assertNotNull(configCont1);
110
111         containsReferences(configCont1, "cont11", "toaster2_lst_config_cont1");
112         containsReferences(configCont1, "lst11", "toaster2_lst_config_lst1");
113
114         final JsonNode configCont11Top = definitions.get("toaster2_lst_cont1_config_cont11_TOP");
115         assertNotNull(configCont11Top);
116
117         containsReferences(configCont11Top, "cont11", "toaster2_lst_config_cont1");
118         final JsonNode configCont11 = definitions.get("toaster2_lst_cont1_config_cont11");
119         assertNotNull(configCont11);
120
121         final JsonNode configLst11Top = definitions.get("toaster2_lst_cont1_config_lst11_TOP");
122         assertNotNull(configLst11Top);
123
124         containsReferences(configLst11Top, "lst11", "toaster2_lst_config_cont1");
125         final JsonNode configLst11 = definitions.get("toaster2_lst_cont1_config_lst11");
126         assertNotNull(configLst11);
127     }
128
129     /**
130      * Checks whether object {@code mainObject} contains in properties/items key $ref with concrete value.
131      */
132     private void containsReferences(final JsonNode mainObject, final String childObject,
133                                     final String expectedRef) {
134         final JsonNode properties = mainObject.get("properties");
135         assertNotNull(properties);
136
137         final JsonNode childNode = properties.get(childObject);
138         assertNotNull(childNode);
139
140         //list case
141         JsonNode refWrapper = childNode.get("items");
142         if (refWrapper == null) {
143             //container case
144             refWrapper = childNode;
145         }
146         assertEquals(expectedRef, refWrapper.get("$ref").asText());
147     }
148
149     @Test
150     public void testEdgeCases() throws Exception {
151         Preconditions.checkArgument(this.helper.getModules() != null, "No modules found");
152
153         for (final Module m : this.helper.getModules()) {
154             if (m.getQNameModule().getNamespace().toString().equals(NAMESPACE_2)
155                     && m.getQNameModule().getRevision().equals(REVISION_2)) {
156                 final SwaggerObject doc = this.generator.getSwaggerDocSpec(m, "http", "localhost:8080", "/restconf", "",
157                         this.schemaContext, URIType.DRAFT02, ApiDocServiceImpl.OAversion.V2_0);
158                 assertNotNull(doc);
159
160                 // testing bugs.opendaylight.org bug 1290. UnionType model type.
161                 final String jsonString = doc.getDefinitions().toString();
162                 assertTrue(jsonString.contains("testUnion\":{\"required\":false,\"type\":\"-2147483648\","
163                         + "\"enum\":[\"-2147483648\",\"Some testUnion\"]}"));
164             }
165         }
166     }
167
168     @Test
169     public void testRPCsModel() throws Exception {
170         Preconditions.checkArgument(this.helper.getModules() != null, "No modules found");
171
172         for (final Module m : this.helper.getModules()) {
173             if (m.getQNameModule().getNamespace().toString().equals(NAMESPACE_2)
174                     && m.getQNameModule().getRevision().equals(REVISION_2)) {
175                 final SwaggerObject doc = this.generator.getSwaggerDocSpec(m, "http","localhost:8181", "/", "",
176                         this.schemaContext, URIType.DRAFT02, ApiDocServiceImpl.OAversion.V2_0);
177                 assertNotNull(doc);
178
179                 final ObjectNode definitions = doc.getDefinitions();
180                 final JsonNode inputTop = definitions.get("toaster_make-toast_input_TOP");
181                 assertNotNull(inputTop);
182                 final String testString = "{\"input\":{\"$ref\":\"toaster_make-toast_input\"}}";
183                 assertEquals(testString, inputTop.get("properties").toString());
184                 final JsonNode input = definitions.get("toaster_make-toast_input");
185                 final JsonNode properties = input.get("properties");
186                 assertTrue(properties.has("toasterDoneness"));
187                 assertTrue(properties.has("toasterToastType"));
188             }
189         }
190     }
191
192     /**
193      * Tests whether from yang files are generated all required paths for HTTP operations (GET, DELETE, PUT, POST)
194      *
195      * <p>
196      * If container | list is augmented then in path there should be specified module name followed with colon (e. g.
197      * "/config/module1:element1/element2/module2:element3")
198      *
199      * @param doc Api declaration
200      * @throws Exception if operation fails
201      */
202     private void validateToaster(final SwaggerObject doc) throws Exception {
203         final Set<String> expectedUrls =
204                 new TreeSet<>(Arrays.asList("/restconf/config", "/restconf/config/toaster2:toaster",
205                         "/restconf/config/toaster2:toaster/toasterSlot/{slotId}",
206                         "/restconf/config/toaster2:toaster/toasterSlot/{slotId}/toaster-augmented:slotInfo",
207                         "/restconf/operational/toaster2:toaster/toasterSlot/{slotId}",
208                         "/restconf/operational/toaster2:toaster/toasterSlot/{slotId}/toaster-augmented:slotInfo",
209                         "/restconf/config/toaster2:lst",
210                         "/restconf/config/toaster2:lst/cont1",
211                         "/restconf/config/toaster2:lst/cont1/cont11",
212                         "/restconf/config/toaster2:lst/cont1/lst11",
213                         "/restconf/config/toaster2:lst/lst1/{key1}/{key2}",
214                         "/restconf/operational/toaster2:lst",
215                         "/restconf/operational/toaster2:lst/cont1",
216                         "/restconf/operational/toaster2:lst/cont1/cont11",
217                         "/restconf/operational/toaster2:lst/cont1/lst11",
218                         "/restconf/operational/toaster2:lst/lst1/{key1}/{key2}",
219                         "/restconf/operational/toaster2:lst/lst1/{key1}/{key2}",
220                         "/restconf/operations/toaster2:make-toast",
221                         "/restconf/operations/toaster2:cancel-toast"));
222
223
224         final Set<String> actualUrls = new TreeSet<>();
225
226         final Set<JsonNode> configActualPathItems = new HashSet<>();
227         for (final JsonNode pathItem : doc.getPaths()) {
228             final String actualUrl = pathItem.fieldNames().next();
229             actualUrls.add(actualUrl);
230             if (actualUrl.contains("/config/toaster2:toaster/")) {
231                 configActualPathItems.add(pathItem);
232             }
233         }
234
235         final boolean isAllDocumented = actualUrls.containsAll(expectedUrls);
236
237         if (!isAllDocumented) {
238             expectedUrls.removeAll(actualUrls);
239             fail("Missing expected urls: " + expectedUrls);
240         }
241
242         final Set<String> expectedConfigMethods = new TreeSet<>(Arrays.asList("get", "put", "delete", "post"));
243
244         for (final JsonNode configPathItem : configActualPathItems) {
245             final Set<String> actualConfigMethods = new TreeSet<>();
246             final JsonNode operations =  configPathItem.get(0);
247             final Iterator<String> it = operations.fieldNames();
248             while (it.hasNext()) {
249                 actualConfigMethods.add(it.next());
250             }
251             final boolean isAllMethods = actualConfigMethods.containsAll(expectedConfigMethods);
252             if (!isAllMethods) {
253                 expectedConfigMethods.removeAll(actualConfigMethods);
254                 fail("Missing expected method on config API: " + expectedConfigMethods);
255             }
256         }
257
258         // TODO: we should really do some more validation of the
259         // documentation...
260         /*
261          * Missing validation: Explicit validation of URLs, and their methods Input / output models.
262          */
263     }
264
265 }