NETCONF: list instance queries should hit the ops tree as well

getItems() operates on config and operation data, not just on the config
data. It is possible that there are lists which only live in an ops data
subtree.

Change-Id: I0206290dfcc4c1ebe91b4728f8e7a9230fc387cb
diff --git a/CMakeLists.txt b/CMakeLists.txt
index e990364..855799c 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -64,6 +64,8 @@
 # we don't need filename tracking, and we prefer to use header-only Boost
 add_definitions(-DBOOST_SPIRIT_X3_NO_FILESYSTEM)
 
+include_directories(${CMAKE_CURRENT_SOURCE_DIR}/src/)
+
 add_library(ast_values STATIC
     src/ast_values.cpp
     )
@@ -125,15 +127,6 @@
     )
 target_link_libraries(parser schemas utils ast_values)
 
-
-add_library(sysreposubscription STATIC
-    tests/mock/sysrepo_subscription.cpp
-    )
-
-target_link_libraries(sysreposubscription ${SYSREPO_LIBRARIES})
-link_directories(${SYSREPO_LIBRARY_DIRS})
-target_include_directories(sysreposubscription SYSTEM PRIVATE ${SYSREPO_INCLUDE_DIRS})
-
 add_executable(sysrepo-cli
     src/cli.cpp
     )
@@ -164,6 +157,11 @@
     target_link_libraries(DoctestIntegration doctest::doctest trompeloeil)
     target_compile_definitions(DoctestIntegration PUBLIC DOCTEST_CONFIG_SUPER_FAST_ASSERTS)
 
+    add_library(sysreposubscription STATIC
+        tests/mock/sysrepo_subscription.cpp
+        )
+    target_link_libraries(sysreposubscription PUBLIC datastoreaccess PRIVATE PkgConfig::SYSREPO)
+
     if (NOT SYSREPOCTL_EXECUTABLE)
         find_program(SYSREPOCTL_EXECUTABLE sysrepoctl)
     endif()
diff --git a/src/netconf_access.cpp b/src/netconf_access.cpp
index 777e94e..f744dc6 100644
--- a/src/netconf_access.cpp
+++ b/src/netconf_access.cpp
@@ -195,7 +195,7 @@
         actualList->insert(selectionLeaf);
     }
 
-    auto instances = m_session->getConfig(NC_DATASTORE_RUNNING, list->print_mem(LYD_XML, 0));
+    auto instances = m_session->get(list->print_mem(LYD_XML, 0));
 
     if (!instances) {
         return res;
diff --git a/tests/datastore_access.cpp b/tests/datastore_access.cpp
index 311b395..a6cadc3 100644
--- a/tests/datastore_access.cpp
+++ b/tests/datastore_access.cpp
@@ -17,6 +17,7 @@
 #else
 #error "Unknown backend"
 #endif
+#include "pretty_printers.hpp"
 #include "sysrepo_subscription.hpp"
 #include "utils.hpp"
 
@@ -27,6 +28,11 @@
     IMPLEMENT_MOCK3(write);
 };
 
+class MockDataSupplier : public trompeloeil::mock_interface<DataSupplier> {
+public:
+    IMPLEMENT_CONST_MOCK1(get_data);
+};
+
 TEST_CASE("setting/getting values")
 {
     trompeloeil::sequence seq1;
@@ -313,6 +319,23 @@
         REQUIRE(datastore.getItems("/example-schema:leafDecimal") == expected);
     }
 
+    SECTION("operational data")
+    {
+        MockDataSupplier mockOpsData;
+        OperationalDataSubscription opsDataSub("/example-schema:temperature", mockOpsData);
+        DatastoreAccess::Tree expected;
+        std::string xpath;
+        SECTION("temperature")
+        {
+            expected = {{"/example-schema:temperature", int32_t{22}}};
+            xpath = "/example-schema:temperature";
+        }
+
+        REQUIRE_CALL(mockOpsData, get_data(xpath)).RETURN(expected);
+        REQUIRE(datastore.getItems(xpath) == expected);
+    }
+
+
     waitForCompletionAndBitMore(seq1);
 }
 
diff --git a/tests/example-schema.yang b/tests/example-schema.yang
index 5ef67d5..ac7724a 100644
--- a/tests/example-schema.yang
+++ b/tests/example-schema.yang
@@ -209,4 +209,9 @@
             }
         }
     }
+
+    leaf temperature {
+        type int32;
+        config false;
+    }
 }
diff --git a/tests/mock/sysrepo_subscription.cpp b/tests/mock/sysrepo_subscription.cpp
index c50e19e..2c0d64b 100644
--- a/tests/mock/sysrepo_subscription.cpp
+++ b/tests/mock/sysrepo_subscription.cpp
@@ -45,6 +45,8 @@
 
 Recorder::~Recorder() = default;
 
+DataSupplier::~DataSupplier() = default;
+
 SysrepoSubscription::SysrepoSubscription(const std::string& moduleName, Recorder* rec)
     : m_connection(new sysrepo::Connection("netconf-cli-test-subscription"))
 {
@@ -58,3 +60,75 @@
 
     m_subscription->module_change_subscribe(moduleName.c_str(), m_callback);
 }
+
+struct leafDataToSysrepoVal {
+    leafDataToSysrepoVal (sysrepo::S_Val v, const std::string& xpath)
+        : v(v)
+        , xpath(xpath)
+    {
+    }
+
+    void operator()(const binary_& what)
+    {
+        v->set(xpath.c_str(), what.m_value.c_str(), SR_BINARY_T);
+    }
+
+    void operator()(const enum_& what)
+    {
+        v->set(xpath.c_str(), what.m_value.c_str(), SR_ENUM_T);
+    }
+
+    void operator()(const identityRef_& what)
+    {
+        v->set(xpath.c_str(), (what.m_prefix->m_name + what.m_value).c_str(), SR_IDENTITYREF_T);
+    }
+
+    void operator()(const std::string& what)
+    {
+        v->set(xpath.c_str(), what.c_str());
+    }
+
+    template <typename Type>
+    void operator()(const Type what)
+    {
+        v->set(xpath.c_str(), what);
+    }
+
+    void operator()([[maybe_unused]] const special_ what)
+    {
+        throw std::logic_error("Attempted to create a SR val from a special_ value");
+    }
+
+    ::sysrepo::S_Val v;
+    std::string xpath;
+};
+
+class OperationalDataCallback : public sysrepo::Callback {
+public:
+    OperationalDataCallback(const DataSupplier& dataSupplier)
+        : m_dataSupplier(dataSupplier)
+    {
+    }
+    int dp_get_items(const char *xpath, sysrepo::S_Vals_Holder vals, [[maybe_unused]] uint64_t request_id, [[maybe_unused]] const char *original_xpath, [[maybe_unused]] void *private_ctx) override
+    {
+        auto data = m_dataSupplier.get_data(xpath);
+        auto out = vals->allocate(data.size());
+        size_t i = 0;
+        for (auto it = data.cbegin(); it != data.cend(); ++it, ++i) {
+            std::string valuePath = it->first;
+            boost::apply_visitor(leafDataToSysrepoVal(out->val(i), valuePath), it->second);
+        }
+        return SR_ERR_OK;
+    }
+private:
+    const DataSupplier& m_dataSupplier;
+};
+
+OperationalDataSubscription::OperationalDataSubscription(const std::string& moduleName, const DataSupplier& dataSupplier)
+    : m_connection(new sysrepo::Connection("netconf-cli-test-subscription"))
+    , m_session(std::make_shared<sysrepo::Session>(m_connection))
+    , m_subscription(std::make_shared<sysrepo::Subscribe>(m_session))
+    , m_callback(std::make_shared<OperationalDataCallback>(dataSupplier))
+{
+    m_subscription->dp_get_items_subscribe(moduleName.c_str(), m_callback);
+}
diff --git a/tests/mock/sysrepo_subscription.hpp b/tests/mock/sysrepo_subscription.hpp
index 0102d1b..7683814 100644
--- a/tests/mock/sysrepo_subscription.hpp
+++ b/tests/mock/sysrepo_subscription.hpp
@@ -10,6 +10,7 @@
 
 #include <optional>
 #include <memory>
+#include "datastore_access.hpp"
 
 namespace sysrepo {
 class Callback;
@@ -25,6 +26,13 @@
     virtual void write(const std::string& xpath, const std::optional<std::string>& oldValue, const std::optional<std::string>& newValue) = 0;
 };
 
+class DataSupplier {
+public:
+    virtual ~DataSupplier();
+    virtual DatastoreAccess::Tree get_data(const std::string& xpath) const = 0;
+};
+
+
 class SysrepoSubscription {
 public:
     SysrepoSubscription(const std::string& moduleName, Recorder* rec = nullptr);
@@ -36,3 +44,14 @@
     std::shared_ptr<sysrepo::Callback> m_callback;
     std::shared_ptr<sysrepo::Subscribe> m_subscription;
 };
+
+class OperationalDataSubscription {
+public:
+    OperationalDataSubscription(const std::string& moduleName, const DataSupplier& dataSupplier);
+private:
+    std::shared_ptr<sysrepo::Connection> m_connection;
+    std::shared_ptr<sysrepo::Session> m_session;
+    std::shared_ptr<YangSchema> m_schema;
+    std::shared_ptr<sysrepo::Subscribe> m_subscription;
+    std::shared_ptr<sysrepo::Callback> m_callback;
+};