Fix module's root POST request payload
[netconf.git] / restconf / restconf-openapi / src / test / java / org / opendaylight / restconf / openapi / impl / OpenApiGeneratorRFC8040Test.java
index da26b1254bbaf652c35818f5ee26f86233eaef73..4d2da507e474a1d1c2288f84b529c3eee8f077a7 100644 (file)
@@ -17,23 +17,23 @@ import static org.mockito.Mockito.when;
 import static org.opendaylight.restconf.openapi.OpenApiTestUtils.getPathGetParameters;
 import static org.opendaylight.restconf.openapi.OpenApiTestUtils.getPathPostParameters;
 import static org.opendaylight.restconf.openapi.impl.BaseYangOpenApiGenerator.BASIC_AUTH_NAME;
+import static org.opendaylight.restconf.openapi.model.builder.OperationBuilder.COMPONENTS_PREFIX;
 
-import com.fasterxml.jackson.databind.JsonNode;
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.stream.Collectors;
-import java.util.stream.StreamSupport;
 import javax.ws.rs.core.UriInfo;
 import org.junit.BeforeClass;
 import org.junit.Test;
 import org.opendaylight.mdsal.dom.api.DOMSchemaService;
 import org.opendaylight.restconf.openapi.DocGenTestHelper;
+import org.opendaylight.restconf.openapi.model.MediaTypeObject;
 import org.opendaylight.restconf.openapi.model.OpenApiObject;
 import org.opendaylight.restconf.openapi.model.Operation;
 import org.opendaylight.restconf.openapi.model.Path;
+import org.opendaylight.restconf.openapi.model.Property;
 import org.opendaylight.restconf.openapi.model.Schema;
 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
 import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils;
@@ -45,7 +45,6 @@ public final class OpenApiGeneratorRFC8040Test {
     private static final String CONFIG_ROOT_CONTAINER = "mandatory-test_root-container";
     private static final String CONFIG_MANDATORY_CONTAINER = "mandatory-test_root-container_mandatory-container";
     private static final String CONFIG_MANDATORY_LIST = "mandatory-test_root-container_mandatory-list";
-    private static final String MANDATORY_TEST_MODULE = "mandatory-test_module";
     private static final String CONTAINER = "container";
     private static final String LIST = "list";
 
@@ -94,8 +93,8 @@ public final class OpenApiGeneratorRFC8040Test {
                 "/rests/data/toaster2:lst={lf1}/cont1/cont11",
                 "/rests/data/toaster2:lst={lf1}/cont1/lst11={lf111}",
                 "/rests/data/toaster2:lst={lf1}/lst1={key1},{key2}");
-        final List<String> configPathsForPost = List.of("/rests/data/toaster2:lst={lf1}/cont1",
-                "/rests/data/toaster2:lst={lf1}/cont1/cont11");
+        final String configPathForPostCont = "/rests/data/toaster2:lst={lf1}/cont1";
+        final String configPathForPostLeaf = "/rests/data/toaster2:lst={lf1}/cont1/cont11";
 
         final OpenApiObject doc = generator.getApiDeclaration(TOASTER_2, REVISION_DATE, uriInfo);
 
@@ -107,10 +106,12 @@ public final class OpenApiGeneratorRFC8040Test {
             assertNotNull(node.patch());
         }
 
-        for (final String path : configPathsForPost) {
-            final Path node = doc.paths().get(path);
-            assertNotNull(node.post());
-        }
+        final Path node = doc.paths().get(configPathForPostCont);
+        assertNotNull(node.post());
+
+        // Assert we do not generate post for container which contains only leafs.
+        final Path nodeLeaf = doc.paths().get(configPathForPostLeaf);
+        assertNull(nodeLeaf.post());
     }
 
     /**
@@ -143,6 +144,23 @@ public final class OpenApiGeneratorRFC8040Test {
         assertNotNull(configLst11);
     }
 
+    /**
+     * Test that reference to schema in each path is valid (all referenced schemas exist).
+     */
+    @Test
+    public void testSchemasExistenceSingleModule() {
+        final var document = generator.getApiDeclaration(TOASTER_2, REVISION_DATE, uriInfo);
+        assertNotNull(document);
+        final var referencedSchemas = new HashSet<String>();
+        for (final var path : document.paths().values()) {
+            referencedSchemas.addAll(extractSchemaRefFromPath(path));
+        }
+        final var schemaNames = document.components().schemas().keySet();
+        for (final var ref : referencedSchemas) {
+            assertTrue("Referenced schema " + ref + " does not exist", schemaNames.contains(ref));
+        }
+    }
+
     /**
      * Test that generated document contains RPC schemas for "make-toast" with correct input.
      */
@@ -153,9 +171,9 @@ public final class OpenApiGeneratorRFC8040Test {
 
         final Map<String, Schema> schemas = doc.components().schemas();
         final Schema input = schemas.get("toaster_make-toast_input");
-        final JsonNode properties = input.properties();
-        assertTrue(properties.has("toasterDoneness"));
-        assertTrue(properties.has("toasterToastType"));
+        final Map<String, Property> properties = input.properties();
+        assertTrue(properties.containsKey("toasterDoneness"));
+        assertTrue(properties.containsKey("toasterToastType"));
     }
 
     @Test
@@ -165,12 +183,12 @@ public final class OpenApiGeneratorRFC8040Test {
 
         final var schemas = doc.components().schemas();
         final var firstContainer = schemas.get("choice-test_first-container");
-        assertEquals("default-value", firstContainer.properties().get("leaf-default").get("default").asText());
-        assertFalse(firstContainer.properties().has("leaf-non-default"));
+        assertEquals("default-value", firstContainer.properties().get("leaf-default").defaultValue().toString());
+        assertFalse(firstContainer.properties().containsKey("leaf-non-default"));
 
         final var secondContainer = schemas.get("choice-test_second-container");
-        assertTrue(secondContainer.properties().has("leaf-first-case"));
-        assertFalse(secondContainer.properties().has("leaf-second-case"));
+        assertTrue(secondContainer.properties().containsKey("leaf-first-case"));
+        assertFalse(secondContainer.properties().containsKey("leaf-second-case"));
     }
 
     @Test
@@ -180,23 +198,19 @@ public final class OpenApiGeneratorRFC8040Test {
         final var schemas = doc.components().schemas();
         final var containersWithRequired = new ArrayList<String>();
 
-        final var reqRootContainerElements = Set.of("mandatory-root-leaf", "mandatory-container",
+        final var reqRootContainerElements = List.of("mandatory-root-leaf", "mandatory-container",
             "mandatory-first-choice", "mandatory-list");
         verifyRequiredField(schemas.get(CONFIG_ROOT_CONTAINER), reqRootContainerElements);
         containersWithRequired.add(CONFIG_ROOT_CONTAINER);
 
-        final var reqMandatoryContainerElements = Set.of("mandatory-leaf", "leaf-list-with-min-elements");
+        final var reqMandatoryContainerElements = List.of("mandatory-leaf", "leaf-list-with-min-elements");
         verifyRequiredField(schemas.get(CONFIG_MANDATORY_CONTAINER), reqMandatoryContainerElements);
         containersWithRequired.add(CONFIG_MANDATORY_CONTAINER);
 
-        final var reqMandatoryListElements = Set.of("mandatory-list-field");
+        final var reqMandatoryListElements = List.of("mandatory-list-field");
         verifyRequiredField(schemas.get(CONFIG_MANDATORY_LIST), reqMandatoryListElements);
         containersWithRequired.add(CONFIG_MANDATORY_LIST);
 
-        final var testModuleMandatoryArray = Set.of("root-container", "root-mandatory-list");
-        verifyRequiredField(schemas.get(MANDATORY_TEST_MODULE), testModuleMandatoryArray);
-        containersWithRequired.add(MANDATORY_TEST_MODULE);
-
         verifyThatOthersNodeDoesNotHaveRequiredField(containersWithRequired, schemas);
     }
 
@@ -299,16 +313,13 @@ public final class OpenApiGeneratorRFC8040Test {
 
         assertEquals(Set.of("/rests/data", "/rests/data/my-yang:data"), doc.paths().keySet());
         final var JsonNodeMyYangData = doc.paths().get("/rests/data/my-yang:data");
-        verifyPostDataRequestRef(JsonNodeMyYangData.post(), "#/components/schemas/my-yang_data",
-            "#/components/schemas/my-yang_data");
         verifyRequestRef(JsonNodeMyYangData.put(), "#/components/schemas/my-yang_data", CONTAINER);
         verifyRequestRef(JsonNodeMyYangData.get(), "#/components/schemas/my-yang_data", CONTAINER);
 
         // Test `components/schemas` objects
         final var definitions = doc.components().schemas();
-        assertEquals(2, definitions.size());
+        assertEquals(1, definitions.size());
         assertTrue(definitions.containsKey("my-yang_data"));
-        assertTrue(definitions.containsKey("my-yang_module"));
     }
 
     @Test
@@ -326,8 +337,6 @@ public final class OpenApiGeneratorRFC8040Test {
 
         final var jsonNodeSlotInfo = doc.paths().get(
             "/rests/data/toaster2:toaster/toasterSlot={slotId}/toaster-augmented:slotInfo");
-        verifyPostDataRequestRef(jsonNodeSlotInfo.post(), "#/components/schemas/toaster2_toaster_toasterSlot_slotInfo",
-            "#/components/schemas/toaster2_toaster_toasterSlot_slotInfo");
         verifyRequestRef(jsonNodeSlotInfo.put(), "#/components/schemas/toaster2_toaster_toasterSlot_slotInfo",
             CONTAINER);
         verifyRequestRef(jsonNodeSlotInfo.get(), "#/components/schemas/toaster2_toaster_toasterSlot_slotInfo",
@@ -348,17 +357,15 @@ public final class OpenApiGeneratorRFC8040Test {
         final var jsonNodeCancelToast = doc.paths().get("/rests/operations/toaster2:cancel-toast");
         assertNull(jsonNodeCancelToast.get());
         // Test RPC with empty input
-        final var postContent = jsonNodeCancelToast.post().requestBody().get("content");
-        final var jsonSchema = postContent.get("application/json").get("schema");
-        assertNull(jsonSchema.get("$ref"));
-        assertEquals(2, jsonSchema.size());
-        final var xmlSchema = postContent.get("application/xml").get("schema");
-        assertNull(xmlSchema.get("$ref"));
-        assertEquals(2, xmlSchema.size());
+        final var postContent = jsonNodeCancelToast.post().requestBody().content();
+        final var jsonSchema = postContent.get("application/json").schema();
+        assertNull(jsonSchema.ref());
+        final var xmlSchema = postContent.get("application/xml").schema();
+        assertNull(xmlSchema.ref());
 
         // Test `components/schemas` objects
         final var definitions = doc.components().schemas();
-        assertEquals(18, definitions.size());
+        assertEquals(10, definitions.size());
     }
 
     /**
@@ -387,46 +394,67 @@ public final class OpenApiGeneratorRFC8040Test {
     }
 
     /**
-     *  Test JSON and XML references for request operation.
+     * Test that checks if namespace for rpc is present.
      */
-    private static void verifyPostDataRequestRef(final Operation operation, final String expectedJsonRef,
-            final String expectedXmlRef) {
-        final JsonNode postContent;
-        if (operation.requestBody() != null) {
-            postContent = operation.requestBody().get("content");
-        } else {
-            postContent = operation.responses().get("200").get("content");
-        }
-        assertNotNull(postContent);
-        final var postJsonRef = postContent.get("application/json").get("schema").get("$ref");
-        assertNotNull(postJsonRef);
-        assertEquals(expectedJsonRef, postJsonRef.textValue());
-        final var postXmlRef = postContent.get("application/xml").get("schema").get("$ref");
-        assertNotNull(postXmlRef);
-        assertEquals(expectedXmlRef, postXmlRef.textValue());
+    @Test
+    public void testRpcNamespace() {
+        final var doc = generator.getApiDeclaration("toaster", "2009-11-20", uriInfo);
+        assertNotNull("Failed to find Datastore API", doc);
+        final var paths = doc.paths();
+        final var path = paths.get("/rests/operations/toaster:cancel-toast");
+        assertNotNull(path);
+        final var content = path.post().requestBody().content().get("application/xml");
+        assertNotNull(content);
+        final var schema = content.schema();
+        assertNotNull(schema);
+        final var xml = schema.xml();
+        assertNotNull(xml);
+        final var namespace = xml.namespace();
+        assertNotNull(namespace);
+        assertEquals("http://netconfcentral.org/ns/toaster", namespace);
+    }
+
+    /**
+     * Test that checks if namespace for actions is present.
+     */
+    @Test
+    public void testActionsNamespace() {
+        final var doc = generator.getApiDeclaration("action-types", null, uriInfo);
+        assertNotNull("Failed to find Datastore API", doc);
+        final var paths = doc.paths();
+        final var path = paths.get("/rests/operations/action-types:multi-container/inner-container/action");
+        assertNotNull(path);
+        final var content = path.post().requestBody().content().get("application/xml");
+        assertNotNull(content);
+        final var schema = content.schema();
+        assertNotNull(schema);
+        final var xml = schema.xml();
+        assertNotNull(xml);
+        final var namespace = xml.namespace();
+        assertNotNull(namespace);
+        assertEquals("urn:ietf:params:xml:ns:yang:test:action:types", namespace);
     }
 
     private static void verifyRequestRef(final Operation operation, final String expectedRef, final String nodeType) {
-        final JsonNode postContent;
+        final Map<String, MediaTypeObject> postContent;
         if (operation.requestBody() != null) {
-            postContent = operation.requestBody().path("content");
+            postContent = operation.requestBody().content();
         } else {
-            postContent = operation.responses().path("200").path("content");
+            postContent = operation.responses().get("200").content();
         }
         assertNotNull(postContent);
         final String postJsonRef;
         if (nodeType.equals(CONTAINER)) {
-            postJsonRef = postContent.path("application/json").path("schema").path("properties").elements().next()
-                .path("$ref").textValue();
+            postJsonRef = postContent.get("application/json").schema().properties().values().iterator().next().ref();
         } else {
-            postJsonRef = postContent.path("application/json").path("schema").path("properties").elements().next()
-                .path("items").path("$ref").textValue();
+            postJsonRef = postContent.get("application/json").schema().properties().values().iterator().next().items()
+                .ref();
         }
         assertNotNull(postJsonRef);
         assertEquals(expectedRef, postJsonRef);
-        final var postXmlRef = postContent.path("application/xml").path("schema").path("$ref");
+        final var postXmlRef = postContent.get("application/xml").schema().ref();
         assertNotNull(postXmlRef);
-        assertEquals(expectedRef, postXmlRef.textValue());
+        assertEquals(expectedRef, postXmlRef);
     }
 
     private static void verifyThatOthersNodeDoesNotHaveRequiredField(final List<String> expected,
@@ -440,14 +468,70 @@ public final class OpenApiGeneratorRFC8040Test {
         }
     }
 
-    private static void verifyRequiredField(final Schema rootContainer, final Set<String> expected) {
+    private static void verifyRequiredField(final Schema rootContainer, final List<String> expected) {
         assertNotNull(rootContainer);
         final var required = rootContainer.required();
         assertNotNull(required);
-        assertTrue(required.isArray());
-        final var actualContainerArray = StreamSupport.stream(required.spliterator(), false)
-            .map(JsonNode::textValue)
-            .collect(Collectors.toSet());
-        assertEquals(expected, actualContainerArray);
+        assertEquals(expected, required);
+    }
+
+    private static Set<String> extractSchemaRefFromPath(final Path path) {
+        if (path == null) {
+            return Set.of();
+        }
+        final var references = new HashSet<String>();
+        final var get = path.get();
+        if (get != null) {
+            references.addAll(schemaRefFromContent(get.responses().get("200").content()));
+        }
+        final var post = path.post();
+        if (post != null) {
+            references.addAll(schemaRefFromContent(post.requestBody().content()));
+        }
+        final var put = path.put();
+        if (put != null) {
+            references.addAll(schemaRefFromContent(put.requestBody().content()));
+        }
+        final var patch = path.patch();
+        if (patch != null) {
+            references.addAll(schemaRefFromContent(patch.requestBody().content()));
+        }
+        return references;
+    }
+
+    /**
+     * The schema node does not have 1 specific structure and the "$ref" child is not always the first child after
+     * schema. Possible schema structures include:
+     * <ul>
+     *   <li>schema/$ref/{reference}</li>
+     *   <li>schema/properties/{nodeName}/$ref/{reference}</li>
+     *   <li>schema/properties/{nodeName}/items/$ref/{reference}</li>
+     * </ul>
+     * @param content the element identified with key "content"
+     * @return the set of referenced schemas
+     */
+    private static Set<String> schemaRefFromContent(final Map<String, MediaTypeObject> content) {
+        final HashSet<String> refs = new HashSet<>();
+        content.values().forEach(mediaType -> {
+            final var schema = mediaType.schema();
+            final var props = mediaType.schema().properties();
+            final String ref;
+            if (props == null) {
+                // either there is no node with the key "properties", try to find immediate child of schema
+                ref = schema.ref();
+            } else if (props.values().iterator().next().items() == null) {
+                // or the "properties" is defined and under that we didn't find the "items" node
+                // try to get "$ref" as immediate child under properties
+                ref = props.values().iterator().next().ref();
+            } else {
+                // or the "items" node is defined, in which case we try to get the "$ref" from this node
+                ref = props.values().iterator().next().items().ref();
+            }
+
+            if (ref != null) {
+                refs.add(ref.replaceFirst(COMPONENTS_PREFIX, ""));
+            }
+        });
+        return refs;
     }
 }