#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "libreichwein/file.h" #include "libreichwein/process.h" #include "config.h" #include "storage.h" #include "whiteboard.h" namespace bp = boost::process; namespace fs = std::filesystem; using namespace Reichwein; using namespace std::string_literals; namespace { const fs::path webserverConfigFilename{"./webserver.conf"}; const fs::path testConfigFilename{"./whiteboard.conf"}; const fs::path testDbFilename{"./whiteboard.db3"}; } class WhiteboardTest: public ::testing::Test { protected: WhiteboardTest(){ } ~WhiteboardTest() override{ } void SetUp() override { File::setFile(testConfigFilename, R"CONFIG( ::1:9876 . 2592000 4 3 )CONFIG"); std::error_code ec; fs::remove(testDbFilename, ec); m_config = std::make_shared(testConfigFilename); m_pid = fork(); if (m_pid == -1) { throw std::runtime_error("Error on fork(): "s + strerror(errno)); } else if (m_pid == 0) { // child Whiteboard whiteboard; std::vector argvv{{"whiteboard", "-c", testConfigFilename.generic_string()}}; char* argv[] = {argvv[0].data(), argvv[1].data(), argvv[2].data(), nullptr}; whiteboard.run(argvv.size(), argv); exit(0); } Process::wait_for_pid_listening_on(m_pid, 9876); std::this_thread::sleep_for(std::chrono::milliseconds(20)); } void TearDown() override { if (m_pid == 0) throw std::runtime_error("Whiteboard not running on requesting SIGTERM"); if (kill(m_pid, SIGTERM) != 0) throw std::runtime_error("Unable to SIGTERM Whiteboard"); if (int result = waitpid(m_pid, NULL, 0); result != m_pid) throw std::runtime_error("waitpid returned "s + std::to_string(result)); std::error_code ec; fs::remove(testDbFilename, ec); fs::remove(testConfigFilename, ec); } std::shared_ptr m_config; pid_t m_pid{}; }; class WebsocketClient { public: WebsocketClient() { std::string host = "::1"; auto const port = "9876" ; // These objects perform our I/O boost::asio::ip::tcp::resolver resolver{ioc_}; ws_ = std::make_unique>(ioc_); // Look up the domain name resolver_results_ = resolver.resolve(host, port); connect(); handshake(); } void connect() { // Make the connection on the IP address we get from a lookup ep_ = boost::asio::connect(boost::beast::get_lowest_layer(*ws_), resolver_results_); } void handshake() { // Update the host_ string. This will provide the value of the // Host HTTP header during the WebSocket handshake. // See https://tools.ietf.org/html/rfc7230#section-5.4 std::string host{"[::1]:9876"}; // Set a decorator to change the User-Agent of the handshake ws_->set_option(boost::beast::websocket::stream_base::decorator( [](boost::beast::websocket::request_type& req) { req.set(boost::beast::http::field::user_agent, std::string("Reichwein.IT Test Websocket Client")); })); // Perform the websocket handshake ws_->handshake(host, "/"); } void write(const std::string& data) { ws_->write(boost::asio::buffer(data)); } std::string read() { boost::beast::flat_buffer buffer; ws_->read(buffer); return {boost::asio::buffers_begin(buffer.data()), boost::asio::buffers_end(buffer.data())}; } ~WebsocketClient() { } bool is_open() { return ws_->is_open(); } private: boost::asio::io_context ioc_; boost::asio::ip::tcp::resolver::results_type resolver_results_; std::unique_ptr> ws_; boost::asio::ip::tcp::endpoint ep_; }; // // tests via websocket server in separate process (hides coverage) // TEST_F(WhiteboardTest, websocket_server_connection) { WebsocketClient wc; } TEST_F(WhiteboardTest, websocket_server_generate_id) { WebsocketClient wc; wc.write("newid"); std::string result0 {wc.read()}; ASSERT_TRUE(boost::algorithm::starts_with(result0, "newid")); ASSERT_TRUE(boost::algorithm::ends_with(result0, "")); ASSERT_EQ(result0.size(), 58); wc.write("newid"); std::string result1 {wc.read()}; ASSERT_TRUE(boost::algorithm::starts_with(result1, "newid")); ASSERT_TRUE(boost::algorithm::ends_with(result1, "")); ASSERT_EQ(result1.size(), 58); ASSERT_NE(result0, result1); } // check number of threads as configured TEST_F(WhiteboardTest, threads) { ASSERT_GE(Process::number_of_threads(m_pid), 4); } TEST_F(WhiteboardTest, max_connections) { WebsocketClient wc1; std::this_thread::sleep_for(std::chrono::milliseconds(20)); ASSERT_TRUE(wc1.is_open()); WebsocketClient wc2; std::this_thread::sleep_for(std::chrono::milliseconds(20)); ASSERT_TRUE(wc2.is_open()); WebsocketClient wc3; std::this_thread::sleep_for(std::chrono::milliseconds(20)); ASSERT_TRUE(wc3.is_open()); ASSERT_THROW(WebsocketClient wc4, std::exception); } TEST_F(WhiteboardTest, id) { WebsocketClient wc; wc.write("getfile1"); std::string result {wc.read()}; EXPECT_EQ(result, "getfile00"); wc.write("getfile"); result = wc.read(); EXPECT_EQ(result, "errorMessage handling error: Invalid id (empty)"); wc.write("getfile01234567890123456789"); result = wc.read(); EXPECT_EQ(result, "errorMessage handling error: Invalid id (size > 16)"); wc.write("getfileX"); result = wc.read(); EXPECT_EQ(result, "errorMessage handling error: Invalid id char: X"); wc.write("getfilea."); result = wc.read(); EXPECT_EQ(result, "errorMessage handling error: Invalid id char: ."); wc.write("getfilea$b"); result = wc.read(); EXPECT_EQ(result, "errorMessage handling error: Invalid id char: $"); }