From 746c0cdfef6808f393be7946630b8acbb0636706 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
Date: Tue, 22 Jul 2025 17:47:50 +0200
Subject: [PATCH 09/38] tests: add SSE event watcher for comments
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Organization: Wires

I want to test whether client receives even the Server Sent Events
which should be ignored, i.e., lines starting with colon.

Change-Id: If54f0af05b4884aab01325f12fd0a6859791b41b
Signed-off-by: Mattias Walström <lazzer@gmail.com>
---
 tests/event_watchers.cpp |  7 ++++++-
 tests/event_watchers.h   |  4 +++-
 tests/restconf_utils.cpp | 36 ++++++++++++++++++++----------------
 tests/restconf_utils.h   | 12 +++++++++---
 4 files changed, 38 insertions(+), 21 deletions(-)

diff --git a/tests/event_watchers.cpp b/tests/event_watchers.cpp
index 690338e..26ba951 100644
--- a/tests/event_watchers.cpp
+++ b/tests/event_watchers.cpp
@@ -64,7 +64,7 @@ void RestconfNotificationWatcher::setDataFormat(const libyang::DataFormat dataFo
     this->dataFormat = dataFormat;
 }
 
-void RestconfNotificationWatcher::operator()(const std::string& msg) const
+void RestconfNotificationWatcher::dataEvent(const std::string& msg) const
 {
     spdlog::trace("Client received data: {}", msg);
     auto notifDataNode = ctx.parseOp(msg,
@@ -79,3 +79,8 @@ void RestconfNotificationWatcher::operator()(const std::string& msg) const
 
     data(*dataRoot->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Shrink));
 }
+
+void RestconfNotificationWatcher::commentEvent(const std::string& msg) const
+{
+    comment(msg);
+}
diff --git a/tests/event_watchers.h b/tests/event_watchers.h
index 3b533ba..7022b64 100644
--- a/tests/event_watchers.h
+++ b/tests/event_watchers.h
@@ -39,8 +39,10 @@ struct RestconfNotificationWatcher {
 
     RestconfNotificationWatcher(const libyang::Context& ctx);
     void setDataFormat(const libyang::DataFormat dataFormat);
-    void operator()(const std::string& msg) const;
+    void dataEvent(const std::string& msg) const;
+    void commentEvent(const std::string& msg) const;
 
+    MAKE_CONST_MOCK1(comment, void(const std::string&));
     MAKE_CONST_MOCK1(data, void(const std::string&));
 };
 
diff --git a/tests/restconf_utils.cpp b/tests/restconf_utils.cpp
index 08b9623..5b1ebe3 100644
--- a/tests/restconf_utils.cpp
+++ b/tests/restconf_utils.cpp
@@ -169,10 +169,11 @@ SSEClient::SSEClient(
     const std::string& server_address,
     const std::string& server_port,
     std::binary_semaphore& requestSent,
-    const RestconfNotificationWatcher& notification,
+    const RestconfNotificationWatcher& eventWatcher,
     const std::string& uri,
     const std::map<std::string, std::string>& headers,
-    const boost::posix_time::seconds silenceTimeout)
+    const boost::posix_time::seconds silenceTimeout,
+    const ReportIgnoredLines reportIgnoredLines)
     : client(std::make_shared<ng_client::session>(io, server_address, server_port))
     , t(io, silenceTimeout)
 {
@@ -191,17 +192,15 @@ SSEClient::SSEClient(
         }
     });
 
-    client->on_connect([&, uri, reqHeaders, silenceTimeout, server_address, server_port](auto) {
+    client->on_connect([&, uri, reqHeaders, silenceTimeout, server_address, server_port, reportIgnoredLines](auto) {
         boost::system::error_code ec;
 
         auto req = client->submit(ec, "GET", serverAddressAndPort(server_address, server_port) + uri, "", reqHeaders);
-        req->on_response([&, silenceTimeout](const ng_client::response& res) {
+        req->on_response([&, silenceTimeout, reportIgnoredLines](const ng_client::response& res) {
             requestSent.release();
-            res.on_data([&, silenceTimeout](const uint8_t* data, std::size_t len) {
+            res.on_data([&, silenceTimeout, reportIgnoredLines](const uint8_t* data, std::size_t len) {
                 // not a production-ready code. In real-life condition the data received in one callback might probably be incomplete
-                for (const auto& event : parseEvents(std::string(reinterpret_cast<const char*>(data), len))) {
-                    notification(event);
-                }
+                parseEvents(std::string(reinterpret_cast<const char*>(data), len), eventWatcher, reportIgnoredLines);
                 t.expires_from_now(silenceTimeout);
             });
         });
@@ -218,25 +217,30 @@ SSEClient::SSEClient(
     });
 }
 
-std::vector<std::string> SSEClient::parseEvents(const std::string& msg)
+void SSEClient::parseEvents(const std::string& msg, const RestconfNotificationWatcher& eventWatcher, const ReportIgnoredLines reportIgnoredLines)
 {
-    static const std::string prefix = "data:";
+    static const std::string dataPrefix = "data:";
+    static const std::string ignorePrefix = ":";
 
-    std::vector<std::string> res;
     std::istringstream iss(msg);
     std::string line;
     std::string event;
 
     while (std::getline(iss, line)) {
-        if (line.starts_with(prefix)) {
-            event += line.substr(prefix.size());
-        } else if (line.empty()) {
-            res.emplace_back(std::move(event));
+        if (line.starts_with(ignorePrefix) && reportIgnoredLines == ReportIgnoredLines::Yes) {
+            eventWatcher.commentEvent(line);
+        } else if (line.starts_with(ignorePrefix)) {
+            continue;
+        } else if (line.starts_with(dataPrefix)) {
+            event += line.substr(dataPrefix.size());
+        } else if (line.empty() && !event.empty()) {
+            eventWatcher.dataEvent(event);
             event.clear();
+        } else if (line.empty()) {
+            continue;
         } else {
             CAPTURE(msg);
             FAIL("Unprefixed response");
         }
     }
-    return res;
 }
diff --git a/tests/restconf_utils.h b/tests/restconf_utils.h
index 9efe398..9dde10b 100644
--- a/tests/restconf_utils.h
+++ b/tests/restconf_utils.h
@@ -88,17 +88,23 @@ struct SSEClient {
     std::shared_ptr<ng_client::session> client;
     boost::asio::deadline_timer t;
 
+    enum class ReportIgnoredLines {
+        No,
+        Yes,
+    };
+
     SSEClient(
         boost::asio::io_service& io,
         const std::string& server_address,
         const std::string& server_port,
         std::binary_semaphore& requestSent,
-        const RestconfNotificationWatcher& notification,
+        const RestconfNotificationWatcher& eventWatcher,
         const std::string& uri,
         const std::map<std::string, std::string>& headers,
-        const boost::posix_time::seconds silenceTimeout = boost::posix_time::seconds(1)); // test code; the server should respond "soon"
+        const boost::posix_time::seconds silenceTimeout = boost::posix_time::seconds(1), // test code; the server should respond "soon"
+        const ReportIgnoredLines reportIgnoredLines = ReportIgnoredLines::No);
 
-    static std::vector<std::string> parseEvents(const std::string& msg);
+    static void parseEvents(const std::string& msg, const RestconfNotificationWatcher& eventWatcher, const ReportIgnoredLines reportIgnoredLines);
 };
 
 #define PREPARE_LOOP_WITH_EXCEPTIONS \
-- 
2.43.0

