Create tests for operational data in OpenAPI 46/106746/11
authorPeter Suna <[email protected]>
Wed, 28 Jun 2023 10:24:46 +0000 (12:24 +0200)
committerIvan Hrasko <[email protected]>
Thu, 3 Aug 2023 07:51:57 +0000 (07:51 +0000)
This commit adds unit tests to ensure the correct association
path reference to schemas in requests involving operational data.

JIRA: NETCONF-1061
Change-Id: I110370ccd2cc16f1b30e8b24152c74f5f7e50220
Signed-off-by: Peter Suna <[email protected]>
restconf/restconf-openapi/src/test/java/org/opendaylight/restconf/openapi/OperationalDataTest.java [new file with mode: 0644]
restconf/restconf-openapi/src/test/resources/operational/action-types.yang [new file with mode: 0644]
restconf/restconf-openapi/src/test/resources/operational/operational.yang [new file with mode: 0644]

diff --git a/restconf/restconf-openapi/src/test/java/org/opendaylight/restconf/openapi/OperationalDataTest.java b/restconf/restconf-openapi/src/test/java/org/opendaylight/restconf/openapi/OperationalDataTest.java
new file mode 100644 (file)
index 0000000..3cb88f5
--- /dev/null
@@ -0,0 +1,285 @@
+/*
+ * Copyright (c) 2023 PANTHEON.tech, s.r.o. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.restconf.openapi;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.Spliterator;
+import java.util.Spliterators;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
+import org.junit.Before;
+import org.junit.Test;
+import org.opendaylight.mdsal.dom.api.DOMMountPoint;
+import org.opendaylight.mdsal.dom.api.DOMMountPointService;
+import org.opendaylight.mdsal.dom.api.DOMSchemaService;
+import org.opendaylight.restconf.openapi.impl.MountPointOpenApiGeneratorRFC8040;
+import org.opendaylight.restconf.openapi.model.OpenApiObject;
+import org.opendaylight.restconf.openapi.model.Schema;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils;
+
+public class OperationalDataTest {
+    private static final String DATA_MP_URI = "/rests/data/nodes/node=123/yang-ext:mount";
+    private static final String OPERATIONS_MP_URI = "/rests/operations/nodes/node=123/yang-ext:mount";
+    private static final Set<String> EXPECTED_SCHEMAS = Set.of(
+        "action-types_config_container",
+        "action-types_config_container_TOP",
+        "action-types_config_list",
+        "action-types_config_list_TOP",
+        "action-types_config_multi-container",
+        "action-types_config_multi-container_TOP",
+        "action-types_container",
+        "action-types_container_TOP",
+        "action-types_container-action_input",
+        "action-types_container-action_input_TOP",
+        "action-types_container-action_output",
+        "action-types_container-action_output_TOP",
+        "action-types_list",
+        "action-types_list_TOP",
+        "action-types_list-action_input_TOP",
+        "action-types_list-action_output_TOP",
+        "action-types_list-action_output",
+        "action-types_list-action_input",
+        "action-types_multi-container",
+        "action-types_multi-container_TOP",
+        "action-types_multi-container_inner-container",
+        "action-types_multi-container_inner-container_TOP",
+        "action-types_multi-container_config_inner-container",
+        "action-types_multi-container_config_inner-container_TOP",
+        "operational_config_root",
+        "operational_config_root_TOP",
+        "operational_root",
+        "operational_root_TOP",
+        "operational_root_config_config-container",
+        "operational_root_config_config-container_TOP",
+        "operational_root_config-container",
+        "operational_root_config-container_TOP",
+        "operational_root_config-container_config-container-oper-list",
+        "operational_root_config-container_config-container-oper-list_TOP",
+        "operational_root_oper-container",
+        "operational_root_oper-container_TOP",
+        "operational_root_oper-container_config-container",
+        "operational_root_oper-container_config-container_TOP",
+        "operational_root_oper-container_oper-container-list",
+        "operational_root_oper-container_oper-container-list_TOP");
+    private static final Set<String> EXPECTED_PATHS = Set.of(
+        OPERATIONS_MP_URI + "/action-types:list={name}/list-action",
+        OPERATIONS_MP_URI + "/action-types:container/container-action",
+        OPERATIONS_MP_URI + "/action-types:multi-container/inner-container/action",
+        OPERATIONS_MP_URI,
+        DATA_MP_URI + "/action-types:list={name}",
+        DATA_MP_URI + "/operational:root",
+        DATA_MP_URI + "/operational:root/oper-container/config-container",
+        DATA_MP_URI + "/operational:root/oper-container/oper-container-list={oper-container-list-leaf}",
+        DATA_MP_URI + "/action-types:multi-container",
+        DATA_MP_URI + "/action-types:multi-container/inner-container",
+        DATA_MP_URI + "/operational:root/oper-container",
+        DATA_MP_URI + "/action-types:container",
+        DATA_MP_URI + "/operational:root/config-container/config-container-oper-list={oper-container-list-leaf}",
+        DATA_MP_URI + "/operational:root/config-container",
+        DATA_MP_URI);
+    private static final String HTTP_URL = "http://localhost/path";
+    private static final YangInstanceIdentifier INSTANCE_ID = YangInstanceIdentifier.builder()
+        .node(QName.create("", "nodes"))
+        .node(QName.create("", "node"))
+        .nodeWithKey(QName.create("", "node"), QName.create("", "id"), "123").build();
+
+    private OpenApiObject mountPointApi;
+    private Map<String, Schema> schemas;
+
+    @Before
+    public void before() throws Exception {
+        final var schemaService = mock(DOMSchemaService.class);
+        final var context = YangParserTestUtils.parseYangResourceDirectory("/operational");
+        when(schemaService.getGlobalContext()).thenReturn(context);
+        final var mountPoint = mock(DOMMountPoint.class);
+        when(mountPoint.getService(DOMSchemaService.class)).thenReturn(Optional.of(schemaService));
+
+        final var service = mock(DOMMountPointService.class);
+        when(service.getMountPoint(INSTANCE_ID)).thenReturn(Optional.of(mountPoint));
+        final var openApi = new MountPointOpenApiGeneratorRFC8040(schemaService, service).getMountPointOpenApi();
+        openApi.onMountPointCreated(INSTANCE_ID);
+        final var mockInfo = DocGenTestHelper.createMockUriInfo(HTTP_URL);
+        mountPointApi = openApi.getMountPointApi(mockInfo, 1L, null);
+        assertNotNull("Failed to find Datastore API", mountPointApi);
+        schemas = mountPointApi.components().schemas();
+    }
+
+    @Test
+    public void testOperationalPath() {
+        final var paths = mountPointApi.paths();
+        assertEquals(EXPECTED_PATHS, paths.keySet());
+        for (final var path : paths.values()) {
+            if (path.get() != null) {
+                final var responses = path.get().responses();
+                final var response = responses.elements().next();
+                final var content = response.get("content");
+                // In case of 200 no content
+                if (content != null) {
+                    verifyOperationHaveCorrectReference(content.get("application/xml"));
+                    verifyOperationHaveCorrectReference(content.get("application/json"));
+                }
+            }
+            if (path.put() != null) {
+                final var responses = path.put().requestBody();
+                final var content = responses.get("content");
+                verifyOperationHaveCorrectReference(content.get("application/xml"));
+                verifyOperationHaveCorrectReference(content.get("application/json"));
+            }
+            if (path.post() != null) {
+                final var responses = path.post().requestBody();
+                final var content = responses.get("content");
+                verifyOperationHaveCorrectReference(content.get("application/xml"));
+                verifyOperationHaveCorrectReference(content.get("application/json"));
+            }
+            if (path.patch() != null) {
+                final var responses = path.patch().requestBody();
+                final var content = responses.get("content");
+                verifyOperationHaveCorrectReference(content.get("application/yang-data+xml"));
+                verifyOperationHaveCorrectReference(content.get("application/yang-data+json"));
+            }
+        }
+    }
+
+    @Test
+    public void testOperationalSchema() {
+        assertEquals(EXPECTED_SCHEMAS, schemas.keySet());
+    }
+
+    @Test
+    public void testOperationalConfigRootSchemaProperties() {
+        final var configRoot = schemas.get("operational_config_root");
+        assertNotNull(configRoot);
+        final var actualProperties = getSetOfProperties(configRoot);
+        assertEquals(Set.of("leaf-config", "leaf-first-case", "config-container"), actualProperties);
+    }
+
+    @Test
+    public void testOperationalConfigContOperListSchemaProperties() {
+        final var configContOperList = schemas.get(
+            "operational_root_config-container_config-container-oper-list");
+        assertNotNull(configContOperList);
+        final var actualProperties = getSetOfProperties(configContOperList);
+        assertEquals(Set.of("oper-container-list-leaf"), actualProperties);
+    }
+
+    @Test
+    public void testOperationalContListSchemaProperties() {
+        final var operContList = schemas.get("operational_root_oper-container_oper-container-list");
+        assertNotNull(operContList);
+        final var actualProperties = getSetOfProperties(operContList);
+        assertEquals(Set.of("oper-container-list-leaf"), actualProperties);
+    }
+
+    @Test
+    public void testOperationalConConfigContSchemaProperties() {
+        final var operConConfigCont = schemas.get("operational_root_oper-container_config-container");
+        assertNotNull(operConConfigCont);
+        final var actualProperties = getSetOfProperties(operConConfigCont);
+        assertEquals(Set.of("config-container-config-leaf", "opconfig-container-oper-leaf"), actualProperties);
+    }
+
+    @Test
+    public void testOperationalConfigContSchemaProperties() {
+        final var configCont = schemas.get("operational_root_config_config-container");
+        assertNotNull(configCont);
+        final var actualProperties = getSetOfProperties(configCont);
+        assertEquals(Set.of("config-container-config-leaf", "leaf-second-case", "oper-leaf-second-case"),
+            actualProperties);
+    }
+
+    @Test
+    public void testOperationalContSchemaProperties() {
+        final var operCont = schemas.get("operational_root_oper-container");
+        assertNotNull(operCont);
+        final var actualProperties = getSetOfProperties(operCont);
+        assertEquals(Set.of("config-container", "oper-container-list", "leaf-first-case", "oper-leaf-first-case",
+            "oper-container-config-leaf-list"), actualProperties);
+    }
+
+    @Test
+    public void testListActionSchemaProperties() {
+        final var configRoot = schemas.get("action-types_config_list");
+        assertNotNull(configRoot);
+        final var actualProperties = getSetOfProperties(configRoot);
+        assertEquals(Set.of("name"), actualProperties);
+    }
+
+    @Test
+    public void testListActionInputSchemaProperties() {
+        final var configRoot = schemas.get("action-types_list-action_input");
+        assertNotNull(configRoot);
+        final var actualProperties = getSetOfProperties(configRoot);
+        assertEquals(Set.of("la-input"), actualProperties);
+    }
+
+    @Test
+    public void testListActionOutputSchemaProperties() {
+        final var configRoot = schemas.get("action-types_list-action_output");
+        assertNotNull(configRoot);
+        final var actualProperties = getSetOfProperties(configRoot);
+        assertEquals(Set.of("la-output"), actualProperties);
+    }
+
+    @Test
+    public void testContainerActionSchemaProperties() {
+        final var configRoot = schemas.get("action-types_config_container");
+        assertNotNull(configRoot);
+        final var actualProperties = getSetOfProperties(configRoot);
+        assertEquals(Set.of(), actualProperties);
+    }
+
+    @Test
+    public void testContainerActionInputSchemaProperties() {
+        final var configRoot = schemas.get("action-types_container-action_input");
+        assertNotNull(configRoot);
+        final var actualProperties = getSetOfProperties(configRoot);
+        assertEquals(Set.of("ca-input"), actualProperties);
+    }
+
+    @Test
+    public void testContainerActionOutputSchemaProperties() {
+        final var configRoot = schemas.get("action-types_container-action_output");
+        assertNotNull(configRoot);
+        final var actualProperties = getSetOfProperties(configRoot);
+        assertEquals(Set.of("ca-output"), actualProperties);
+    }
+
+    private static void verifyOperationHaveCorrectReference(final JsonNode jsonNode) {
+        final var schema = jsonNode.get("schema");
+        final var ref = schema.get("$ref");
+        // In case of a POST RPC with a direct input body and no reference value
+        if (ref != null) {
+            final var refValue = ref.textValue();
+            final var schemaElement = refValue.substring(refValue.lastIndexOf("/") + 1);
+            assertTrue("Reference [" + refValue + "] not found in EXPECTED Schemas",
+                EXPECTED_SCHEMAS.contains(schemaElement));
+        } else {
+            final var type = schema.get("type");
+            assertNotNull(type);
+            assertEquals("object", type.asText());
+        }
+    }
+
+    private static Set<String> getSetOfProperties(final Schema schema) {
+        final var fieldNames = schema.properties().fieldNames();
+        final var fieldNamesSpliterator = Spliterators.spliteratorUnknownSize(fieldNames, Spliterator.ORDERED);
+        return StreamSupport.stream(fieldNamesSpliterator, false)
+            .collect(Collectors.toSet());
+    }
+}
diff --git a/restconf/restconf-openapi/src/test/resources/operational/action-types.yang b/restconf/restconf-openapi/src/test/resources/operational/action-types.yang
new file mode 100644 (file)
index 0000000..4d6d316
--- /dev/null
@@ -0,0 +1,50 @@
+module action-types {
+  yang-version 1.1;
+  namespace "urn:ietf:params:xml:ns:yang:test:action:types";
+  prefix "act-tp";
+
+  list list {
+    key name;
+    leaf name {
+      type string;
+    }
+    action list-action {
+      input {
+        leaf la-input {
+          type string;
+          mandatory true;
+        }
+      }
+      output {
+        leaf la-output {
+          type string;
+          mandatory true;
+        }
+      }
+    }
+  }
+
+  container container {
+    action container-action {
+      input {
+        leaf ca-input {
+          type string;
+          mandatory true;
+        }
+      }
+      output {
+        leaf ca-output {
+          type string;
+          mandatory true;
+        }
+      }
+    }
+  }
+
+  container multi-container {
+    container inner-container {
+      action action {
+      }
+    }
+  }
+}
diff --git a/restconf/restconf-openapi/src/test/resources/operational/operational.yang b/restconf/restconf-openapi/src/test/resources/operational/operational.yang
new file mode 100644 (file)
index 0000000..f4f5b1d
--- /dev/null
@@ -0,0 +1,106 @@
+module operational {
+  namespace "urn:opendaylight:oper";
+  prefix "oper";
+
+  container root {
+    leaf leaf-config {
+      type string;
+    }
+    leaf leaf-oper {
+      mandatory true;
+      type string;
+      config false;
+    }
+    choice first-choice {
+      case first-case {
+        leaf leaf-first-case {
+          config false;
+          type string;
+        }
+      }
+      case second-case {
+        leaf leaf-second-case {
+          type string;
+        }
+      }
+      default first-case;
+    }
+
+    container oper-container {
+      config false;
+      container config-container {
+        leaf config-container-config-leaf {
+          mandatory true;
+          type string;
+        }
+        leaf opconfig-container-oper-leaf {
+          config false;
+          type string;
+        }
+      }
+      list oper-container-list {
+        key oper-container-list-leaf;
+        leaf oper-container-list-leaf {
+          type string;
+        }
+      }
+      leaf-list oper-container-config-leaf-list {
+        type string;
+      }
+      choice first-choice {
+        case first-case {
+          leaf leaf-first-case {
+            config false;
+            type string;
+          }
+          leaf oper-leaf-first-case {
+            type string;
+          }
+        }
+        case second-case {
+          leaf leaf-second-case {
+            type string;
+          }
+        }
+      }
+    }
+    container config-container {
+      leaf config-container-oper-leaf {
+        config false;
+        type string;
+      }
+      leaf config-container-config-leaf {
+        type string;
+      }
+      list config-container-oper-list {
+        config false;
+        key oper-container-list-leaf;
+        leaf oper-container-list-leaf {
+          type string;
+        }
+      }
+      leaf-list config-container-oper-leaf-list {
+        config false;
+        type string;
+      }
+      choice first-choice {
+        case first-case {
+          leaf leaf-first-case {
+            config false;
+            type string;
+          }
+        }
+        case second-case {
+          leaf leaf-second-case {
+            type string;
+          }
+          leaf oper-leaf-second-case {
+            config false;
+            type string;
+          }
+        }
+        default second-case;
+      }
+    }
+  }
+}