Introduce restconf.server.{api,spi,mdsal}
[netconf.git] / restconf / restconf-nb / src / test / java / org / opendaylight / restconf / server / mdsal / streams / dtcl / CreateNotificationStreamRpcTest.java
@@ -5,7 +5,7 @@
  * 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.nb.rfc8040.streams;
+package org.opendaylight.restconf.server.mdsal.streams.dtcl;
 
 import static org.hamcrest.CoreMatchers.startsWith;
 import static org.hamcrest.MatcherAssert.assertThat;
@@ -20,6 +20,7 @@ import static org.mockito.Mockito.doReturn;
 import com.google.common.collect.ImmutableClassToInstanceMap;
 import java.net.URI;
 import java.util.UUID;
+import org.eclipse.jdt.annotation.Nullable;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
@@ -32,12 +33,13 @@ import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
 import org.opendaylight.mdsal.dom.api.DOMDataBroker;
 import org.opendaylight.mdsal.dom.api.DOMDataTreeChangeService;
 import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteTransaction;
-import org.opendaylight.mdsal.dom.api.DOMMountPointService;
-import org.opendaylight.mdsal.dom.api.DOMNotificationService;
 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
 import org.opendaylight.restconf.nb.rfc8040.databind.DatabindContext;
 import org.opendaylight.restconf.nb.rfc8040.databind.DatabindProvider;
+import org.opendaylight.restconf.server.mdsal.MdsalRestconfStreamRegistry;
+import org.opendaylight.restconf.server.spi.OperationInput;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.restconf.monitoring.rev170126.restconf.state.streams.stream.Access;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.remote.rev140114.CreateDataChangeEventSubscription;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.remote.rev140114.CreateDataChangeEventSubscriptionOutput;
 import org.opendaylight.yangtools.yang.common.ErrorTag;
 import org.opendaylight.yangtools.yang.common.ErrorType;
@@ -50,55 +52,51 @@ import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
 import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
-import org.opendaylight.yangtools.yang.model.api.ContainerLike;
 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
-import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
-import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
+import org.opendaylight.yangtools.yang.model.api.stmt.RpcEffectiveStatement;
+import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
 import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils;
 
 @ExtendWith(MockitoExtension.class)
-class ListenersBrokerTest {
+class CreateNotificationStreamRpcTest {
     private static final EffectiveModelContext SCHEMA_CTX = YangParserTestUtils.parseYangResourceDirectory("/streams");
-    private static final URI BASE_URI = URI.create("baseURI");
+    private static final URI RESTCONF_URI = URI.create("/rests");
+    private static final YangInstanceIdentifier TOASTER = YangInstanceIdentifier.of(
+        QName.create("http://netconfcentral.org/ns/toaster", "2009-11-20", "toaster"));
 
     @Mock
     private DOMDataBroker dataBroker;
     @Mock
     private DOMDataTreeChangeService treeChange;
     @Mock
-    private DOMMountPointService mountPointService;
-    @Mock
-    private DOMNotificationService notificationService;
-    @Mock
     private DOMDataTreeWriteTransaction tx;
     @Captor
     private ArgumentCaptor<YangInstanceIdentifier> pathCaptor;
     @Captor
     private ArgumentCaptor<NormalizedNode> dataCaptor;
 
-    private ListenersBroker listenersBroker;
     private DatabindProvider databindProvider;
 
+    private CreateDataChangeEventSubscriptionRpc rpc;
+
     @BeforeEach
     public void before() {
-        listenersBroker = new ListenersBroker.ServerSentEvents(dataBroker, notificationService, mountPointService);
         databindProvider = () -> DatabindContext.ofModel(SCHEMA_CTX);
-    }
 
-    @Test
-    void createStreamTest() {
         doReturn(ImmutableClassToInstanceMap.of(DOMDataTreeChangeService.class, treeChange))
             .when(dataBroker).getExtensions();
+        rpc = new CreateDataChangeEventSubscriptionRpc(new MdsalRestconfStreamRegistry(dataBroker), databindProvider,
+            dataBroker);
+    }
 
+    @Test
+    void createStreamTest() {
         doReturn(tx).when(dataBroker).newWriteOnlyTransaction();
         doNothing().when(tx).put(eq(LogicalDatastoreType.OPERATIONAL), pathCaptor.capture(), dataCaptor.capture());
         doReturn(CommitInfo.emptyFluentFuture()).when(tx).commit();
 
-        final var output = assertInstanceOf(ContainerNode.class,
-            listenersBroker.createDataChangeNotifiStream(databindProvider, BASE_URI,
-                prepareDomPayload("create-data-change-event-subscription", "toaster", "path"), SCHEMA_CTX)
-            .getOrThrow()
-            .orElse(null));
+        final var output = assertInstanceOf(ContainerNode.class, rpc.invoke(RESTCONF_URI, createInput("path", TOASTER))
+            .getOrThrow().output());
 
         assertEquals(new NodeIdentifier(CreateDataChangeEventSubscriptionOutput.QNAME), output.name());
         assertEquals(1, output.size());
@@ -133,12 +131,12 @@ class ListenersBrokerTest {
                 .withChild(Builders.mapEntryBuilder()
                     .withNodeIdentifier(NodeIdentifierWithPredicates.of(Access.QNAME, rcEncoding, "json"))
                     .withChild(ImmutableNodes.leafNode(rcEncoding, "json"))
-                    .withChild(ImmutableNodes.leafNode(rcLocation, "rests/streams/json/" + name))
+                    .withChild(ImmutableNodes.leafNode(rcLocation, "/rests/streams/json/" + name))
                     .build())
                 .withChild(Builders.mapEntryBuilder()
                     .withNodeIdentifier(NodeIdentifierWithPredicates.of(Access.QNAME, rcEncoding, "xml"))
                     .withChild(ImmutableNodes.leafNode(rcEncoding, "xml"))
-                    .withChild(ImmutableNodes.leafNode(rcLocation, "rests/streams/xml/" + name))
+                    .withChild(ImmutableNodes.leafNode(rcLocation, "/rests/streams/xml/" + name))
                     .build())
                 .build())
             .build().prettyTree().toString(), dataCaptor.getValue().prettyTree().toString());
@@ -146,58 +144,39 @@ class ListenersBrokerTest {
 
     @Test
     void createStreamWrongValueTest() {
-        final var payload = prepareDomPayload("create-data-change-event-subscription", "String value", "path");
-        final var errors = assertThrows(RestconfDocumentedException.class,
-            () -> listenersBroker.createDataChangeNotifiStream(databindProvider, BASE_URI, payload, SCHEMA_CTX))
-            .getErrors();
-        assertEquals(1, errors.size());
-        final var error = errors.get(0);
-        assertEquals(ErrorType.APPLICATION, error.getErrorType());
-        assertEquals(ErrorTag.OPERATION_FAILED, error.getErrorTag());
-        assertEquals("Instance identifier was not normalized correctly", error.getErrorMessage());
+        final var payload = createInput("path", "String value");
+        final var ex = assertThrows(IllegalArgumentException.class, () -> rpc.invoke(RESTCONF_URI, payload));
+        assertEquals("""
+            Bad child leafNode (urn:opendaylight:params:xml:ns:yang:controller:md:sal:remote@2014-01-14)path = \
+            "String value"\
+            """, ex.getMessage());
     }
 
     @Test
     void createStreamWrongInputRpcTest() {
-        final var payload = prepareDomPayload("create-data-change-event-subscription2", "toaster", "path2");
-        final var errors = assertThrows(RestconfDocumentedException.class,
-            () -> listenersBroker.createDataChangeNotifiStream(databindProvider, BASE_URI, payload, SCHEMA_CTX))
-            .getErrors();
+        final var future = rpc.invoke(RESTCONF_URI, createInput(null, null));
+        final var errors = assertThrows(RestconfDocumentedException.class, future::getOrThrow).getErrors();
         assertEquals(1, errors.size());
         final var error = errors.get(0);
         assertEquals(ErrorType.APPLICATION, error.getErrorType());
-        assertEquals(ErrorTag.OPERATION_FAILED, error.getErrorTag());
-        assertEquals("Instance identifier was not normalized correctly", error.getErrorMessage());
+        assertEquals(ErrorTag.MISSING_ELEMENT, error.getErrorTag());
+        assertEquals("missing path", error.getErrorMessage());
     }
 
-    private static ContainerNode prepareDomPayload(final String rpcName, final String toasterValue,
-            final String inputOutputName) {
-        final var rpcModule = SCHEMA_CTX.findModules("sal-remote").iterator().next();
-        final QName rpcQName = QName.create(rpcModule.getQNameModule(), rpcName);
-
-        ContainerLike containerSchemaNode = null;
-        for (final RpcDefinition rpc : rpcModule.getRpcs()) {
-            if (rpcQName.isEqualWithoutRevision(rpc.getQName())) {
-                containerSchemaNode = rpc.getInput();
-                break;
-            }
+    private OperationInput createInput(final @Nullable String leafName, final Object leafValue) {
+        final var stack = SchemaInferenceStack.of(SCHEMA_CTX);
+        final var rpcStmt = assertInstanceOf(RpcEffectiveStatement.class,
+            stack.enterSchemaTree(CreateDataChangeEventSubscription.QNAME));
+        final var inference = stack.toInference();
+
+        final var builder = Builders.containerBuilder()
+            .withNodeIdentifier(new NodeIdentifier(rpcStmt.input().argument()));
+        if (leafName != null) {
+            final var lfQName = QName.create(rpcStmt.argument(), leafName);
+            stack.enterDataTree(rpcStmt.input().argument());
+            stack.enterDataTree(lfQName);
+            builder.withChild(ImmutableNodes.leafNode(lfQName, leafValue));
         }
-        assertNotNull(containerSchemaNode);
-
-        final QName lfQName = QName.create(rpcModule.getQNameModule(), inputOutputName);
-        assertInstanceOf(LeafSchemaNode.class, containerSchemaNode.dataChildByName(lfQName));
-
-        final Object o;
-        if ("toaster".equals(toasterValue)) {
-            final QName rpcQname = QName.create("http://netconfcentral.org/ns/toaster", "2009-11-20", toasterValue);
-            o = YangInstanceIdentifier.of(rpcQname);
-        } else {
-            o = toasterValue;
-        }
-
-        return Builders.containerBuilder()
-            .withNodeIdentifier(new NodeIdentifier(containerSchemaNode.getQName()))
-            .withChild(ImmutableNodes.leafNode(lfQName, o))
-            .build();
+        return new OperationInput(databindProvider.currentContext(), inference, builder.build());
     }
 }