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/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;
+};