Add support for executing RPCs

Creating a temporary YangAccess for RPC input means I need to somehow
give the right libyang schemas. For that reason I supply a callable
which is able to fetch the schema and create a YangAccess instance for
ProxyDatastore.

The ProxyDatastore class now has a simple mechanism for deciding whether
to use the normal datastore and the temporary based on a path prefix.

Change-Id: Ib455f53237598bc2620161a44fb89c48ddfeb6e3
diff --git a/tests/datastore_access.cpp b/tests/datastore_access.cpp
index b45f027..fb44018 100644
--- a/tests/datastore_access.cpp
+++ b/tests/datastore_access.cpp
@@ -8,6 +8,8 @@
 
 #include "trompeloeil_doctest.hpp"
 #include <sysrepo-cpp/Session.hpp>
+#include "yang_schema.hpp"
+#include "proxy_datastore.hpp"
 
 #ifdef sysrepo_BACKEND
 #include "sysrepo_access.hpp"
@@ -831,9 +833,11 @@
     int rpc(const char *xpath, const ::sysrepo::S_Vals input, ::sysrepo::S_Vals_Holder output, void *) override
     {
         const auto nukes = "/example-schema:launch-nukes"s;
-        if (xpath == "/example-schema:noop"s) {
+        if (xpath == "/example-schema:noop"s || xpath == "/example-schema:fire"s) {
             return SR_ERR_OK;
-        } else if (xpath == nukes) {
+        }
+
+        if (xpath == nukes) {
             uint64_t kilotons = 0;
             bool hasCities = false;
             for (size_t i = 0; i < input->val_cnt(); ++i) {
@@ -879,21 +883,32 @@
     auto srSubscription = std::make_shared<sysrepo::Subscribe>(srSession);
     auto cb = std::make_shared<RpcCb>();
     sysrepo::Logs{}.set_stderr(SR_LL_INF);
+    auto doNothingCb = std::make_shared<sysrepo::Callback>();
+    srSubscription->module_change_subscribe("example-schema", doNothingCb, nullptr, SR_SUBSCR_CTX_REUSE);
+    // careful here, sysrepo insists on module_change CBs being registered before RPC CBs, otherwise there's a memleak
     srSubscription->rpc_subscribe("/example-schema:noop", cb, nullptr, SR_SUBSCR_CTX_REUSE);
     srSubscription->rpc_subscribe("/example-schema:launch-nukes", cb, nullptr, SR_SUBSCR_CTX_REUSE);
+    srSubscription->rpc_subscribe("/example-schema:fire", cb, nullptr, SR_SUBSCR_CTX_REUSE);
 
 #ifdef sysrepo_BACKEND
-    SysrepoAccess datastore("netconf-cli-test", Datastore::Running);
+    auto datastore = std::make_shared<SysrepoAccess>("netconf-cli-test", Datastore::Running);
 #elif defined(netconf_BACKEND)
-    NetconfAccess datastore(NETOPEER_SOCKET_PATH);
+    auto datastore = std::make_shared<NetconfAccess>(NETOPEER_SOCKET_PATH);
 #elif defined(yang_BACKEND)
-    YangAccess datastore;
-    datastore.addSchemaDir(schemaDir);
-    datastore.addSchemaFile(exampleSchemaFile);
+    auto datastore = std::make_shared<YangAccess>();
+    datastore->addSchemaDir(schemaDir);
+    datastore->addSchemaFile(exampleSchemaFile);
 #else
 #error "Unknown backend"
 #endif
 
+    auto createTemporaryDatastore = [](const std::shared_ptr<DatastoreAccess>& datastore) {
+        return std::make_shared<YangAccess>(std::static_pointer_cast<YangSchema>(datastore->schema()));
+    };
+
+    ProxyDatastore proxyDatastore(datastore, createTemporaryDatastore);
+
+    // ProxyDatastore cannot easily read DatastoreAccess::Tree, so we need to set the input via create/setLeaf/etc.
     SECTION("valid")
     {
         std::string rpc;
@@ -901,6 +916,7 @@
 
         SECTION("noop") {
             rpc = "/example-schema:noop";
+            proxyDatastore.initiateRpc(rpc);
         }
 
         SECTION("small nuke") {
@@ -909,6 +925,8 @@
                 {"description", "dummy"s},
                 {"payload/kilotons", uint64_t{333'666}},
             };
+            proxyDatastore.initiateRpc(rpc);
+            proxyDatastore.setLeaf("/example-schema:launch-nukes/example-schema:payload/example-schema:kilotons", uint64_t{333'666});
             // no data are returned
         }
 
@@ -918,6 +936,9 @@
                 {"description", "dummy"s},
                 {"payload/kilotons", uint64_t{4}},
             };
+            proxyDatastore.initiateRpc(rpc);
+            proxyDatastore.setLeaf("/example-schema:launch-nukes/example-schema:payload/example-schema:kilotons", uint64_t{4});
+
             output = {
                 {"blast-radius", uint32_t{33'666}},
                 {"actual-yield", uint64_t{5}},
@@ -930,6 +951,9 @@
                 {"payload/kilotons", uint64_t{6}},
                 {"cities/targets[city='Prague']/city", "Prague"s},
             };
+            proxyDatastore.initiateRpc(rpc);
+            proxyDatastore.setLeaf("/example-schema:launch-nukes/example-schema:payload/example-schema:kilotons", uint64_t{6});
+            proxyDatastore.createItem("/example-schema:launch-nukes/example-schema:cities/example-schema:targets[city='Prague']");
             output = {
                 {"blast-radius", uint32_t{33'666}},
                 {"actual-yield", uint64_t{7}},
@@ -941,12 +965,25 @@
             };
         }
 
-        catching<OnRPC>([&] {REQUIRE(datastore.executeRpc(rpc, input) == output);});
+        SECTION("with leafref") {
+            datastore->createItem("/example-schema:person[name='Colton']");
+            datastore->commitChanges();
+
+            rpc = "/example-schema:fire";
+            input = {
+                {"whom", "Colton"s}
+            };
+            proxyDatastore.initiateRpc(rpc);
+            proxyDatastore.setLeaf("/example-schema:fire/example-schema:whom", "Colton"s);
+        }
+
+        catching<OnRPC>([&] {REQUIRE(datastore->executeRpc(rpc, input) == output);});
+        catching<OnRPC>([&] {REQUIRE(proxyDatastore.executeRpc() == output);});
     }
 
     SECTION("non-existing RPC")
     {
-        catching<OnInvalidRpcPath>([&] {datastore.executeRpc("/example-schema:non-existing", DatastoreAccess::Tree{});});
+        catching<OnInvalidRpcPath>([&] {datastore->executeRpc("/example-schema:non-existing", DatastoreAccess::Tree{});});
     }
 
     waitForCompletionAndBitMore(seq1);