Add tests for SysrepoAccess

Change-Id: I5b112a706b1b58401d520057c6ab2b2c6d33fedb
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 702ccb8..23fff35 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -94,6 +94,15 @@
     )
 target_link_libraries(parser schemas)
 
+
+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(netconf-cli
     src/main.cpp
     )
@@ -130,6 +139,16 @@
     target_include_directories(TestCatchIntegration PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/tests/ ${CMAKE_CURRENT_SOURCE_DIR}/src/)
     target_link_libraries(TestCatchIntegration spdlog::spdlog)
 
+    if (NOT SYSREPOCTL_EXECUTABLE)
+        find_program(SYSREPOCTL_EXECUTABLE sysrepoctl)
+    endif()
+    if (NOT SYSREPOCTL_EXECUTABLE)
+        message(FATAL_ERROR "Unable to find sysrepoctl, set SYSREPOCTL_EXECUTABLE manually.")
+    endif()
+
+    configure_file("${PROJECT_SOURCE_DIR}/sysrepo_vars.hpp.in" "${PROJECT_BINARY_DIR}/sysrepo_vars.hpp" @ONLY)
+    configure_file("${PROJECT_SOURCE_DIR}/example-schema.yang" "${PROJECT_BINARY_DIR}/example-schema.yang" COPYONLY)
+
     function(cli_test fname)
         add_executable(test_${fname}
             tests/${fname}.cpp
@@ -147,6 +166,9 @@
     cli_test(leaf_editing)
     cli_test(yang)
     target_link_libraries(test_yang yangschema)
+    cli_test(sysrepo)
+    target_link_libraries(test_sysrepo sysreposubscription sysrepoaccess yangschema parser)
+    target_include_directories(test_sysrepo PRIVATE ${PROJECT_SOURCE_DIR}/tests/mock)
 
 endif()
 
diff --git a/example-schema.yang b/example-schema.yang
new file mode 100644
index 0000000..4074172
--- /dev/null
+++ b/example-schema.yang
@@ -0,0 +1,31 @@
+module example-schema {
+    prefix aha;
+    namespace "http://example.com";
+
+    leaf leafInt {
+        type int32;
+    }
+
+    leaf leafString {
+        type string;
+    }
+
+    leaf leafEnum {
+        type enumeration {
+            enum lol;
+            enum data;
+            enum coze;
+        }
+    }
+
+    leaf leafDecimal {
+        type decimal64 {
+            fraction-digits 9;
+        }
+    }
+
+    container pContainer {
+        presence true;
+    }
+
+}
diff --git a/sysrepo_vars.hpp.in b/sysrepo_vars.hpp.in
new file mode 100644
index 0000000..16bc035
--- /dev/null
+++ b/sysrepo_vars.hpp.in
@@ -0,0 +1,10 @@
+/*
+ * Copyright (C) 2018 CESNET, https://photonics.cesnet.cz/
+ * Copyright (C) 2018 FIT CVUT, https://fit.cvut.cz/
+ *
+ * Written by Václav Kubernát <kubervac@fit.cvut.cz>
+ *
+*/
+
+#define SYSREPOCTLEXECUTABLE "@SYSREPOCTL_EXECUTABLE@"
+#define EXAMPLE_SCHEMA_LOCATION "@CMAKE_CURRENT_SOURCE_DIR@/example-schema.yang"
diff --git a/tests/mock/sysrepo_subscription.cpp b/tests/mock/sysrepo_subscription.cpp
new file mode 100644
index 0000000..801ec1c
--- /dev/null
+++ b/tests/mock/sysrepo_subscription.cpp
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2018 CESNET, https://photonics.cesnet.cz/
+ * Copyright (C) 2018 FIT CVUT, https://fit.cvut.cz/
+ *
+ * Written by Václav Kubernát <kubervac@fit.cvut.cz>
+ *
+*/
+
+#include <sysrepo-cpp/Session.hpp>
+#include "sysrepo_subscription.hpp"
+
+
+class MyCallback : public sysrepo::Callback {
+public:
+    MyCallback(const std::string& moduleName, Recorder* rec)
+        : m_moduleName(moduleName)
+        , m_recorder(rec)
+    {
+    }
+
+    int module_change(sysrepo::S_Session sess, const char* module_name, sr_notif_event_t event, void*) override
+    {
+        using namespace std::string_literals;
+        auto xpath = "/"s + module_name + ":*";
+        auto it = sess->get_changes_iter(xpath.c_str());
+
+        if (event == SR_EV_APPLY)
+            return SR_ERR_OK;
+
+        while (auto change = sess->get_change_next(it)) {
+            m_recorder->write(change->new_val()->xpath(),
+                              change->old_val() ? change->old_val()->val_to_string() : "",
+                              change->new_val()->val_to_string());
+        }
+
+        return SR_ERR_OK;
+    }
+
+private:
+    std::string m_moduleName;
+    Recorder* m_recorder;
+};
+
+Recorder::~Recorder() = default;
+
+SysrepoSubscription::SysrepoSubscription(Recorder* rec)
+    : 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);
+    const char* modName = "example-schema";
+    m_callback = std::make_shared<MyCallback>(modName, rec);
+
+    m_subscription->module_change_subscribe(modName, m_callback);
+}
diff --git a/tests/mock/sysrepo_subscription.hpp b/tests/mock/sysrepo_subscription.hpp
new file mode 100644
index 0000000..36681a6
--- /dev/null
+++ b/tests/mock/sysrepo_subscription.hpp
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2018 CESNET, https://photonics.cesnet.cz/
+ * Copyright (C) 2018 FIT CVUT, https://fit.cvut.cz/
+ *
+ * Written by Václav Kubernát <kubervac@fit.cvut.cz>
+ *
+*/
+
+#pragma once
+
+#include <memory>
+
+namespace sysrepo {
+class Callback;
+class Connection;
+class Session;
+class Subscribe;
+}
+class YangSchema;
+
+class Recorder {
+public:
+    virtual ~Recorder();
+    virtual void write(const std::string& xpath, const std::string& oldValue, const std::string& newValue) = 0;
+};
+
+class SysrepoSubscription {
+public:
+    SysrepoSubscription(Recorder* rec);
+
+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::Callback> m_callback;
+    std::shared_ptr<sysrepo::Subscribe> m_subscription;
+};
diff --git a/tests/sysrepo.cpp b/tests/sysrepo.cpp
new file mode 100644
index 0000000..fb9a3c3
--- /dev/null
+++ b/tests/sysrepo.cpp
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2018 CESNET, https://photonics.cesnet.cz/
+ * Copyright (C) 2018 FIT CVUT, https://fit.cvut.cz/
+ *
+ * Written by Václav Kubernát <kubervac@fit.cvut.cz>
+ *
+*/
+
+#include "trompeloeil_catch.h"
+
+#include "sysrepo_access.hpp"
+#include "sysrepo_subscription.hpp"
+#include "sysrepo_vars.hpp"
+
+class MockRecorder : public Recorder
+{
+public:
+    MAKE_MOCK3(write, void(const std::string&, const std::string&, const std::string&), override);
+};
+
+TEST_CASE("setting values")
+{
+    if (system(SYSREPOCTLEXECUTABLE " --uninstall --module example-schema > /dev/null") != 0) {
+        // uninstall can fail if it isn't already installed -> do nothing
+        // This crappy comment is here due to https://gcc.gnu.org/bugzilla/show_bug.cgi?id=25509
+    }
+    REQUIRE(system(SYSREPOCTLEXECUTABLE " --install --yang " EXAMPLE_SCHEMA_LOCATION " > /dev/null") == 0);
+
+    trompeloeil::sequence seq1;
+    MockRecorder mock;
+    SysrepoSubscription subscription(&mock);
+    SysrepoAccess datastore("netconf-cli-test");
+
+    SECTION("set leafInt to 123")
+    {
+        REQUIRE_CALL(mock, write("/example-schema:leafInt", "", "123"));
+        datastore.setLeaf("/example-schema:leafInt", 123);
+        datastore.commitChanges();
+    }
+
+    SECTION("set leafEnum to coze")
+    {
+        REQUIRE_CALL(mock, write("/example-schema:leafEnum", "", "coze"));
+        datastore.setLeaf("/example-schema:leafEnum", enum_{"coze"});
+        datastore.commitChanges();
+    }
+
+    SECTION("set leafDecimal to 123.544")
+    {
+        REQUIRE_CALL(mock, write("/example-schema:leafDecimal", "", "123.544"));
+        datastore.setLeaf("/example-schema:leafDecimal", 123.544);
+        datastore.commitChanges();
+    }
+
+    SECTION("create presence container")
+    {
+        REQUIRE_CALL(mock, write("/example-schema:pContainer", "", ""));
+        datastore.createPresenceContainer("/example-schema:pContainer");
+        datastore.commitChanges();
+    }
+
+    waitForCompletionAndBitMore(seq1);
+}