tests: encapsulate RPC watcher into a class

Change-Id: I62e09cd4333717ce8941a2bcbe17298cc007f315
diff --git a/CMakeLists.txt b/CMakeLists.txt
index d6697bc..c93a422 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -341,6 +341,8 @@
         tests/sysrepo-helpers/datastore.h
         tests/sysrepo-helpers/notifications.cpp
         tests/sysrepo-helpers/notifications.h
+        tests/sysrepo-helpers/rpc.cpp
+        tests/sysrepo-helpers/rpc.h
         )
     target_link_libraries(SysrepoTesting DoctestIntegration PkgConfig::SYSREPO)
 
diff --git a/tests/health_systemd-units.cpp b/tests/health_systemd-units.cpp
index 8378550..1b18dba 100644
--- a/tests/health_systemd-units.cpp
+++ b/tests/health_systemd-units.cpp
@@ -15,55 +15,28 @@
 #include "pretty_printers.h"
 #include "test_log_setup.h"
 #include "tests/sysrepo-helpers/common.h"
+#include "tests/sysrepo-helpers/rpc.h"
 #include "utils/log-init.h"
 #include "utils/log.h"
 
 using namespace std::chrono_literals;
 
 // clang-format off
-#define EXPECT_ALARM_RPC(RESOURCE, SEVERITY, TEXT) REQUIRE_CALL(fakeAlarmServer, rpcCalled(alarmRPC, std::map<std::string, std::string>{ \
-            {"/sysrepo-ietf-alarms:create-or-update-alarm/alarm-text", TEXT},                                                            \
-            {"/sysrepo-ietf-alarms:create-or-update-alarm/alarm-type-id", "velia-alarms:systemd-unit-failure"},                          \
-            {"/sysrepo-ietf-alarms:create-or-update-alarm/alarm-type-qualifier", ""},                                                    \
-            {"/sysrepo-ietf-alarms:create-or-update-alarm/resource", RESOURCE},                                                          \
-            {"/sysrepo-ietf-alarms:create-or-update-alarm/severity", SEVERITY}                                                           \
+#define REQUIRE_ALARM_RPC(RESOURCE, SEVERITY, TEXT) REQUIRE_RPC_CALL(alarmRPC, (Values{                          \
+            {"/sysrepo-ietf-alarms:create-or-update-alarm", "(unprintable)"},                                   \
+            {"/sysrepo-ietf-alarms:create-or-update-alarm/alarm-text", TEXT},                                   \
+            {"/sysrepo-ietf-alarms:create-or-update-alarm/alarm-type-id", "velia-alarms:systemd-unit-failure"}, \
+            {"/sysrepo-ietf-alarms:create-or-update-alarm/alarm-type-qualifier", ""},                           \
+            {"/sysrepo-ietf-alarms:create-or-update-alarm/resource", RESOURCE},                                 \
+            {"/sysrepo-ietf-alarms:create-or-update-alarm/severity", SEVERITY}                                  \
         })).IN_SEQUENCE(seq1);
 // clang-format on
 
-const auto alarmRPC = "/sysrepo-ietf-alarms:create-or-update-alarm";
-
-class FakeAlarmServerSysrepo {
-public:
-    FakeAlarmServerSysrepo();
-    MAKE_CONST_MOCK2(rpcCalled, void(std::string_view, const std::map<std::string, std::string>&));
-
-private:
-    sysrepo::Session m_srSess;
-    std::optional<sysrepo::Subscription> m_srSub;
-};
-
-
-FakeAlarmServerSysrepo::FakeAlarmServerSysrepo()
-    : m_srSess(sysrepo::Connection{}.sessionStart())
-{
-    m_srSub = m_srSess.onRPCAction(alarmRPC, [&](auto, auto, std::string_view path, const libyang::DataNode input, auto, auto, auto) {
-        std::map<std::string, std::string> in;
-
-        for (auto n : input.childrenDfs()) {
-            if (n.isTerm()) {
-                in[n.path()] = n.asTerm().valueStr();
-            }
-        }
-
-        rpcCalled(path, in);
-        return sysrepo::ErrorCode::Ok;
-    });
-}
-
 TEST_CASE("systemd unit state monitoring (alarms)")
 {
     TEST_INIT_LOGS;
     TEST_SYSREPO_INIT;
+    TEST_SYSREPO_INIT_CLIENT;
     trompeloeil::sequence seq1;
 
     // Create and setup separate connections for both client and server to simulate real-world server-client architecture.
@@ -74,19 +47,17 @@
     serverConnection->enterEventLoopAsync();
 
     auto server = DbusSystemdServer(*serverConnection);
-    FakeAlarmServerSysrepo fakeAlarmServer;
+    RPCWatcher alarmRPC(client, "/sysrepo-ietf-alarms:create-or-update-alarm");
 
-    EXPECT_ALARM_RPC("unit1.service", "cleared", "systemd unit state: (active, running)");
+    REQUIRE_ALARM_RPC("unit1.service", "cleared", "systemd unit state: (active, running)");
     server.createUnit(*serverConnection, "unit1.service", "/org/freedesktop/systemd1/unit/unit1", "active", "running");
-    EXPECT_ALARM_RPC("unit2.service", "critical", "systemd unit state: (activating, auto-restart)");
+    REQUIRE_ALARM_RPC("unit2.service", "critical", "systemd unit state: (activating, auto-restart)");
     server.createUnit(*serverConnection, "unit2.service", "/org/freedesktop/systemd1/unit/unit2", "activating", "auto-restart");
-    EXPECT_ALARM_RPC("unit3.service", "critical", "systemd unit state: (failed, failed)");
+    REQUIRE_ALARM_RPC("unit3.service", "critical", "systemd unit state: (failed, failed)");
     server.createUnit(*serverConnection, "unit3.service", "/org/freedesktop/systemd1/unit/unit3", "failed", "failed");
 
     auto systemdAlarms = std::make_shared<velia::health::SystemdUnits>(srSess, *clientConnection, serverConnection->getUniqueName(), "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "org.freedesktop.systemd1.Unit");
 
-    waitForCompletionAndBitMore(seq1);
-    // clang-format off
     REQUIRE(dataFromSysrepo(srSess, "/ietf-alarms:alarms/alarm-inventory", sysrepo::Datastore::Operational) == std::map<std::string, std::string>{
             {"/alarm-type[alarm-type-id='velia-alarms:systemd-unit-failure'][alarm-type-qualifier='']", ""},
             {"/alarm-type[alarm-type-id='velia-alarms:systemd-unit-failure'][alarm-type-qualifier='']/alarm-type-id", "velia-alarms:systemd-unit-failure"},
@@ -100,15 +71,15 @@
             });
     // clang-format on
 
-    EXPECT_ALARM_RPC("unit2.service", "cleared", "systemd unit state: (active, running)");
-    EXPECT_ALARM_RPC("unit3.service", "cleared", "systemd unit state: (active, running)");
-    EXPECT_ALARM_RPC("unit4.service", "critical", "systemd unit state: (failed, failed)");
-    EXPECT_ALARM_RPC("unit3.service", "critical", "systemd unit state: (activating, auto-restart)");
-    EXPECT_ALARM_RPC("unit3.service", "cleared", "systemd unit state: (active, running)");
-    EXPECT_ALARM_RPC("unit3.service", "critical", "systemd unit state: (failed, failed)");
-    EXPECT_ALARM_RPC("unit3.service", "critical", "systemd unit state: (activating, auto-restart)");
-    EXPECT_ALARM_RPC("unit3.service", "cleared", "systemd unit state: (active, running)");
-    EXPECT_ALARM_RPC("unit4.service", "cleared", "systemd unit state: (active, running)");
+    REQUIRE_ALARM_RPC("unit2.service", "cleared", "systemd unit state: (active, running)");
+    REQUIRE_ALARM_RPC("unit3.service", "cleared", "systemd unit state: (active, running)");
+    REQUIRE_ALARM_RPC("unit4.service", "critical", "systemd unit state: (failed, failed)");
+    REQUIRE_ALARM_RPC("unit3.service", "critical", "systemd unit state: (activating, auto-restart)");
+    REQUIRE_ALARM_RPC("unit3.service", "cleared", "systemd unit state: (active, running)");
+    REQUIRE_ALARM_RPC("unit3.service", "critical", "systemd unit state: (failed, failed)");
+    REQUIRE_ALARM_RPC("unit3.service", "critical", "systemd unit state: (activating, auto-restart)");
+    REQUIRE_ALARM_RPC("unit3.service", "cleared", "systemd unit state: (active, running)");
+    REQUIRE_ALARM_RPC("unit4.service", "cleared", "systemd unit state: (active, running)");
 
     std::thread systemdSimulator([&] {
         server.changeUnitState("/org/freedesktop/systemd1/unit/unit2", "active", "running");
diff --git a/tests/sysrepo-helpers/rpc.cpp b/tests/sysrepo-helpers/rpc.cpp
new file mode 100644
index 0000000..eb32038
--- /dev/null
+++ b/tests/sysrepo-helpers/rpc.cpp
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 CESNET, https://photonics.cesnet.cz/
+ *
+ * Written by Tomáš Pecka <tomas.pecka@cesnet.cz>
+ *
+ */
+
+#include "rpc.h"
+#include "sysrepo-helpers/common.h"
+
+RPCWatcher::RPCWatcher(sysrepo::Session& session, const std::string& xpath)
+    : m_sub(session.onRPCAction(xpath, [&](auto, auto, auto, const libyang::DataNode input, auto, auto, auto) {
+        std::map<std::string, std::string> in;
+
+        for (auto n : input.childrenDfs()) {
+            in.emplace(n.path(), nodeAsString(n));
+        }
+
+        rpc(in);
+        return sysrepo::ErrorCode::Ok;
+    }))
+{
+}
diff --git a/tests/sysrepo-helpers/rpc.h b/tests/sysrepo-helpers/rpc.h
new file mode 100644
index 0000000..2ea3045
--- /dev/null
+++ b/tests/sysrepo-helpers/rpc.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 CESNET, https://photonics.cesnet.cz/
+ *
+ * Written by Tomáš Pecka <tomas.pecka@cesnet.cz>
+ *
+ */
+
+#pragma once
+
+#include "trompeloeil_doctest.h"
+#include <sysrepo-cpp/Subscription.hpp>
+#include "sysrepo-helpers/common.h"
+#include "test_log_setup.h"
+
+/** @brief Watch for a given RPC */
+struct RPCWatcher {
+    RPCWatcher(sysrepo::Session& session, const std::string& xpath);
+    MAKE_MOCK1(rpc, void(const Values&));
+
+private:
+    sysrepo::Subscription m_sub;
+};
+
+#define REQUIRE_RPC_CALL(WATCHER, VALUES) REQUIRE_CALL(WATCHER, rpc(VALUES))
diff --git a/tests/sysrepo_ietf-hardware.cpp b/tests/sysrepo_ietf-hardware.cpp
index 2888716..4bb3ecc 100644
--- a/tests/sysrepo_ietf-hardware.cpp
+++ b/tests/sysrepo_ietf-hardware.cpp
@@ -7,15 +7,12 @@
 #include "mock/ietf_hardware.h"
 #include "pretty_printers.h"
 #include "sysrepo-helpers/datastore.h"
+#include "sysrepo-helpers/rpc.h"
 #include "test_log_setup.h"
 #include "tests/sysrepo-helpers/common.h"
 
 using namespace std::literals;
 
-struct AlarmEvent {
-    MAKE_CONST_MOCK1(event, void(const std::map<std::string, std::string>&));
-};
-
 struct AlarmInventory {
     std::map<std::pair<std::string, std::string>, std::vector<std::string>> inventory;
 
@@ -48,7 +45,7 @@
         .LR_SIDE_EFFECT(alarmInventory.add(ALARM_TYPE, "", COMPONENT(IETF_HARDWARE_RESOURCE)))
 
 #define REQUIRE_ALARM_RPC(ALARM_TYPE_ID, IETF_HARDWARE_RESOURCE_KEY, SEVERITY, TEXT)                                               \
-    REQUIRE_CALL(alarmEvents, event(std::map<std::string, std::string>{                                                            \
+    REQUIRE_RPC_CALL(alarmEvents, (Values{                                                                                         \
                                   {"/sysrepo-ietf-alarms:create-or-update-alarm", "(unprintable)"},                                \
                                   {"/sysrepo-ietf-alarms:create-or-update-alarm/alarm-text", TEXT},                                \
                                   {"/sysrepo-ietf-alarms:create-or-update-alarm/alarm-type-id", ALARM_TYPE_ID},                    \
@@ -72,22 +69,10 @@
 
     client.switchDatastore(sysrepo::Datastore::Operational);
 
-    AlarmEvent alarmEvents;
-    AlarmInventory alarmInventory;
-
     trompeloeil::sequence seq1;
 
-    auto alarmsRPC = alarmsClient.onRPCAction("/sysrepo-ietf-alarms:create-or-update-alarm", [&](auto, auto, auto, const libyang::DataNode input, auto, auto, auto) {
-        std::map<std::string, std::string> inputData;
-
-        for (const auto& node : input.childrenDfs()) {
-            inputData.emplace(node.path(), nodeAsString(node));
-        }
-
-        alarmEvents.event(inputData);
-
-        return sysrepo::ErrorCode::Ok;
-    });
+    AlarmInventory alarmInventory;
+    RPCWatcher alarmEvents(alarmsClient, "/sysrepo-ietf-alarms:create-or-update-alarm");
 
     auto directLeafNodeQuery = [&](const std::string& xpath) {
         auto val = client.getData(xpath);