Add unit test for SSE

Writing this test exposed some bugs in SSE that got merged.
sendSSEHeader was never called, leading to a connection that starts
and immediately closes with no error code.

This issue has been corrected in code, such that the sockets start.

To allow for unit tests, the io_service needs to be passed into the
class, previously, the SSE connection was pulling the io_context from
the DBus connection, which is odd, given that the SSE connection has
no other dependencies on DBus.

Unit tests should help keep it working.

Tested: Unit tests pass.

Change-Id: I48080d2a94b6349989f556cd1c7b103bad498526
Signed-off-by: Ed Tanous <ed@tanous.net>
diff --git a/test/http/server_sent_event_test.cpp b/test/http/server_sent_event_test.cpp
new file mode 100644
index 0000000..d3f9403
--- /dev/null
+++ b/test/http/server_sent_event_test.cpp
@@ -0,0 +1,111 @@
+#include "boost/asio/read.hpp"
+#include "boost/asio/read_until.hpp"
+#include "http/http_request.hpp"
+#include "http/http_response.hpp"
+#include "http/server_sent_event.hpp"
+
+#include <boost/asio/steady_timer.hpp>
+#include <boost/beast/_experimental/test/stream.hpp>
+
+#include <memory>
+#include <string>
+#include <string_view>
+
+#include "gtest/gtest.h"
+namespace crow
+{
+namespace sse_socket
+{
+
+namespace
+{
+
+TEST(ServerSentEvent, SseWorks)
+{
+    boost::asio::io_context io;
+    boost::beast::test::stream stream(io);
+    boost::beast::test::stream out(io);
+    stream.connect(out);
+
+    bool openCalled = false;
+    auto openHandler = [&openCalled](Connection&) { openCalled = true; };
+    bool closeCalled = false;
+    auto closeHandler = [&closeCalled](Connection&) { closeCalled = true; };
+
+    std::shared_ptr<ConnectionImpl<boost::beast::test::stream>> conn =
+        std::make_shared<ConnectionImpl<boost::beast::test::stream>>(
+            io, std::move(stream), openHandler, closeHandler);
+    conn->start();
+    // Connect
+    {
+        constexpr std::string_view expected =
+            "HTTP/1.1 200 OK\r\n"
+            "Content-Type: text/event-stream\r\n"
+            "\r\n";
+
+        while (out.str().size() != expected.size())
+        {
+            io.run_for(std::chrono::milliseconds(1));
+        }
+
+        std::string eventContent;
+        eventContent.resize(expected.size());
+        boost::asio::read(out, boost::asio::buffer(eventContent));
+
+        EXPECT_EQ(eventContent, expected);
+        EXPECT_TRUE(openCalled);
+        EXPECT_FALSE(closeCalled);
+        EXPECT_TRUE(out.str().empty());
+    }
+    // Send one event
+    {
+        conn->sendEvent("TestEventId", "TestEventContent");
+        std::string_view expected = "id: TestEventId\n"
+                                    "data: TestEventContent\n"
+                                    "\n";
+
+        while (out.str().size() < expected.size())
+        {
+            io.run_for(std::chrono::milliseconds(1));
+        }
+        EXPECT_EQ(out.str(), expected);
+
+        std::string eventContent;
+        eventContent.resize(expected.size());
+        boost::asio::read(out, boost::asio::buffer(eventContent));
+
+        EXPECT_EQ(eventContent, expected);
+        EXPECT_TRUE(out.str().empty());
+    }
+    // Send second event
+    {
+        conn->sendEvent("TestEventId2", "TestEvent\nContent2");
+        constexpr std::string_view expected = "id: TestEventId2\n"
+                                              "data: TestEvent\n"
+                                              "data: Content2\n"
+                                              "\n";
+
+        while (out.str().size() < expected.size())
+        {
+            io.run_for(std::chrono::milliseconds(1));
+        }
+
+        std::string eventContent;
+        eventContent.resize(expected.size());
+        boost::asio::read(out, boost::asio::buffer(eventContent));
+        EXPECT_EQ(eventContent, expected);
+        EXPECT_TRUE(out.str().empty());
+    }
+    // close the remote
+    {
+        out.close();
+        while (!closeCalled)
+        {
+            io.run_for(std::chrono::milliseconds(1));
+        }
+    }
+}
+} // namespace
+
+} // namespace sse_socket
+} // namespace crow