Fix WebSocket test (#485)
* Fix websocket unit tests hanging * Rename websocket to monitoring unit test
This commit is contained in:
parent
bbfef45b37
commit
531db2d47c
@ -359,5 +359,5 @@ add_dependencies(memgraph__unit test_lcp)
|
||||
# Test websocket
|
||||
find_package(Boost REQUIRED)
|
||||
|
||||
add_unit_test(websocket.cpp)
|
||||
target_link_libraries(${test_prefix}websocket mg-communication Boost::headers)
|
||||
add_unit_test(monitoring.cpp)
|
||||
target_link_libraries(${test_prefix}monitoring mg-communication Boost::headers)
|
||||
|
@ -7,17 +7,19 @@
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt."""
|
||||
// licenses/APL.txt.
|
||||
|
||||
#define BOOST_ASIO_USE_TS_EXECUTOR_AS_DEFAULT
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <thread>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#include <fmt/core.h>
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <spdlog/common.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <boost/asio/connect.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
@ -38,8 +40,6 @@ inline constexpr auto kResponseSuccess{"success"};
|
||||
inline constexpr auto kResponseMessage{"message"};
|
||||
|
||||
struct MockAuth : public memgraph::communication::websocket::AuthenticationInterface {
|
||||
MockAuth() = default;
|
||||
|
||||
bool Authenticate(const std::string & /*username*/, const std::string & /*password*/) const override {
|
||||
return authentication;
|
||||
}
|
||||
@ -55,25 +55,50 @@ struct MockAuth : public memgraph::communication::websocket::AuthenticationInter
|
||||
bool has_any_users{true};
|
||||
};
|
||||
|
||||
class WebSocketServerTest : public ::testing::Test {
|
||||
public:
|
||||
class MonitoringServerTest : public ::testing::Test {
|
||||
protected:
|
||||
WebSocketServerTest() : websocket_server{{"0.0.0.0", 0}, &context, auth} {
|
||||
EXPECT_NO_THROW(websocket_server.Start());
|
||||
}
|
||||
void SetUp() override { ASSERT_NO_THROW(monitoring_server.Start()); }
|
||||
|
||||
void TearDown() override {
|
||||
EXPECT_NO_THROW(websocket_server.Shutdown());
|
||||
EXPECT_NO_THROW(websocket_server.AwaitShutdown());
|
||||
StopLogging();
|
||||
ASSERT_NO_THROW(monitoring_server.Shutdown());
|
||||
ASSERT_NO_THROW(monitoring_server.AwaitShutdown());
|
||||
}
|
||||
|
||||
std::string ServerPort() const { return std::to_string(websocket_server.GetEndpoint().port()); }
|
||||
std::string ServerPort() const { return std::to_string(monitoring_server.GetEndpoint().port()); }
|
||||
|
||||
std::string ServerAddress() const { return websocket_server.GetEndpoint().address().to_string(); }
|
||||
std::string ServerAddress() const { return monitoring_server.GetEndpoint().address().to_string(); }
|
||||
|
||||
void StartLogging(std::vector<std::pair<spdlog::level::level_enum, std::string>> messages) {
|
||||
messages_ = std::move(messages);
|
||||
logging_.store(true, std::memory_order_relaxed);
|
||||
bg_thread_ = std::jthread([this]() {
|
||||
while (logging_.load(std::memory_order_relaxed)) {
|
||||
for (const auto &[message_level, message_content] : messages_) {
|
||||
spdlog::log(message_level, message_content);
|
||||
spdlog::default_logger()->flush();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void StopLogging() {
|
||||
if (!logging_.load(std::memory_order_relaxed)) {
|
||||
return;
|
||||
}
|
||||
logging_.store(false, std::memory_order_relaxed);
|
||||
ASSERT_TRUE(bg_thread_.joinable());
|
||||
bg_thread_.join();
|
||||
}
|
||||
|
||||
MockAuth auth;
|
||||
memgraph::communication::ServerContext context{};
|
||||
memgraph::communication::websocket::Server websocket_server;
|
||||
memgraph::communication::ServerContext context;
|
||||
memgraph::communication::websocket::Server monitoring_server{{"0.0.0.0", 0}, &context, auth};
|
||||
|
||||
private:
|
||||
std::jthread bg_thread_;
|
||||
std::vector<std::pair<spdlog::level::level_enum, std::string>> messages_;
|
||||
std::atomic<bool> logging_{false};
|
||||
};
|
||||
|
||||
class Client {
|
||||
@ -97,18 +122,18 @@ class Client {
|
||||
|
||||
std::string Read() {
|
||||
ws_.read(buffer_);
|
||||
const std::string response = beast::buffers_to_string(buffer_.data());
|
||||
std::string response = beast::buffers_to_string(buffer_.data());
|
||||
buffer_.consume(buffer_.size());
|
||||
return response;
|
||||
}
|
||||
|
||||
private:
|
||||
net::io_context ioc_{};
|
||||
net::io_context ioc_;
|
||||
websocket::stream<tcp::socket> ws_{ioc_};
|
||||
beast::flat_buffer buffer_;
|
||||
};
|
||||
|
||||
TEST(WebSocketServer, WebsocketWorkflow) {
|
||||
TEST(MonitoringServer, MonitoringWorkflow) {
|
||||
/**
|
||||
* Notice how there is no port management for the clients
|
||||
* and the servers, that is because when using "0.0.0.0" as address and
|
||||
@ -116,75 +141,85 @@ TEST(WebSocketServer, WebsocketWorkflow) {
|
||||
* and it is the keeper of all available port numbers and
|
||||
* assigns them automatically.
|
||||
*/
|
||||
MockAuth auth{};
|
||||
memgraph::communication::ServerContext context{};
|
||||
memgraph::communication::websocket::Server websocket_server({"0.0.0.0", 0}, &context, auth);
|
||||
const auto port = websocket_server.GetEndpoint().port();
|
||||
MockAuth auth;
|
||||
memgraph::communication::ServerContext context;
|
||||
memgraph::communication::websocket::Server monitoring_server({"0.0.0.0", 0}, &context, auth);
|
||||
const auto port = monitoring_server.GetEndpoint().port();
|
||||
|
||||
SCOPED_TRACE(fmt::format("Checking port number different then 0: {}", port));
|
||||
EXPECT_NE(port, 0);
|
||||
EXPECT_NO_THROW(websocket_server.Start());
|
||||
EXPECT_TRUE(websocket_server.IsRunning());
|
||||
EXPECT_NO_THROW(monitoring_server.Start());
|
||||
EXPECT_TRUE(monitoring_server.IsRunning());
|
||||
|
||||
EXPECT_NO_THROW(websocket_server.Shutdown());
|
||||
EXPECT_FALSE(websocket_server.IsRunning());
|
||||
EXPECT_NO_THROW(monitoring_server.Shutdown());
|
||||
EXPECT_FALSE(monitoring_server.IsRunning());
|
||||
|
||||
EXPECT_NO_THROW(websocket_server.AwaitShutdown());
|
||||
EXPECT_FALSE(websocket_server.IsRunning());
|
||||
EXPECT_NO_THROW(monitoring_server.AwaitShutdown());
|
||||
EXPECT_FALSE(monitoring_server.IsRunning());
|
||||
}
|
||||
|
||||
TEST_F(WebSocketServerTest, WebsocketConnection) {
|
||||
TEST(MonitoringServer, Connection) {
|
||||
MockAuth auth;
|
||||
memgraph::communication::ServerContext context;
|
||||
memgraph::communication::websocket::Server monitoring_server({"0.0.0.0", 0}, &context, auth);
|
||||
ASSERT_NO_THROW(monitoring_server.Start());
|
||||
{
|
||||
auto client = Client{};
|
||||
EXPECT_NO_THROW(client.Connect("0.0.0.0", ServerPort()));
|
||||
Client client;
|
||||
EXPECT_NO_THROW(client.Connect("0.0.0.0", std::to_string(monitoring_server.GetEndpoint().port())));
|
||||
}
|
||||
|
||||
websocket_server.Shutdown();
|
||||
websocket_server.AwaitShutdown();
|
||||
ASSERT_NO_THROW(monitoring_server.Shutdown());
|
||||
ASSERT_NO_THROW(monitoring_server.AwaitShutdown());
|
||||
ASSERT_FALSE(monitoring_server.IsRunning());
|
||||
}
|
||||
|
||||
TEST_F(WebSocketServerTest, WebsocketLogging) {
|
||||
TEST_F(MonitoringServerTest, Logging) {
|
||||
auth.has_any_users = false;
|
||||
// Set up the websocket logger as one of the defaults for spdlog
|
||||
{
|
||||
auto default_logger = spdlog::default_logger();
|
||||
auto sinks = default_logger->sinks();
|
||||
sinks.push_back(websocket_server.GetLoggingSink());
|
||||
sinks.push_back(monitoring_server.GetLoggingSink());
|
||||
|
||||
auto logger = std::make_shared<spdlog::logger>("memgraph_log", sinks.begin(), sinks.end());
|
||||
logger->set_level(default_logger->level());
|
||||
logger->flush_on(spdlog::level::trace);
|
||||
spdlog::set_default_logger(std::move(logger));
|
||||
}
|
||||
{
|
||||
auto client = Client();
|
||||
client.Connect(ServerAddress(), ServerPort());
|
||||
Client client;
|
||||
client.Connect(ServerAddress(), ServerPort());
|
||||
std::vector<std::pair<spdlog::level::level_enum, std::string>> messages{
|
||||
{spdlog::level::err, "Sending error message!"},
|
||||
{spdlog::level::warn, "Sending warn message!"},
|
||||
{spdlog::level::info, "Sending info message!"},
|
||||
{spdlog::level::trace, "Sending trace message!"},
|
||||
};
|
||||
|
||||
auto log_message = [](spdlog::level::level_enum level, std::string_view message) {
|
||||
spdlog::log(level, message);
|
||||
spdlog::default_logger()->flush();
|
||||
};
|
||||
auto log_and_check = [log_message, &client](spdlog::level::level_enum level, std::string_view message,
|
||||
std::string_view log_level_received) {
|
||||
std::thread(log_message, level, message).detach();
|
||||
const auto received_message = client.Read();
|
||||
EXPECT_EQ(received_message, fmt::format("{{\"event\": \"log\", \"level\": \"{}\", \"message\": \"{}\"}}\n",
|
||||
log_level_received, message));
|
||||
};
|
||||
|
||||
log_and_check(spdlog::level::err, "Sending error message!", "error");
|
||||
log_and_check(spdlog::level::warn, "Sending warn message!", "warning");
|
||||
log_and_check(spdlog::level::info, "Sending info message!", "info");
|
||||
log_and_check(spdlog::level::trace, "Sending trace message!", "trace");
|
||||
StartLogging(messages);
|
||||
std::unordered_set<std::string> received_messages;
|
||||
// Worst case scenario we might need to read 100 messages to get all messages
|
||||
// that we expect, in case messages get scrambled in network
|
||||
for (size_t i{0}; i < 100; ++i) {
|
||||
const auto received_message = client.Read();
|
||||
received_messages.insert(received_message);
|
||||
if (received_messages.size() == 4) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
ASSERT_EQ(received_messages.size(), 4);
|
||||
for (const auto &[message_level, message_content] : messages) {
|
||||
EXPECT_TRUE(
|
||||
received_messages.contains(fmt::format("{{\"event\": \"log\", \"level\": \"{}\", \"message\": \"{}\"}}\n",
|
||||
spdlog::level::to_string_view(message_level), message_content)));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(WebSocketServerTest, WebsocketAuthenticationParsingError) {
|
||||
TEST_F(MonitoringServerTest, AuthenticationParsingError) {
|
||||
static constexpr auto auth_fail = "Cannot parse JSON for WebSocket authentication";
|
||||
|
||||
{
|
||||
SCOPED_TRACE("Checking handling of first request parsing error.");
|
||||
auto client = Client();
|
||||
Client client;
|
||||
EXPECT_NO_THROW(client.Connect(ServerAddress(), ServerPort()));
|
||||
EXPECT_NO_THROW(client.Write("Test"));
|
||||
const auto response = nlohmann::json::parse(client.Read());
|
||||
@ -196,7 +231,7 @@ TEST_F(WebSocketServerTest, WebsocketAuthenticationParsingError) {
|
||||
}
|
||||
{
|
||||
SCOPED_TRACE("Checking handling of JSON parsing error.");
|
||||
auto client = Client();
|
||||
Client client;
|
||||
EXPECT_NO_THROW(client.Connect(ServerAddress(), ServerPort()));
|
||||
const std::string json_without_comma = R"({"username": "user" "password": "123"})";
|
||||
EXPECT_NO_THROW(client.Write(json_without_comma));
|
||||
@ -209,12 +244,12 @@ TEST_F(WebSocketServerTest, WebsocketAuthenticationParsingError) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(WebSocketServerTest, WebsocketAuthenticationWhenAuthPasses) {
|
||||
TEST_F(MonitoringServerTest, AuthenticationWhenAuthPasses) {
|
||||
static constexpr auto auth_success = R"({"message":"User has been successfully authenticated!","success":true})";
|
||||
|
||||
{
|
||||
SCOPED_TRACE("Checking successful authentication response.");
|
||||
auto client = Client();
|
||||
Client client;
|
||||
EXPECT_NO_THROW(client.Connect(ServerAddress(), ServerPort()));
|
||||
EXPECT_NO_THROW(client.Write(R"({"username": "user", "password": "123"})"));
|
||||
const auto response = client.Read();
|
||||
@ -223,13 +258,13 @@ TEST_F(WebSocketServerTest, WebsocketAuthenticationWhenAuthPasses) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(WebSocketServerTest, WebsocketAuthenticationWithMultipleAttempts) {
|
||||
TEST_F(MonitoringServerTest, AuthenticationWithMultipleAttempts) {
|
||||
static constexpr auto auth_success = R"({"message":"User has been successfully authenticated!","success":true})";
|
||||
static constexpr auto auth_fail = "Cannot parse JSON for WebSocket authentication";
|
||||
|
||||
{
|
||||
SCOPED_TRACE("Checking multiple authentication tries from same client");
|
||||
auto client = Client();
|
||||
Client client;
|
||||
EXPECT_NO_THROW(client.Connect(ServerAddress(), ServerPort()));
|
||||
EXPECT_NO_THROW(client.Write(R"({"username": "user" "password": "123"})"));
|
||||
|
||||
@ -250,8 +285,8 @@ TEST_F(WebSocketServerTest, WebsocketAuthenticationWithMultipleAttempts) {
|
||||
}
|
||||
{
|
||||
SCOPED_TRACE("Checking multiple authentication tries from different clients");
|
||||
auto client1 = Client();
|
||||
auto client2 = Client();
|
||||
Client client1;
|
||||
Client client2;
|
||||
|
||||
EXPECT_NO_THROW(client1.Connect(ServerAddress(), ServerPort()));
|
||||
EXPECT_NO_THROW(client2.Connect(ServerAddress(), ServerPort()));
|
||||
@ -274,12 +309,12 @@ TEST_F(WebSocketServerTest, WebsocketAuthenticationWithMultipleAttempts) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(WebSocketServerTest, WebsocketAuthenticationFails) {
|
||||
TEST_F(MonitoringServerTest, AuthenticationFails) {
|
||||
auth.authentication = false;
|
||||
|
||||
static constexpr auto auth_fail = R"({"message":"Authentication failed!","success":false})";
|
||||
{
|
||||
auto client = Client();
|
||||
Client client;
|
||||
EXPECT_NO_THROW(client.Connect(ServerAddress(), ServerPort()));
|
||||
EXPECT_NO_THROW(client.Write(R"({"username": "user", "password": "123"})"));
|
||||
|
||||
@ -289,12 +324,12 @@ TEST_F(WebSocketServerTest, WebsocketAuthenticationFails) {
|
||||
}
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
TEST_F(WebSocketServerTest, WebsocketAuthorizationFails) {
|
||||
TEST_F(MonitoringServerTest, AuthorizationFails) {
|
||||
auth.authorization = false;
|
||||
static constexpr auto auth_fail = R"({"message":"Authorization failed!","success":false})";
|
||||
|
||||
{
|
||||
auto client = Client();
|
||||
Client client;
|
||||
EXPECT_NO_THROW(client.Connect(ServerAddress(), ServerPort()));
|
||||
EXPECT_NO_THROW(client.Write(R"({"username": "user", "password": "123"})"));
|
||||
|
Loading…
Reference in New Issue
Block a user