Add datastore support for YANG actions

Change-Id: I15b96f70ce89b7bbe3ac0fefb7b018374eeabd84
diff --git a/tests/datastore_access.cpp b/tests/datastore_access.cpp
index fb44018..2fac222 100644
--- a/tests/datastore_access.cpp
+++ b/tests/datastore_access.cpp
@@ -18,14 +18,14 @@
 using OnInvalidSchemaPathMove = sysrepo::sysrepo_exception;
 using OnInvalidRpcPath = sysrepo::sysrepo_exception;
 using OnKeyNotFound = void;
-using OnRPC = void;
+using OnExec = void;
 #elif defined(netconf_BACKEND)
 using OnInvalidSchemaPathCreate = std::runtime_error;
 using OnInvalidSchemaPathDelete = std::runtime_error;
 using OnInvalidSchemaPathMove = std::runtime_error;
 using OnInvalidRpcPath = std::runtime_error;
 using OnKeyNotFound = std::runtime_error;
-using OnRPC = void;
+using OnExec = void;
 #include "netconf_access.hpp"
 #include "netopeer_vars.hpp"
 #elif defined(yang_BACKEND)
@@ -37,7 +37,7 @@
 using OnInvalidSchemaPathMove = DatastoreException;
 using OnInvalidRpcPath = DatastoreException;
 using OnKeyNotFound = DatastoreException;
-using OnRPC = std::logic_error;
+using OnExec = std::logic_error;
 #else
 #error "Unknown backend"
 #endif
@@ -830,6 +830,17 @@
 }
 
 class RpcCb: public sysrepo::Callback {
+    int action(const char *xpath, [[maybe_unused]] const ::sysrepo::S_Vals input, ::sysrepo::S_Vals_Holder output, void* priv) override
+    {
+        auto schema = reinterpret_cast<YangSchema*>(priv);
+        if (schema->dataPathToSchemaPath(xpath) == "/example-schema:ports/shutdown") {
+            auto buf = output->allocate(1);
+            buf->val(0)->set(joinPaths(xpath, "success").c_str(), true);
+            return SR_ERR_OK;
+        }
+        throw std::runtime_error("unrecognized RPC");
+    }
+
     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;
@@ -876,19 +887,8 @@
     }
 };
 
-TEST_CASE("rpc") {
+TEST_CASE("rpc/action") {
     trompeloeil::sequence seq1;
-    auto srConn = std::make_shared<sysrepo::Connection>("netconf-cli-test-rpc");
-    auto srSession = std::make_shared<sysrepo::Session>(srConn);
-    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
     auto datastore = std::make_shared<SysrepoAccess>("netconf-cli-test", Datastore::Running);
@@ -902,88 +902,124 @@
 #error "Unknown backend"
 #endif
 
-    auto createTemporaryDatastore = [](const std::shared_ptr<DatastoreAccess>& datastore) {
-        return std::make_shared<YangAccess>(std::static_pointer_cast<YangSchema>(datastore->schema()));
-    };
+    auto srConn = std::make_shared<sysrepo::Connection>("netconf-cli-test-rpc");
+    auto srSession = std::make_shared<sysrepo::Session>(srConn);
+    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);
+    srSubscription->action_subscribe("/example-schema:ports/shutdown", cb, datastore->schema().get(), SR_SUBSCR_CTX_REUSE);
 
-    ProxyDatastore proxyDatastore(datastore, createTemporaryDatastore);
-
-    // ProxyDatastore cannot easily read DatastoreAccess::Tree, so we need to set the input via create/setLeaf/etc.
-    SECTION("valid")
+    SECTION("rpc")
     {
-        std::string rpc;
-        DatastoreAccess::Tree input, output;
+        auto createTemporaryDatastore = [](const std::shared_ptr<DatastoreAccess>& datastore) {
+            return std::make_shared<YangAccess>(std::static_pointer_cast<YangSchema>(datastore->schema()));
+        };
+        ProxyDatastore proxyDatastore(datastore, createTemporaryDatastore);
 
-        SECTION("noop") {
-            rpc = "/example-schema:noop";
-            proxyDatastore.initiateRpc(rpc);
+        // ProxyDatastore cannot easily read DatastoreAccess::Tree, so we need to set the input via create/setLeaf/etc.
+        SECTION("valid")
+        {
+            std::string rpc;
+            DatastoreAccess::Tree input, output;
+
+            SECTION("noop") {
+                rpc = "/example-schema:noop";
+                proxyDatastore.initiateRpc(rpc);
+            }
+
+            SECTION("small nuke") {
+                rpc = "/example-schema:launch-nukes";
+                input = {
+                    {"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
+            }
+
+            SECTION("small nuke") {
+                rpc = "/example-schema:launch-nukes";
+                input = {
+                    {"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}},
+                };
+            }
+
+            SECTION("with lists") {
+                rpc = "/example-schema:launch-nukes";
+                input = {
+                    {"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}},
+                    {"damaged-places", special_{SpecialValue::PresenceContainer}},
+                    {"damaged-places/targets[city='London']", special_{SpecialValue::List}},
+                    {"damaged-places/targets[city='London']/city", "London"s},
+                    {"damaged-places/targets[city='Berlin']", special_{SpecialValue::List}},
+                    {"damaged-places/targets[city='Berlin']/city", "Berlin"s},
+                };
+            }
+
+            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<OnExec>([&] {REQUIRE(datastore->executeRpc(rpc, input) == output);});
+            catching<OnExec>([&] {REQUIRE(proxyDatastore.executeRpc() == output);});
         }
 
-        SECTION("small nuke") {
-            rpc = "/example-schema:launch-nukes";
-            input = {
-                {"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
+        SECTION("non-existing RPC")
+        {
+            catching<OnInvalidRpcPath>([&] {datastore->executeRpc("/example-schema:non-existing", DatastoreAccess::Tree{});});
         }
-
-        SECTION("small nuke") {
-            rpc = "/example-schema:launch-nukes";
-            input = {
-                {"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}},
-            };
-        }
-
-        SECTION("with lists") {
-            rpc = "/example-schema:launch-nukes";
-            input = {
-                {"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}},
-                {"damaged-places", special_{SpecialValue::PresenceContainer}},
-                {"damaged-places/targets[city='London']", special_{SpecialValue::List}},
-                {"damaged-places/targets[city='London']/city", "London"s},
-                {"damaged-places/targets[city='Berlin']", special_{SpecialValue::List}},
-                {"damaged-places/targets[city='Berlin']/city", "Berlin"s},
-            };
-        }
-
-        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")
+    SECTION("action")
     {
-        catching<OnInvalidRpcPath>([&] {datastore->executeRpc("/example-schema:non-existing", DatastoreAccess::Tree{});});
+        std::string path;
+        DatastoreAccess::Tree input, output;
+
+        output = {
+#ifdef netconf_BACKEND
+            {"/example-schema:ports[name='A']", special_{SpecialValue::List}},
+            {"/example-schema:ports[name='A']/name", enum_{"A"}},
+#endif
+            {"success", true}
+        };
+        datastore->createItem("/example-schema:ports[name='A']");
+        datastore->commitChanges();
+        SECTION("shutdown") {
+            path = "/example-schema:ports[name='A']/shutdown";
+        }
+
+        catching<OnExec>([&] {REQUIRE(datastore->executeAction(path, input) == output);});
     }
 
     waitForCompletionAndBitMore(seq1);