Refactor OpenApiTestUtils methods
[netconf.git] / restconf / sal-rest-docgen / src / test / java / org / opendaylight / netconf / sal / rest / doc / impl / ApiDocGeneratorRFC8040Test.java
1 /*
2  * Copyright (c) 2021 PANTHEON.tech, s.r.o. 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.assertNull;
14 import static org.junit.Assert.assertTrue;
15 import static org.opendaylight.netconf.sal.rest.doc.model.builder.OperationBuilder.CONTENT_KEY;
16 import static org.opendaylight.netconf.sal.rest.doc.model.builder.OperationBuilder.REF_KEY;
17 import static org.opendaylight.netconf.sal.rest.doc.model.builder.OperationBuilder.REQUEST_BODY_KEY;
18 import static org.opendaylight.netconf.sal.rest.doc.model.builder.OperationBuilder.RESPONSES_KEY;
19 import static org.opendaylight.netconf.sal.rest.doc.model.builder.OperationBuilder.SCHEMA_KEY;
20 import static org.opendaylight.netconf.sal.rest.doc.model.builder.OperationBuilder.getAppropriateModelPrefix;
21
22 import com.fasterxml.jackson.databind.JsonNode;
23 import com.fasterxml.jackson.databind.node.ArrayNode;
24 import com.fasterxml.jackson.databind.node.JsonNodeFactory;
25 import com.fasterxml.jackson.databind.node.ObjectNode;
26 import com.google.common.collect.ImmutableList;
27 import com.google.common.collect.Sets;
28 import java.util.ArrayList;
29 import java.util.HashSet;
30 import java.util.List;
31 import java.util.Set;
32 import java.util.stream.Collectors;
33 import java.util.stream.StreamSupport;
34 import org.junit.Test;
35 import org.opendaylight.netconf.sal.rest.doc.swagger.OpenApiObject;
36 import org.opendaylight.netconf.sal.rest.doc.swagger.SwaggerObject;
37
38 public final class ApiDocGeneratorRFC8040Test extends AbstractApiDocTest {
39     private static final String NAME = "toaster2";
40     private static final String MY_YANG = "my-yang";
41     private static final String MY_YANG_REVISION = "2022-10-06";
42     private static final String REVISION_DATE = "2009-11-20";
43     private static final String NAME_2 = "toaster";
44     private static final String REVISION_DATE_2 = "2009-11-20";
45     private static final String PATH_PARAMS_TEST_MODULE = "path-params-test";
46     private static final String MANDATORY_TEST = "mandatory-test";
47     private static final String CONFIG_ROOT_CONTAINER = "mandatory-test_config_root-container";
48     private static final String ROOT_CONTAINER = "mandatory-test_root-container";
49     private static final String CONFIG_MANDATORY_CONTAINER = "mandatory-test_root-container_config_mandatory-container";
50     private static final String MANDATORY_CONTAINER = "mandatory-test_root-container_mandatory-container";
51     private static final String CONFIG_MANDATORY_LIST = "mandatory-test_root-container_config_mandatory-list";
52     private static final String CONFIG_MANDATORY_LIST_POST = "mandatory-test_root-container_config_mandatory-list_post";
53     private static final String MANDATORY_LIST = "mandatory-test_root-container_mandatory-list";
54     private static final String MANDATORY_TEST_MODULE = "mandatory-test_config_module";
55     private static final String CHOICE_TEST_MODULE = "choice-test";
56     private static final String PROPERTIES = "properties";
57     private static final String CONTAINER = "container";
58     private static final String LIST = "list";
59
60     private final ApiDocGeneratorRFC8040 generator = new ApiDocGeneratorRFC8040(SCHEMA_SERVICE);
61
62     /**
63      * Test that paths are generated according to the model.
64      */
65     @Test
66     public void testPaths() {
67         final SwaggerObject doc = (SwaggerObject) generator.getApiDeclaration(NAME, REVISION_DATE, URI_INFO,
68             ApiDocServiceImpl.OAversion.V2_0);
69
70         assertEquals(List.of("/rests/data",
71             "/rests/data/toaster2:toaster",
72             "/rests/data/toaster2:toaster/toasterSlot={slotId}",
73             "/rests/data/toaster2:toaster/toasterSlot={slotId}/toaster-augmented:slotInfo",
74             "/rests/data/toaster2:lst",
75             "/rests/data/toaster2:lst/cont1",
76             "/rests/data/toaster2:lst/cont1/cont11",
77             "/rests/data/toaster2:lst/cont1/lst11",
78             "/rests/data/toaster2:lst/lst1={key1},{key2}",
79             "/rests/operations/toaster2:make-toast",
80             "/rests/operations/toaster2:cancel-toast",
81             "/rests/operations/toaster2:restock-toaster"),
82             ImmutableList.copyOf(doc.getPaths().fieldNames()));
83     }
84
85     /**
86      * Test that generated configuration paths allow to use operations: get, put, delete and post.
87      */
88     @Test
89     public void testConfigPaths() {
90         final List<String> configPaths = List.of("/rests/data/toaster2:lst",
91                 "/rests/data/toaster2:lst/cont1",
92                 "/rests/data/toaster2:lst/cont1/cont11",
93                 "/rests/data/toaster2:lst/cont1/lst11",
94                 "/rests/data/toaster2:lst/lst1={key1},{key2}");
95
96         final SwaggerObject doc = (SwaggerObject) generator.getApiDeclaration(NAME, REVISION_DATE, URI_INFO,
97             ApiDocServiceImpl.OAversion.V2_0);
98
99         for (final String path : configPaths) {
100             final JsonNode node = doc.getPaths().get(path);
101             assertFalse(node.path("get").isMissingNode());
102             assertFalse(node.path("put").isMissingNode());
103             assertFalse(node.path("delete").isMissingNode());
104             assertFalse(node.path("post").isMissingNode());
105         }
106     }
107
108     /**
109      * Test that generated document contains the following definitions.
110      */
111     @Test
112     public void testDefinitions() {
113         final SwaggerObject doc = (SwaggerObject) generator.getApiDeclaration(NAME, REVISION_DATE, URI_INFO,
114             ApiDocServiceImpl.OAversion.V2_0);
115
116         final ObjectNode definitions = doc.getDefinitions();
117         assertNotNull(definitions);
118
119         final JsonNode configLstTop = definitions.get("toaster2_config_lst_TOP");
120         assertNotNull(configLstTop);
121         DocGenTestHelper.containsReferences(configLstTop, "toaster2:lst", "#/definitions/toaster2_config_lst");
122
123         final JsonNode configLst = definitions.get("toaster2_config_lst");
124         assertNotNull(configLst);
125         DocGenTestHelper.containsReferences(configLst, "lst1", "#/definitions/toaster2_lst_config_lst1");
126         DocGenTestHelper.containsReferences(configLst, "cont1", "#/definitions/toaster2_lst_config_cont1");
127
128         final JsonNode configLst1Top = definitions.get("toaster2_lst_config_lst1_TOP");
129         assertNotNull(configLst1Top);
130         DocGenTestHelper.containsReferences(configLst1Top, "toaster2:lst1", "#/definitions/toaster2_lst_config_lst1");
131
132         final JsonNode configLst1 = definitions.get("toaster2_lst_config_lst1");
133         assertNotNull(configLst1);
134
135         final JsonNode configCont1Top = definitions.get("toaster2_lst_config_cont1_TOP");
136         assertNotNull(configCont1Top);
137         DocGenTestHelper.containsReferences(configCont1Top, "toaster2:cont1",
138             "#/definitions/toaster2_lst_config_cont1");
139
140         final JsonNode configCont1 = definitions.get("toaster2_lst_config_cont1");
141         assertNotNull(configCont1);
142         DocGenTestHelper.containsReferences(configCont1, "cont11", "#/definitions/toaster2_lst_cont1_config_cont11");
143         DocGenTestHelper.containsReferences(configCont1, "lst11", "#/definitions/toaster2_lst_cont1_config_lst11");
144
145         final JsonNode configCont11Top = definitions.get("toaster2_lst_cont1_config_cont11_TOP");
146         assertNotNull(configCont11Top);
147         DocGenTestHelper.containsReferences(configCont11Top, "toaster2:cont11",
148             "#/definitions/toaster2_lst_cont1_config_cont11");
149
150         final JsonNode configCont11 = definitions.get("toaster2_lst_cont1_config_cont11");
151         assertNotNull(configCont11);
152
153         final JsonNode configLst11Top = definitions.get("toaster2_lst_cont1_config_lst11_TOP");
154         assertNotNull(configLst11Top);
155         DocGenTestHelper.containsReferences(configLst11Top, "toaster2:lst11",
156             "#/definitions/toaster2_lst_cont1_config_lst11");
157
158         final JsonNode configLst11 = definitions.get("toaster2_lst_cont1_config_lst11");
159         assertNotNull(configLst11);
160     }
161
162     /**
163      * Test that generated document contains RPC definition for "make-toast" with correct input.
164      */
165     @Test
166     public void testRPC() {
167         final SwaggerObject doc = (SwaggerObject) generator.getApiDeclaration(NAME_2, REVISION_DATE_2, URI_INFO,
168             ApiDocServiceImpl.OAversion.V2_0);
169         assertNotNull(doc);
170
171         final ObjectNode definitions = doc.getDefinitions();
172         final JsonNode inputTop = definitions.get("toaster_make-toast_input_TOP");
173         assertNotNull(inputTop);
174         final String testString = "{\"toaster:input\":{\"$ref\":\"#/definitions/toaster_make-toast_input\"}}";
175         assertEquals(testString, inputTop.get("properties").toString());
176         final JsonNode input = definitions.get("toaster_make-toast_input");
177         final JsonNode properties = input.get("properties");
178         assertTrue(properties.has("toasterDoneness"));
179         assertTrue(properties.has("toasterToastType"));
180     }
181
182     @Test
183     public void testMandatory() {
184         final var doc = (OpenApiObject) generator.getApiDeclaration(MANDATORY_TEST, null, URI_INFO,
185             ApiDocServiceImpl.OAversion.V3_0);
186         assertNotNull(doc);
187         final var definitions = doc.getComponents().getSchemas();
188         final var containersWithRequired = new ArrayList<String>();
189
190         final var reqRootContainerElements = Set.of("mandatory-root-leaf", "mandatory-container",
191             "mandatory-first-choice", "mandatory-list");
192         verifyRequiredField(definitions.get(CONFIG_ROOT_CONTAINER), reqRootContainerElements);
193         containersWithRequired.add(CONFIG_ROOT_CONTAINER);
194         verifyRequiredField(definitions.get(ROOT_CONTAINER), reqRootContainerElements);
195         containersWithRequired.add(ROOT_CONTAINER);
196
197         final var reqMandatoryContainerElements = Set.of("mandatory-leaf", "leaf-list-with-min-elements");
198         verifyRequiredField(definitions.get(CONFIG_MANDATORY_CONTAINER), reqMandatoryContainerElements);
199         containersWithRequired.add(CONFIG_MANDATORY_CONTAINER);
200         verifyRequiredField(definitions.get(MANDATORY_CONTAINER), reqMandatoryContainerElements);
201         containersWithRequired.add(MANDATORY_CONTAINER);
202
203         final var reqMandatoryListElements = Set.of("mandatory-list-field");
204         verifyRequiredField(definitions.get(CONFIG_MANDATORY_LIST), reqMandatoryListElements);
205         containersWithRequired.add(CONFIG_MANDATORY_LIST);
206         verifyRequiredField(definitions.get(MANDATORY_LIST), reqMandatoryListElements);
207         containersWithRequired.add(MANDATORY_LIST);
208
209         final var testModuleMandatoryArray = Set.of("root-container", "root-mandatory-list");
210         verifyRequiredField(definitions.get(MANDATORY_TEST_MODULE), testModuleMandatoryArray);
211         containersWithRequired.add(MANDATORY_TEST_MODULE);
212
213         verifyThatPropertyDoesNotHaveRequired(containersWithRequired, definitions);
214     }
215
216     /**
217      * Test that request parameters are correctly numbered.
218      *
219      * <p>
220      * It means we should have name and name1, etc. when we have the same parameter in path multiple times.
221      */
222     @Test
223     public void testParametersNumbering() {
224         final var doc = (OpenApiObject) generator.getApiDeclaration(PATH_PARAMS_TEST_MODULE, null, URI_INFO,
225             ApiDocServiceImpl.OAversion.V3_0);
226
227         var pathToList1 = "/rests/data/path-params-test:cont/list1={name}";
228         assertTrue(doc.getPaths().has(pathToList1));
229         assertEquals(List.of("name"), getPathGetParameters(doc.getPaths(), pathToList1));
230
231         var pathToList2 = "/rests/data/path-params-test:cont/list1={name}/list2={name1}";
232         assertTrue(doc.getPaths().has(pathToList2));
233         assertEquals(List.of("name", "name1"), getPathGetParameters(doc.getPaths(), pathToList2));
234
235         var pathToList3 = "/rests/data/path-params-test:cont/list3={name}";
236         assertTrue(doc.getPaths().has(pathToList3));
237         assertEquals(List.of("name"), getPathGetParameters(doc.getPaths(), pathToList3));
238
239         var pathToList4 = "/rests/data/path-params-test:cont/list1={name}/list4={name1}";
240         assertTrue(doc.getPaths().has(pathToList4));
241         assertEquals(List.of("name", "name1"), getPathGetParameters(doc.getPaths(), pathToList4));
242
243         var pathToList5 = "/rests/data/path-params-test:cont/list1={name}/cont2";
244         assertTrue(doc.getPaths().has(pathToList4));
245         assertEquals(List.of("name"), getPathGetParameters(doc.getPaths(), pathToList5));
246     }
247
248     private static void verifyThatPropertyDoesNotHaveRequired(final List<String> expected,
249         final ObjectNode definitions) {
250         final var fields = definitions.fields();
251         while (fields.hasNext()) {
252             final var next = fields.next();
253             final var nodeName = next.getKey();
254             final var jsonNode = next.getValue();
255             if (expected.contains(nodeName)) {
256                 continue;
257             }
258             assertNull("Json node " + nodeName + " should not have 'required' field in body",
259                 jsonNode.get("required"));
260         }
261     }
262
263     private static void verifyRequiredField(final JsonNode rootContainer, final Set<String> expected) {
264         assertNotNull(rootContainer);
265         final var required = rootContainer.get("required");
266         assertNotNull(required);
267         assertTrue(required.isArray());
268         final var actualContainerArray = StreamSupport.stream(required.spliterator(), false)
269             .map(JsonNode::textValue)
270             .collect(Collectors.toSet());
271         assertEquals(expected, actualContainerArray);
272     }
273
274     /**
275      * Test that request parameters are correctly typed.
276      */
277     @Test
278     public void testParametersTypes() {
279         final var doc = (OpenApiObject) generator.getApiDeclaration("typed-params", "2023-10-24", URI_INFO,
280             ApiDocServiceImpl.OAversion.V3_0);
281         final var pathToContainer = "/rests/data/typed-params:typed/";
282         final var integerTypes = List.of("uint64", "uint32", "uint16", "uint8", "int64", "int32", "int16", "int8");
283         for (final var type: integerTypes) {
284             final var typeKey = type + "-key";
285             final var path = pathToContainer + type + "={" + typeKey + "}";
286             assertTrue(doc.getPaths().has(path));
287             assertEquals("integer", doc.getPaths().get(path).get("get").get("parameters").get(0).get("schema")
288                 .get("type").textValue());
289         }
290     }
291
292     /**
293      * Test that request for actions is correct and has parameters.
294      */
295     @Test
296     public void testActionPathsParams() {
297         final var doc = (OpenApiObject) generator.getApiDeclaration("action-types", null, URI_INFO,
298             ApiDocServiceImpl.OAversion.V3_0);
299
300         final var pathWithParameters = "/rests/operations/action-types:list={name}/list-action";
301         assertTrue(doc.getPaths().has(pathWithParameters));
302         assertEquals(List.of("name"), getPathPostParameters(doc.getPaths(), pathWithParameters));
303
304         final var pathWithoutParameters = "/rests/operations/action-types:multi-container/inner-container/action";
305         assertTrue(doc.getPaths().has(pathWithoutParameters));
306         assertEquals(List.of(), getPathPostParameters(doc.getPaths(), pathWithoutParameters));
307     }
308
309     @Test
310     public void testChoice() {
311         final var doc = (SwaggerObject) generator.getApiDeclaration(CHOICE_TEST_MODULE, null, URI_INFO,
312             ApiDocServiceImpl.OAversion.V2_0);
313         assertNotNull(doc);
314
315         final var definitions = doc.getDefinitions();
316         JsonNode firstContainer = definitions.get("choice-test_first-container");
317         assertEquals("default-value",
318                 firstContainer.get(PROPERTIES).get("leaf-default").get("default").asText());
319         assertFalse(firstContainer.get(PROPERTIES).has("leaf-non-default"));
320
321         JsonNode secondContainer = definitions.get("choice-test_second-container");
322         assertTrue(secondContainer.get(PROPERTIES).has("leaf-first-case"));
323         assertFalse(secondContainer.get(PROPERTIES).has("leaf-second-case"));
324     }
325
326     @Test
327     public void testSimpleOpenApiObjects() {
328         final var doc = (OpenApiObject) generator.getApiDeclaration(MY_YANG, MY_YANG_REVISION, URI_INFO,
329             ApiDocServiceImpl.OAversion.V3_0);
330         assertEquals(List.of("/rests/data", "/rests/data/my-yang:data"),
331                 ImmutableList.copyOf(doc.getPaths().fieldNames()));
332
333         final var JsonNodeMyYangData = doc.getPaths().get("/rests/data/my-yang:data");
334         verifyRequestRef(JsonNodeMyYangData.path("post"),
335                 "#/components/schemas/my-yang_config_data",
336                 "#/components/schemas/my-yang_config_data");
337         verifyRequestRef(JsonNodeMyYangData.path("put"), "#/components/schemas/my-yang_config_data_TOP",
338                 "#/components/schemas/my-yang_config_data");
339         verifyRequestRef(JsonNodeMyYangData.path("get"), "#/components/schemas/my-yang_data_TOP",
340                 "#/components/schemas/my-yang_data");
341
342         // Test `components/schemas` objects
343         final var definitions = doc.getComponents().getSchemas();
344         assertEquals(5, definitions.size());
345         assertTrue(definitions.has("my-yang_config_data"));
346         assertTrue(definitions.has("my-yang_config_data_TOP"));
347         assertTrue(definitions.has("my-yang_data"));
348         assertTrue(definitions.has("my-yang_data_TOP"));
349         assertTrue(definitions.has("my-yang_config_module"));
350     }
351
352     @Test
353     public void testToaster2OpenApiObjects() {
354         final var doc = (OpenApiObject) generator.getApiDeclaration(NAME, REVISION_DATE, URI_INFO,
355             ApiDocServiceImpl.OAversion.V3_0);
356         final var jsonNodeToaster = doc.getPaths().get("/rests/data/toaster2:toaster");
357         verifyPostRequestRef(jsonNodeToaster.path("post"),
358             "#/components/schemas/toaster2_toaster_config_toasterSlot",
359             "#/components/schemas/toaster2_toaster_config_toasterSlot", LIST);
360         verifyRequestRef(jsonNodeToaster.path("put"), "#/components/schemas/toaster2_config_toaster_TOP",
361                 "#/components/schemas/toaster2_config_toaster");
362         verifyRequestRef(jsonNodeToaster.path("get"), "#/components/schemas/toaster2_toaster_TOP",
363                 "#/components/schemas/toaster2_toaster");
364
365         final var jsonNodeToasterSlot = doc.getPaths().get("/rests/data/toaster2:toaster/toasterSlot={slotId}");
366         verifyPostRequestRef(jsonNodeToasterSlot.path("post"),
367                 "#/components/schemas/toaster2_toaster_toasterSlot_config_slotInfo",
368                 "#/components/schemas/toaster2_toaster_toasterSlot_config_slotInfo", CONTAINER);
369         verifyRequestRef(jsonNodeToasterSlot.path("put"),
370                 "#/components/schemas/toaster2_toaster_config_toasterSlot_TOP",
371                 "#/components/schemas/toaster2_toaster_config_toasterSlot");
372         verifyRequestRef(jsonNodeToasterSlot.path("get"), "#/components/schemas/toaster2_toaster_toasterSlot_TOP",
373                 "#/components/schemas/toaster2_toaster_toasterSlot");
374
375         final var jsonNodeSlotInfo = doc.getPaths().get(
376                 "/rests/data/toaster2:toaster/toasterSlot={slotId}/toaster-augmented:slotInfo");
377         verifyRequestRef(jsonNodeSlotInfo.path("post"),
378                 "#/components/schemas/toaster2_toaster_toasterSlot_config_slotInfo",
379                 "#/components/schemas/toaster2_toaster_toasterSlot_config_slotInfo");
380         verifyRequestRef(jsonNodeSlotInfo.path("put"),
381                 "#/components/schemas/toaster2_toaster_toasterSlot_config_slotInfo_TOP",
382                 "#/components/schemas/toaster2_toaster_toasterSlot_config_slotInfo");
383         verifyRequestRef(jsonNodeSlotInfo.path("get"), "#/components/schemas/toaster2_toaster_toasterSlot_slotInfo_TOP",
384                 "#/components/schemas/toaster2_toaster_toasterSlot_slotInfo");
385
386         final var jsonNodeLst = doc.getPaths().get("/rests/data/toaster2:lst");
387         verifyPostRequestRef(jsonNodeLst.path("post"), "#/components/schemas/toaster2_lst_config_cont1",
388                 "#/components/schemas/toaster2_lst_config_cont1", CONTAINER);
389         verifyRequestRef(jsonNodeLst.path("put"), "#/components/schemas/toaster2_config_lst_TOP",
390                 "#/components/schemas/toaster2_config_lst");
391         verifyRequestRef(jsonNodeLst.path("get"), "#/components/schemas/toaster2_lst_TOP",
392                 "#/components/schemas/toaster2_lst");
393
394         final var jsonNodeLst1 = doc.getPaths().get("/rests/data/toaster2:lst/lst1={key1},{key2}");
395         verifyRequestRef(jsonNodeLst1.path("post"), "#/components/schemas/toaster2_lst_config_lst1",
396                 "#/components/schemas/toaster2_lst_config_lst1");
397         verifyRequestRef(jsonNodeLst1.path("put"), "#/components/schemas/toaster2_lst_config_lst1_TOP",
398                 "#/components/schemas/toaster2_lst_config_lst1");
399         verifyRequestRef(jsonNodeLst1.path("get"), "#/components/schemas/toaster2_lst_lst1_TOP",
400                 "#/components/schemas/toaster2_lst_lst1");
401
402         final var jsonNodeMakeToast = doc.getPaths().get("/rests/operations/toaster2:make-toast");
403         assertTrue(jsonNodeMakeToast.path("get").isMissingNode());
404         verifyRequestRef(jsonNodeMakeToast.path("post"), "#/components/schemas/toaster2_make-toast_input_TOP",
405                 "#/components/schemas/toaster2_make-toast_input");
406
407         final var jsonNodeCancelToast = doc.getPaths().get("/rests/operations/toaster2:cancel-toast");
408         assertTrue(jsonNodeCancelToast.path("get").isMissingNode());
409         // Test RPC with empty input
410         final var postContent = jsonNodeCancelToast.path("post").get("requestBody").get("content");
411         final var jsonSchema = postContent.get("application/json").get("schema");
412         assertNull(jsonSchema.get("$ref"));
413         assertEquals(2, jsonSchema.size());
414         final var xmlSchema = postContent.get("application/xml").get("schema");
415         assertNull(xmlSchema.get("$ref"));
416         assertEquals(2, xmlSchema.size());
417         // Test `components/schemas` objects
418         final var definitions = doc.getComponents().getSchemas();
419         assertEquals(44, definitions.size());
420     }
421
422     /**
423      * Test that reference to schema in each path is valid (all referenced schemas exist).
424      */
425     @Test
426     public void testRootPostSchemaReference() {
427         final var document = (OpenApiObject) generator.getApiDeclaration(NAME, REVISION_DATE, URI_INFO,
428             ApiDocServiceImpl.OAversion.V3_0);
429         assertNotNull(document);
430         final var expectedSchema = "toaster2_config_module";
431         // verify schema reference itself
432         verifyRequestRef(document.getPaths().path("/rests/data").path("post"),
433                 getAppropriateModelPrefix(ApiDocServiceImpl.OAversion.V3_0) + expectedSchema,
434                 getAppropriateModelPrefix(ApiDocServiceImpl.OAversion.V3_0) + expectedSchema);
435         // verify existence of the schemas being referenced
436         assertTrue("The expected referenced schema (" + expectedSchema + ") is not created",
437                 document.getComponents().getSchemas().has(expectedSchema));
438     }
439
440     /**
441      * Test that reference to schema in each path is valid (all referenced schemas exist).
442      */
443     @Test
444     public void testSchemasExistenceSingleModule() {
445         final var document = (OpenApiObject) generator.getApiDeclaration(NAME, REVISION_DATE, URI_INFO,
446             ApiDocServiceImpl.OAversion.V3_0);
447         assertNotNull(document);
448         final var referencedSchemas = new HashSet<String>();
449         for (final var elements = document.getPaths().elements(); elements.hasNext(); ) {
450             final var path = elements.next();
451             referencedSchemas.addAll(extractSchemaRefFromPath(path, ApiDocServiceImpl.OAversion.V3_0));
452         }
453         final var schemaNamesIterator = document.getComponents().getSchemas().fieldNames();
454         final var schemaNames = Sets.newHashSet(schemaNamesIterator);
455         for (final var ref : referencedSchemas) {
456             assertTrue("Referenced schema " + ref + " does not exist", schemaNames.contains(ref));
457         }
458     }
459
460     /**
461      * Test that checks if namespace for rpc is present.
462      */
463     @Test
464     public void testRpcNamespace() {
465         final var doc = (OpenApiObject) generator.getApiDeclaration(NAME_2, REVISION_DATE, URI_INFO,
466             ApiDocServiceImpl.OAversion.V3_0);
467         assertNotNull(doc);
468         final var path = doc.getPaths().get("/rests/operations/toaster:cancel-toast");
469         assertNotNull(path);
470         final var post = path.get("post");
471         assertNotNull(post);
472         final var requestBody = post.get("requestBody");
473         assertNotNull(requestBody);
474         final var content = requestBody.get("content");
475         assertNotNull(content);
476         final var application = content.get("application/xml");
477         assertNotNull(application);
478         final var schema = application.get("schema");
479         assertNotNull(schema);
480         final var xml = schema.get("xml");
481         assertNotNull(xml);
482         final var namespace = xml.get("namespace");
483         assertNotNull(namespace);
484         assertEquals("http://netconfcentral.org/ns/toaster", namespace.asText());
485     }
486
487     /**
488      * Test that checks if namespace for actions is present.
489      */
490     @Test
491     public void testActionsNamespace() {
492         final var doc = (OpenApiObject) generator.getApiDeclaration("action-types", null, URI_INFO,
493             ApiDocServiceImpl.OAversion.V3_0);
494         assertNotNull(doc);
495         final var path = doc.getPaths().get("/rests/operations/action-types:multi-container/inner-container/action");
496         assertNotNull(path);
497         final var post = path.get("post");
498         assertNotNull(post);
499         final var requestBody = post.get("requestBody");
500         assertNotNull(requestBody);
501         final var content = requestBody.get("content");
502         assertNotNull(content);
503         final var application = content.get("application/xml");
504         assertNotNull(application);
505         final var schema = application.get("schema");
506         assertNotNull(schema);
507         final var xml = schema.get("xml");
508         assertNotNull(xml);
509         final var namespace = xml.get("namespace");
510         assertNotNull(namespace);
511         assertEquals("urn:ietf:params:xml:ns:yang:test:action:types", namespace.asText());
512     }
513
514     /**
515      * Test that number of elements in payload is correct.
516      */
517     @SuppressWarnings("unchecked")
518     @Test
519     public void testLeafListWithMinElementsPayload() {
520         final var doc = (OpenApiObject) generator.getApiDeclaration(MANDATORY_TEST, null, URI_INFO,
521             ApiDocServiceImpl.OAversion.V3_0);
522         assertNotNull(doc);
523         final var paths = doc.getPaths();
524         final var path = paths.path("/rests/data/mandatory-test:root-container/mandatory-container");
525         final var requestBody = path.path("post").path("requestBody").path("content");
526         final var jsonRef = requestBody.path("application/json").path("schema").path("$ref");
527         final var xmlRef = requestBody.path("application/xml").path("schema").path("$ref");
528         final var schema = doc.getComponents().getSchemas().path("mandatory-test_root-container_mandatory-container");
529         final var minItems = schema.path("properties").path("leaf-list-with-min-elements").path("minItems");
530         final var listOfExamples = ((ArrayNode) schema.path("properties").path("leaf-list-with-min-elements")
531             .path("example"));
532         final var expectedListOfExamples = JsonNodeFactory.instance.arrayNode()
533             .add("Some leaf-list-with-min-elements")
534             .add("Some leaf-list-with-min-elements");
535         assertFalse(listOfExamples.isMissingNode());
536         assertEquals(xmlRef, jsonRef);
537         assertEquals(2, minItems.intValue());
538         assertEquals(expectedListOfExamples, listOfExamples);
539     }
540
541     /**
542      *  Test JSON and XML references for request operation.
543      */
544     private static void verifyRequestRef(final JsonNode path, final String expectedJsonRef,
545             final String expectedXmlRef) {
546         final JsonNode postContent;
547         if (path.get("requestBody") != null) {
548             postContent = path.get("requestBody").get("content");
549         } else {
550             postContent = path.get("responses").get("200").get("content");
551         }
552         assertNotNull(postContent);
553         final var postJsonRef = postContent.get("application/json").get("schema").get("$ref");
554         assertNotNull(postJsonRef);
555         assertEquals(expectedJsonRef, postJsonRef.textValue());
556         final var postXmlRef = postContent.get("application/xml").get("schema").get("$ref");
557         assertNotNull(postXmlRef);
558         assertEquals(expectedXmlRef, postXmlRef.textValue());
559     }
560
561     private static void verifyPostRequestRef(final JsonNode path, final String expectedJsonRef,
562         final String expectedXmlRef, String nodeType) {
563         final JsonNode postContent;
564         if (path.get("requestBody") != null) {
565             postContent = path.get("requestBody").get("content");
566         } else {
567             postContent = path.get("responses").get("200").get("content");
568         }
569         assertNotNull(postContent);
570         final String postJsonRef;
571         if (nodeType.equals(CONTAINER)) {
572             postJsonRef = postContent.path("application/json").path("schema").path("properties").elements().next()
573                 .path("$ref").textValue();
574         } else {
575             postJsonRef = postContent.path("application/json").path("schema").path("properties").elements().next()
576                 .path("items").path("$ref").textValue();
577         }
578         assertEquals(expectedJsonRef, postJsonRef);
579         final var postXmlRef = postContent.get("application/xml").get("schema").get("$ref");
580         assertNotNull(postXmlRef);
581         assertEquals(expectedXmlRef, postXmlRef.textValue());
582     }
583
584     private static Set<String> extractSchemaRefFromPath(final JsonNode path,
585             final ApiDocServiceImpl.OAversion oaversion) {
586         if (path == null || path.isMissingNode()) {
587             return Set.of();
588         }
589         final var references = new HashSet<String>();
590         final var get = path.path("get");
591         if (!get.isMissingNode()) {
592             references.addAll(
593                     schemaRefFromContent(get.path(RESPONSES_KEY).path("200").path(CONTENT_KEY), oaversion));
594         }
595         final var post = path.path("post");
596         if (!post.isMissingNode()) {
597             references.addAll(schemaRefFromContent(post.path(REQUEST_BODY_KEY).path(CONTENT_KEY), oaversion));
598         }
599         final var put = path.path("put");
600         if (!put.isMissingNode()) {
601             references.addAll(schemaRefFromContent(put.path(REQUEST_BODY_KEY).path(CONTENT_KEY), oaversion));
602         }
603         final var patch = path.path("patch");
604         if (!patch.isMissingNode()) {
605             references.addAll(schemaRefFromContent(patch.path(REQUEST_BODY_KEY).path(CONTENT_KEY), oaversion));
606         }
607         return references;
608     }
609
610     private static Set<String> schemaRefFromContent(final JsonNode content,
611             final ApiDocServiceImpl.OAversion oaversion) {
612         final HashSet<String> refs = new HashSet<>();
613         content.fieldNames().forEachRemaining(mediaType -> {
614             final JsonNode ref = content.path(mediaType).path(SCHEMA_KEY).path(REF_KEY);
615             if (ref != null && !ref.isMissingNode()) {
616                 refs.add(ref.asText().replaceFirst(getAppropriateModelPrefix(oaversion), ""));
617             }
618         });
619         return refs;
620     }
621 }