Add Websocket e2e tests (#353)
This commit is contained in:
parent
bd2c30fddc
commit
1d88893715
@ -16,11 +16,12 @@
|
||||
namespace communication::websocket {
|
||||
|
||||
bool SafeAuth::Authenticate(const std::string &username, const std::string &password) const {
|
||||
// TODO: Make ReadLock after dealing with Authenticate
|
||||
return auth_->Lock()->Authenticate(username, password).has_value();
|
||||
}
|
||||
|
||||
bool SafeAuth::HasUserPermission(const std::string &username, const auth::Permission permission) const {
|
||||
if (const auto user = auth_->Lock()->GetUser(username); user) {
|
||||
if (const auto user = auth_->ReadLock()->GetUser(username); user) {
|
||||
return user->GetPermissions().Has(permission) == auth::PermissionLevel::GRANT;
|
||||
}
|
||||
return false;
|
||||
|
@ -80,6 +80,7 @@ bool Session::Run() {
|
||||
return false;
|
||||
}
|
||||
|
||||
authenticated_ = !auth_.HasAnyUsers();
|
||||
connected_.store(true, std::memory_order_relaxed);
|
||||
|
||||
// run on the strand
|
||||
@ -120,13 +121,14 @@ void Session::DoWrite() {
|
||||
void Session::OnWrite(boost::beast::error_code ec, size_t /*bytes_transferred*/) {
|
||||
messages_.pop_front();
|
||||
|
||||
if (ec) {
|
||||
return LogError(ec, "write");
|
||||
}
|
||||
if (close_) {
|
||||
DoShutdown();
|
||||
return;
|
||||
}
|
||||
if (ec) {
|
||||
close_ = true;
|
||||
return LogError(ec, "write");
|
||||
}
|
||||
if (!messages_.empty()) {
|
||||
DoWrite();
|
||||
}
|
||||
@ -149,10 +151,10 @@ void Session::DoClose() {
|
||||
}
|
||||
|
||||
void Session::OnClose(boost::beast::error_code ec) {
|
||||
connected_.store(false, std::memory_order_relaxed);
|
||||
if (ec) {
|
||||
return LogError(ec, "close");
|
||||
}
|
||||
connected_.store(false, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
utils::BasicResult<std::string> Session::Authorize(const nlohmann::json &creds) {
|
||||
@ -194,9 +196,9 @@ void Session::OnRead(const boost::beast::error_code ec, const size_t /*bytes_tra
|
||||
response["success"] = true;
|
||||
response["message"] = "User has been successfully authenticated!";
|
||||
MG_ASSERT(messages_.empty());
|
||||
authenticated_ = true;
|
||||
messages_.push_back(make_shared<std::string>(response.dump()));
|
||||
DoWrite();
|
||||
authenticated_ = true;
|
||||
} catch (const nlohmann::json::out_of_range &out_of_range) {
|
||||
const auto err_msg = fmt::format("Invalid JSON for authentication received: {}!", out_of_range.what());
|
||||
spdlog::error(err_msg);
|
||||
@ -213,7 +215,7 @@ void Session::OnRead(const boost::beast::error_code ec, const size_t /*bytes_tra
|
||||
DoRead();
|
||||
}
|
||||
|
||||
bool Session::IsAuthenticated() const { return authenticated_ || !auth_.HasAnyUsers(); }
|
||||
bool Session::IsAuthenticated() const { return authenticated_; }
|
||||
|
||||
void Session::DoShutdown() {
|
||||
std::visit(utils::Overloaded{[this](SSLWebSocket &ssl_ws) {
|
||||
|
@ -2314,7 +2314,7 @@ void Interpreter::Commit() {
|
||||
|
||||
reset_necessary_members();
|
||||
|
||||
SPDLOG_DEBUG("Finished comitting the transaction");
|
||||
SPDLOG_DEBUG("Finished committing the transaction");
|
||||
}
|
||||
|
||||
void Interpreter::AdvanceCommand() {
|
||||
|
@ -14,5 +14,6 @@ add_subdirectory(streams)
|
||||
add_subdirectory(temporal_types)
|
||||
add_subdirectory(write_procedures)
|
||||
add_subdirectory(module_file_manager)
|
||||
add_subdirectory(websocket)
|
||||
|
||||
copy_e2e_python_files(pytest_runner pytest_runner.sh "")
|
||||
|
@ -38,27 +38,32 @@ def wait_for_server(port, delay=0.01):
|
||||
|
||||
def extract_bolt_port(args):
|
||||
for arg_index, arg in enumerate(args):
|
||||
if arg.startswith('--bolt-port='):
|
||||
maybe_port = arg.split('=')[1]
|
||||
if arg.startswith("--bolt-port="):
|
||||
maybe_port = arg.split("=")[1]
|
||||
if not maybe_port.isdigit():
|
||||
raise Exception('Unable to read Bolt port after --bolt-port=.')
|
||||
raise Exception("Unable to read Bolt port after --bolt-port=.")
|
||||
return int(maybe_port)
|
||||
elif arg == '--bolt-port':
|
||||
elif arg == "--bolt-port":
|
||||
maybe_port = args[arg_index + 1]
|
||||
if not maybe_port.isdigit():
|
||||
raise Exception('Unable to read Bolt port after --bolt-port.')
|
||||
raise Exception("Unable to read Bolt port after --bolt-port.")
|
||||
return int(maybe_port)
|
||||
return 7687
|
||||
|
||||
|
||||
class MemgraphInstanceRunner():
|
||||
def __init__(self, binary_path=MEMGRAPH_BINARY, args=[]):
|
||||
self.host = '127.0.0.1'
|
||||
self.bolt_port = extract_bolt_port(args)
|
||||
def replace_paths(path):
|
||||
return path.replace("$PROJECT_DIR", PROJECT_DIR).replace("$SCRIPT_DIR", SCRIPT_DIR).replace("$BUILD_DIR", BUILD_DIR)
|
||||
|
||||
|
||||
class MemgraphInstanceRunner:
|
||||
def __init__(self, binary_path=MEMGRAPH_BINARY, use_ssl=False):
|
||||
self.host = "127.0.0.1"
|
||||
self.bolt_port = None
|
||||
self.binary_path = binary_path
|
||||
self.args = args
|
||||
self.args = None
|
||||
self.proc_mg = None
|
||||
self.conn = None
|
||||
self.ssl = use_ssl
|
||||
|
||||
def query(self, query):
|
||||
cursor = self.conn.cursor()
|
||||
@ -70,18 +75,21 @@ class MemgraphInstanceRunner():
|
||||
return
|
||||
self.stop()
|
||||
self.args = copy.deepcopy(args)
|
||||
self.args = [replace_paths(arg) for arg in self.args]
|
||||
self.data_directory = tempfile.TemporaryDirectory()
|
||||
args_mg = [self.binary_path,
|
||||
"--data-directory", self.data_directory.name,
|
||||
"--storage-wal-enabled",
|
||||
"--storage-snapshot-interval-sec", "300",
|
||||
"--storage-properties-on-edges"] + self.args
|
||||
args_mg = [
|
||||
self.binary_path,
|
||||
"--data-directory",
|
||||
self.data_directory.name,
|
||||
"--storage-wal-enabled",
|
||||
"--storage-snapshot-interval-sec",
|
||||
"300",
|
||||
"--storage-properties-on-edges",
|
||||
] + self.args
|
||||
self.bolt_port = extract_bolt_port(args_mg)
|
||||
self.proc_mg = subprocess.Popen(args_mg)
|
||||
wait_for_server(self.bolt_port)
|
||||
self.conn = mgclient.connect(
|
||||
host=self.host,
|
||||
port=self.bolt_port)
|
||||
self.conn = mgclient.connect(host=self.host, port=self.bolt_port, sslmode=self.ssl)
|
||||
self.conn.autocommit = True
|
||||
assert self.is_running(), "The Memgraph process died!"
|
||||
|
||||
|
@ -9,12 +9,13 @@
|
||||
# by the Apache License, Version 2.0, included in the file
|
||||
# licenses/APL.txt.
|
||||
|
||||
from argparse import ArgumentParser
|
||||
import atexit
|
||||
import logging
|
||||
import os
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
from argparse import ArgumentParser
|
||||
from pathlib import Path
|
||||
|
||||
import yaml
|
||||
|
||||
from memgraph import MemgraphInstanceRunner
|
||||
@ -36,18 +37,17 @@ def load_args():
|
||||
|
||||
def load_workloads(root_directory):
|
||||
workloads = []
|
||||
for file in Path(root_directory).rglob('*.yaml'):
|
||||
for file in Path(root_directory).rglob("*.yaml"):
|
||||
with open(file, "r") as f:
|
||||
workloads.extend(yaml.load(f, Loader=yaml.FullLoader)['workloads'])
|
||||
workloads.extend(yaml.load(f, Loader=yaml.FullLoader)["workloads"])
|
||||
return workloads
|
||||
|
||||
|
||||
def run(args):
|
||||
workloads = load_workloads(args.workloads_root_directory)
|
||||
for workload in workloads:
|
||||
workload_name = workload['name']
|
||||
if args.workload_name is not None and \
|
||||
args.workload_name != workload_name:
|
||||
workload_name = workload["name"]
|
||||
if args.workload_name is not None and args.workload_name != workload_name:
|
||||
continue
|
||||
log.info("%s STARTED.", workload_name)
|
||||
# Setup.
|
||||
@ -57,38 +57,36 @@ def run(args):
|
||||
def cleanup():
|
||||
for mg_instance in mg_instances.values():
|
||||
mg_instance.stop()
|
||||
for name, config in workload['cluster'].items():
|
||||
mg_instance = MemgraphInstanceRunner(MEMGRAPH_BINARY)
|
||||
mg_instances[name] = mg_instance
|
||||
log_file_path = os.path.join(BUILD_DIR, 'logs', config['log_file'])
|
||||
binary_args = config['args'] + ["--log-file", log_file_path]
|
||||
if 'proc' in workload:
|
||||
procdir = "--query-modules-directory=" + \
|
||||
os.path.join(BUILD_DIR, workload['proc'])
|
||||
binary_args.append(procdir)
|
||||
|
||||
for name, config in workload["cluster"].items():
|
||||
use_ssl = False
|
||||
if "ssl" in config:
|
||||
use_ssl = bool(config["ssl"])
|
||||
config.pop("ssl")
|
||||
mg_instance = MemgraphInstanceRunner(MEMGRAPH_BINARY, use_ssl)
|
||||
mg_instances[name] = mg_instance
|
||||
log_file_path = os.path.join(BUILD_DIR, "logs", config["log_file"])
|
||||
binary_args = config["args"] + ["--log-file", log_file_path]
|
||||
if "proc" in workload:
|
||||
procdir = "--query-modules-directory=" + os.path.join(BUILD_DIR, workload["proc"])
|
||||
binary_args.append(procdir)
|
||||
mg_instance.start(args=binary_args)
|
||||
for query in config['setup_queries']:
|
||||
for query in config.get("setup_queries", []):
|
||||
mg_instance.query(query)
|
||||
# Test.
|
||||
mg_test_binary = os.path.join(BUILD_DIR, workload['binary'])
|
||||
subprocess.run(
|
||||
[mg_test_binary] + workload['args'],
|
||||
check=True,
|
||||
stderr=subprocess.STDOUT)
|
||||
mg_test_binary = os.path.join(BUILD_DIR, workload["binary"])
|
||||
subprocess.run([mg_test_binary] + workload["args"], check=True, stderr=subprocess.STDOUT)
|
||||
# Validation.
|
||||
for name, config in workload['cluster'].items():
|
||||
for validation in config['validation_queries']:
|
||||
for name, config in workload["cluster"].items():
|
||||
for validation in config.get("validation_queries", []):
|
||||
mg_instance = mg_instances[name]
|
||||
data = mg_instance.query(validation['query'])[0][0]
|
||||
assert data == validation['expected']
|
||||
data = mg_instance.query(validation["query"])[0][0]
|
||||
assert data == validation["expected"]
|
||||
cleanup()
|
||||
log.info("%s PASSED.", workload_name)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
args = load_args()
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(levelname)s %(asctime)s %(name)s] %(message)s')
|
||||
logging.basicConfig(level=logging.INFO, format="%(levelname)s %(asctime)s %(name)s] %(message)s")
|
||||
run(args)
|
||||
|
10
tests/e2e/websocket/CMakeLists.txt
Normal file
10
tests/e2e/websocket/CMakeLists.txt
Normal file
@ -0,0 +1,10 @@
|
||||
find_package(gflags REQUIRED)
|
||||
find_package(Boost REQUIRED)
|
||||
|
||||
add_executable(memgraph__e2e__websocket websocket.cpp)
|
||||
target_link_libraries(memgraph__e2e__websocket mgclient mg-utils json gflags Boost::headers)
|
||||
|
||||
add_executable(memgraph__e2e__websocket_ssl websocket_ssl.cpp)
|
||||
target_link_libraries(memgraph__e2e__websocket_ssl mgclient mg-utils json gflags Boost::headers)
|
||||
file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/memgraph-selfsigned.crt DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
|
||||
file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/memgraph-selfsigned.key DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
|
309
tests/e2e/websocket/common.hpp
Normal file
309
tests/e2e/websocket/common.hpp
Normal file
@ -0,0 +1,309 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// 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.
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
|
||||
#include <gflags/gflags.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <unistd.h>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/asio/strand.hpp>
|
||||
#include <boost/beast/core.hpp>
|
||||
#include <boost/beast/core/buffers_to_string.hpp>
|
||||
#include <boost/beast/ssl/ssl_stream.hpp>
|
||||
#include <boost/beast/websocket.hpp>
|
||||
#include <json/json.hpp>
|
||||
#include <mgclient.hpp>
|
||||
|
||||
#include "utils/logging.hpp"
|
||||
|
||||
namespace beast = boost::beast;
|
||||
namespace http = beast::http;
|
||||
namespace websocket = beast::websocket;
|
||||
namespace net = boost::asio;
|
||||
using tcp = boost::asio::ip::tcp;
|
||||
namespace ssl = boost::asio::ssl;
|
||||
|
||||
constexpr std::array kSupportedLogLevels{"debug", "trace", "info", "warning", "error", "critical"};
|
||||
|
||||
struct Credentials {
|
||||
std::string_view username;
|
||||
std::string_view passsword;
|
||||
};
|
||||
|
||||
inline void Fail(beast::error_code ec, char const *what) { std::cerr << what << ": " << ec.message() << "\n"; }
|
||||
|
||||
inline std::string GetAuthenticationJSON(const Credentials &creds) {
|
||||
nlohmann::json json_creds;
|
||||
json_creds["username"] = creds.username;
|
||||
json_creds["password"] = creds.passsword;
|
||||
return json_creds.dump();
|
||||
}
|
||||
|
||||
template <bool ssl = false>
|
||||
class Session : public std::enable_shared_from_this<Session<ssl>> {
|
||||
using std::enable_shared_from_this<Session<ssl>>::shared_from_this;
|
||||
|
||||
public:
|
||||
explicit Session(net::io_context &ioc, ssl::context &ctx, std::vector<std::string> &expected_messages) requires(ssl)
|
||||
: resolver_(net::make_strand(ioc)), ws_(net::make_strand(ioc), ctx), received_messages_{expected_messages} {}
|
||||
|
||||
explicit Session(net::io_context &ioc, std::vector<std::string> &expected_messages) requires(!ssl)
|
||||
: resolver_(net::make_strand(ioc)), ws_(net::make_strand(ioc)), received_messages_{expected_messages} {}
|
||||
|
||||
template <typename... Args>
|
||||
explicit Session(Credentials creds, Args &&...args) : Session<ssl>(std::forward<Args>(args)...) {
|
||||
creds_.emplace(creds);
|
||||
}
|
||||
|
||||
void Run(std::string_view host, std::string_view port) {
|
||||
host_ = host;
|
||||
resolver_.async_resolve(host, port, beast::bind_front_handler(&Session::OnResolve, shared_from_this()));
|
||||
}
|
||||
|
||||
void OnResolve(beast::error_code ec, tcp::resolver::results_type results) {
|
||||
if (ec) {
|
||||
return Fail(ec, "resolve");
|
||||
}
|
||||
|
||||
beast::get_lowest_layer(ws_).expires_after(std::chrono::seconds(30));
|
||||
beast::get_lowest_layer(ws_).async_connect(results,
|
||||
beast::bind_front_handler(&Session::OnConnect, shared_from_this()));
|
||||
}
|
||||
|
||||
void OnConnect(beast::error_code ec, tcp::resolver::results_type::endpoint_type ep) {
|
||||
if (ec) {
|
||||
return Fail(ec, "connect");
|
||||
}
|
||||
|
||||
host_ = fmt::format("{}:{}", host_, ep.port());
|
||||
|
||||
beast::get_lowest_layer(ws_).expires_after(std::chrono::seconds(30));
|
||||
|
||||
if constexpr (ssl) {
|
||||
if (!SSL_set_tlsext_host_name(ws_.next_layer().native_handle(), host_.c_str())) {
|
||||
ec = beast::error_code(static_cast<int>(::ERR_get_error()), net::error::get_ssl_category());
|
||||
return Fail(ec, "connect");
|
||||
}
|
||||
ws_.next_layer().async_handshake(ssl::stream_base::client,
|
||||
beast::bind_front_handler(&Session::OnSSLHandshake, shared_from_this()));
|
||||
} else {
|
||||
ws_.set_option(websocket::stream_base::timeout::suggested(beast::role_type::client));
|
||||
|
||||
ws_.set_option(websocket::stream_base::decorator([](websocket::request_type &req) {
|
||||
req.set(http::field::user_agent, std::string(BOOST_BEAST_VERSION_STRING) + " websocket-client-async");
|
||||
}));
|
||||
|
||||
host_ = fmt::format("{}:{}", host_, ep.port());
|
||||
|
||||
ws_.async_handshake(host_, "/", beast::bind_front_handler(&Session::OnHandshake, shared_from_this()));
|
||||
}
|
||||
}
|
||||
|
||||
void OnHandshake(beast::error_code ec) {
|
||||
if (ec) {
|
||||
return Fail(ec, "handshake");
|
||||
}
|
||||
if (creds_) {
|
||||
ws_.async_write(net::buffer(GetAuthenticationJSON(*creds_)),
|
||||
beast::bind_front_handler(&Session::OnWrite, shared_from_this()));
|
||||
} else {
|
||||
ws_.async_read(buffer_, beast::bind_front_handler(&Session::OnRead, shared_from_this()));
|
||||
}
|
||||
}
|
||||
|
||||
void OnSSLHandshake(beast::error_code ec) {
|
||||
if (ec) {
|
||||
return Fail(ec, "ssl_handshake");
|
||||
}
|
||||
|
||||
beast::get_lowest_layer(ws_).expires_never();
|
||||
ws_.set_option(websocket::stream_base::timeout::suggested(beast::role_type::client));
|
||||
ws_.set_option(websocket::stream_base::decorator([](websocket::request_type &req) {
|
||||
req.set(http::field::user_agent, std::string(BOOST_BEAST_VERSION_STRING) + " websocket-client-async-ssl");
|
||||
}));
|
||||
|
||||
ws_.async_handshake(host_, "/", beast::bind_front_handler(&Session::OnHandshake, shared_from_this()));
|
||||
}
|
||||
|
||||
void OnWrite(beast::error_code ec, std::size_t bytes_transferred) {
|
||||
boost::ignore_unused(bytes_transferred);
|
||||
|
||||
if (ec) {
|
||||
return Fail(ec, "write");
|
||||
}
|
||||
|
||||
ws_.async_read(buffer_, beast::bind_front_handler(&Session::OnRead, shared_from_this()));
|
||||
}
|
||||
|
||||
void OnRead(beast::error_code ec, std::size_t bytes_transferred) {
|
||||
boost::ignore_unused(bytes_transferred);
|
||||
|
||||
if (ec) {
|
||||
return Fail(ec, "read");
|
||||
}
|
||||
|
||||
received_messages_.push_back(boost::beast::buffers_to_string(buffer_.data()));
|
||||
buffer_.clear();
|
||||
|
||||
ws_.async_read(buffer_, beast::bind_front_handler(&Session::OnRead, shared_from_this()));
|
||||
}
|
||||
|
||||
void OnClose(beast::error_code ec) {
|
||||
if (ec) {
|
||||
return Fail(ec, "close");
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
using InternalStream = std::conditional_t<ssl, beast::ssl_stream<beast::tcp_stream>, beast::tcp_stream>;
|
||||
tcp::resolver resolver_;
|
||||
websocket::stream<InternalStream> ws_;
|
||||
beast::flat_buffer buffer_;
|
||||
std::string host_;
|
||||
std::vector<std::string> &received_messages_;
|
||||
std::optional<Credentials> creds_{std::nullopt};
|
||||
};
|
||||
|
||||
std::unique_ptr<mg::Client> GetBoltClient(const uint16_t bolt_port, const bool use_ssl) {
|
||||
auto client = mg::Client::Connect({.host = "127.0.0.1", .port = bolt_port, .use_ssl = use_ssl});
|
||||
MG_ASSERT(client, "Failed to connect!");
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
inline void CleanDatabase(std::unique_ptr<mg::Client> &client) {
|
||||
MG_ASSERT(client->Execute("MATCH (n) DETACH DELETE n;"));
|
||||
client->DiscardAll();
|
||||
}
|
||||
|
||||
inline void AddUser(std::unique_ptr<mg::Client> &client) {
|
||||
MG_ASSERT(client->Execute("CREATE USER test IDENTIFIED BY 'testing';"));
|
||||
client->DiscardAll();
|
||||
}
|
||||
|
||||
inline void AddVertex(std::unique_ptr<mg::Client> &client) {
|
||||
MG_ASSERT(client->Execute("CREATE ();"));
|
||||
client->DiscardAll();
|
||||
}
|
||||
inline void AddConnectedVertices(std::unique_ptr<mg::Client> &client) {
|
||||
MG_ASSERT(client->Execute("CREATE ()-[:TO]->();"));
|
||||
client->DiscardAll();
|
||||
}
|
||||
|
||||
inline void RunQueries(std::unique_ptr<mg::Client> &mg_client) {
|
||||
CleanDatabase(mg_client);
|
||||
AddVertex(mg_client);
|
||||
AddVertex(mg_client);
|
||||
AddVertex(mg_client);
|
||||
AddConnectedVertices(mg_client);
|
||||
CleanDatabase(mg_client);
|
||||
}
|
||||
|
||||
inline void AssertAuthMessage(auto &json_message, const bool success = true) {
|
||||
MG_ASSERT(json_message.at("message").is_string(), "Event is not a string!");
|
||||
MG_ASSERT(json_message.at("success").is_boolean(), "Success is not a boolean!");
|
||||
MG_ASSERT(json_message.at("success").template get<bool>() == success, "Success does not match expected!");
|
||||
}
|
||||
|
||||
inline void AssertLogMessage(const std::string &log_message) {
|
||||
const auto json_message = nlohmann::json::parse(log_message);
|
||||
if (json_message.contains("success")) {
|
||||
spdlog::info("Received auth message: {}", json_message.dump());
|
||||
AssertAuthMessage(json_message);
|
||||
return;
|
||||
}
|
||||
MG_ASSERT(json_message.at("event").is_string(), "Event is not a string!");
|
||||
MG_ASSERT(json_message.at("event").get<std::string>() == "log", "Event is not equal to `log`!");
|
||||
MG_ASSERT(json_message.at("level").is_string(), "Level is not a string!");
|
||||
MG_ASSERT(std::ranges::count(kSupportedLogLevels, json_message.at("level")) == 1);
|
||||
MG_ASSERT(json_message.at("message").is_string(), "Message is not a string!");
|
||||
}
|
||||
|
||||
template <typename TWebsocketClient>
|
||||
void TestWebsocketWithoutAnyUsers(std::unique_ptr<mg::Client> &mg_client) {
|
||||
spdlog::info("Starting websocket connection without any users.");
|
||||
auto websocket_client = TWebsocketClient();
|
||||
websocket_client.Connect("127.0.0.1", "7444");
|
||||
|
||||
RunQueries(mg_client);
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
|
||||
websocket_client.Close();
|
||||
websocket_client.AwaitClose();
|
||||
const auto received_messages = websocket_client.GetReceivedMessages();
|
||||
spdlog::info("Received {} messages.", received_messages.size());
|
||||
MG_ASSERT(!received_messages.empty(), "There are no received messages!");
|
||||
std::ranges::for_each(received_messages, AssertLogMessage);
|
||||
|
||||
spdlog::info("Finishing websocket connection without any users.");
|
||||
}
|
||||
|
||||
template <typename TWebsocketClient>
|
||||
void TestWebsocketWithAuthentication(std::unique_ptr<mg::Client> &mg_client) {
|
||||
spdlog::info("Starting websocket connection with users.");
|
||||
AddUser(mg_client);
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
auto websocket_client = TWebsocketClient({"test", "testing"});
|
||||
websocket_client.Connect("127.0.0.1", "7444");
|
||||
|
||||
RunQueries(mg_client);
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
|
||||
websocket_client.Close();
|
||||
websocket_client.AwaitClose();
|
||||
const auto received_messages = websocket_client.GetReceivedMessages();
|
||||
spdlog::info("Received {} messages.", received_messages.size());
|
||||
|
||||
MG_ASSERT(!received_messages.empty(), "There are no received messages!");
|
||||
std::ranges::for_each(received_messages, AssertLogMessage);
|
||||
|
||||
spdlog::info("Finishing websocket connection with users.");
|
||||
}
|
||||
|
||||
template <typename TWebsocketClient>
|
||||
void TestWebsocketWithoutBeingAuthorized(std::unique_ptr<mg::Client> &mg_client) {
|
||||
spdlog::info("Starting websocket connection with users but without being authenticated.");
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
auto websocket_client = TWebsocketClient({"wrong", "credentials"});
|
||||
websocket_client.Connect("127.0.0.1", "7444");
|
||||
|
||||
RunQueries(mg_client);
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
|
||||
websocket_client.Close();
|
||||
websocket_client.AwaitClose();
|
||||
const auto received_messages = websocket_client.GetReceivedMessages();
|
||||
spdlog::info("Received {} messages.", received_messages.size());
|
||||
|
||||
MG_ASSERT(received_messages.size() == 1, "There must be only one message received!");
|
||||
if (!received_messages.empty()) {
|
||||
auto json_message = nlohmann::json::parse(received_messages[0]);
|
||||
AssertAuthMessage(json_message, false);
|
||||
}
|
||||
spdlog::info("Finishing websocket connection with users but without being authenticated.");
|
||||
}
|
||||
|
||||
template <typename TWebsocketClient>
|
||||
void RunTestCases(std::unique_ptr<mg::Client> &mg_client) {
|
||||
TestWebsocketWithoutAnyUsers<TWebsocketClient>(mg_client);
|
||||
TestWebsocketWithAuthentication<TWebsocketClient>(mg_client);
|
||||
TestWebsocketWithoutBeingAuthorized<TWebsocketClient>(mg_client);
|
||||
}
|
23
tests/e2e/websocket/memgraph-selfsigned.crt
Normal file
23
tests/e2e/websocket/memgraph-selfsigned.crt
Normal file
@ -0,0 +1,23 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDwTCCAqmgAwIBAgIUYJ/LCgAVvJ3t/8PqdnhJFx/dD5UwDQYJKoZIhvcNAQEL
|
||||
BQAwcDELMAkGA1UEBhMCR0IxDzANBgNVBAgMBkxvbmRvbjEPMA0GA1UEBwwGTG9u
|
||||
ZG9uMRYwFAYDVQQKDA1NZW1ncmFwaCBMdGQuMRQwEgYDVQQLDAtNZW1ncmFwaCBE
|
||||
QjERMA8GA1UEAwwITWVtZ3JhcGgwHhcNMjIwMjEwMTk1NjEzWhcNMzIwMjA4MTk1
|
||||
NjEzWjBwMQswCQYDVQQGEwJHQjEPMA0GA1UECAwGTG9uZG9uMQ8wDQYDVQQHDAZM
|
||||
b25kb24xFjAUBgNVBAoMDU1lbWdyYXBoIEx0ZC4xFDASBgNVBAsMC01lbWdyYXBo
|
||||
IERCMREwDwYDVQQDDAhNZW1ncmFwaDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
|
||||
AQoCggEBAL0f+jPohig22qiySSTYoTbaX8f2kPnf+/lW09nyrKZbb+NADRjcbzqa
|
||||
kIOm1p5XdPNRCDRkTRZFscdibQFt4QjaD/81zSdCqZ32p02hz7NC6Kdh6dI35bVV
|
||||
DavJ2F3yOfcCU38sr3lPP7BSh/frFRMWb87QjYHZrpm8d8lvW5V0Fyh8tk/YD5sI
|
||||
lgrlzBKQJCbDv0IDgHkrvLRWtEQLDqWXyQVvTGoOifu+KqbwuFXAKYIMzAcMAmb2
|
||||
gQuc+nDMqk/kpAMh3xRt+swDEBntMSe0HTnbbbajWz3EMs4Wu6qp9x8lxi6O+q/K
|
||||
5Mm9rbB8oSvdnPpOfDLnlt5uxb8aHtMCAwEAAaNTMFEwHQYDVR0OBBYEFE5nAFXR
|
||||
1jQpal+s8tL5A6uCW8GeMB8GA1UdIwQYMBaAFE5nAFXR1jQpal+s8tL5A6uCW8Ge
|
||||
MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBALJFDLcKysFZt9Sr
|
||||
SnsItVrl/xVqxZUDSqoQqIIyRxBjKjpTilUSBK/q/dfSM049GBK81mW7wrqtgv++
|
||||
yfiA6jKEaUa6HDN/5cOuSbjTUvdXIfHld3zL51UdGu9mpw1RIGQcjQcWS22Hbrus
|
||||
GpxLuj7izP3h9JH9sTNC0XaO572T9cjEw7KMLw3vgQ5GSrevXjJx7+cxPoor8K8b
|
||||
zfEu+Q38PsaFl7WYQChQI9hJr/Nzxnvppd59wEEB/NQiBxlJnM0FqDNErFrNTHfx
|
||||
1w52tJQk5skKaa7kz70W9jm+VrKR5DeaHfk+aYWPRazDLzD4q7ggp8Z3CNFyCibC
|
||||
vvT4VhA=
|
||||
-----END CERTIFICATE-----
|
28
tests/e2e/websocket/memgraph-selfsigned.key
Normal file
28
tests/e2e/websocket/memgraph-selfsigned.key
Normal file
@ -0,0 +1,28 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC9H/oz6IYoNtqo
|
||||
skkk2KE22l/H9pD53/v5VtPZ8qymW2/jQA0Y3G86mpCDptaeV3TzUQg0ZE0WRbHH
|
||||
Ym0BbeEI2g//Nc0nQqmd9qdNoc+zQuinYenSN+W1VQ2rydhd8jn3AlN/LK95Tz+w
|
||||
Uof36xUTFm/O0I2B2a6ZvHfJb1uVdBcofLZP2A+bCJYK5cwSkCQmw79CA4B5K7y0
|
||||
VrRECw6ll8kFb0xqDon7viqm8LhVwCmCDMwHDAJm9oELnPpwzKpP5KQDId8UbfrM
|
||||
AxAZ7TEntB052222o1s9xDLOFruqqfcfJcYujvqvyuTJva2wfKEr3Zz6Tnwy55be
|
||||
bsW/Gh7TAgMBAAECggEAKrxEFGCxl/q1NDN9NbdBzpxP0maT4fLMBE6rdm9QthKp
|
||||
WMeLbhe5hpuQlj8n9gW7JpQj73rOtH6xKmXCTfVfSVnFDZ1Bq2Jz437t1SwAO/id
|
||||
i90jMd5xqKT8ftoQa0Il+PvslCsrx5Sxxo0PrmTPkU2bnDciErp2qsScqMbiAgYi
|
||||
q+eUuPHqc1bFPQYdOufJFrDhKoj/wBdyAN7qLXk0CSk9uozKseaQkv6vCwn1k2W0
|
||||
cw9k9nwcebD1rZPpySLFXjDwLn/nprfxViRZWBYetOxENap6ohL6ADWXp004bOGp
|
||||
zAWJ2zq3I97I7U/KMza2nvy2fK1F5CoTrTEqRbPykQKBgQDxTeF2mBY70aXRCG68
|
||||
2Jkofg6//yDmN+//HHbUJP4tmV0XrIdu0pyACBUmL1g5zfdwDWQJyDk4Qv5lT+Kt
|
||||
+0sN43w8KkBWigJih6vQoUw/HRsx5WDVUjfYmCFODsmcrETnA2PIglJ6ssFtq0Rk
|
||||
+k783kaVWdB/WpgLQB4yYs4fGwKBgQDIpJS0IpzuMkulrBgdtmCj1Xr3xNgoCho4
|
||||
ShXce/6pGfesfkm9OAApoPkbQYXHglEM+ON23B1UtvxawGtlP1ZQ8zRz0cHg7z5s
|
||||
aK7dwj6+Kbs2xUw+hqeQNsQRurSEnvhegmsOym0sU9o9xvA5vcBbjFXQ2wvv2uMS
|
||||
1Lx9LxIiqQKBgQDFmILB/HRoc5qW+5LCrZNtZxxo+GBWZQPwIbzkp45EhMuIcU1N
|
||||
4MUqynOXGznhy9mNNknvALhqa+Gp0KrM0XR6YQgAtJCOzOB7EqINa6fmHs2AJFcR
|
||||
GAyHHrxXESDjXOXnTg1NyfoEY5ClX5PYJGi+BL5D2pIzV1oeDFkt5V7odQKBgQCA
|
||||
AI9lxFc9lxSvovTXr8xDeSQ0AX6tPJSxqIH94ZT7qLdbck30y08/P5TskIaaW0b9
|
||||
8aKb551GuF6SPwPE2f1lM2MZKI55w+edHcPBcfS5OMJZFNGpahpoZKf9b5FOsmRd
|
||||
VvKZwwaXKv+mPc5v5+BaB1OQJM6evJP1JYcCjg8R0QKBgF/fepOeeNwv5nKD567G
|
||||
3M5LEg7tNXfhz9bDODIXRoU75lP3le4PynhudL9SESDUZ/sVHTeNoddf5NEQVwjr
|
||||
5TrPyjPua2S3o4WJ8n74MuQhwhp51m1sIMHzsEiVNWOu2DlK7sxxhZBkdfXwbGQg
|
||||
VMdksPEMVdO2AFp0jrh7ax0r
|
||||
-----END PRIVATE KEY-----
|
68
tests/e2e/websocket/websocket.cpp
Normal file
68
tests/e2e/websocket/websocket.cpp
Normal file
@ -0,0 +1,68 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// 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.
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <gflags/gflags.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <unistd.h>
|
||||
#include <mgclient.hpp>
|
||||
|
||||
#include "common.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
|
||||
DEFINE_uint64(bolt_port, 7687, "Bolt port");
|
||||
|
||||
class WebsocketClient {
|
||||
public:
|
||||
WebsocketClient() : session_{std::make_shared<Session<false>>(ioc_, received_messages_)} {}
|
||||
|
||||
explicit WebsocketClient(Credentials creds)
|
||||
: session_{std::make_shared<Session<false>>(creds, ioc_, received_messages_)} {}
|
||||
|
||||
void Connect(const std::string_view host, const std::string_view port) {
|
||||
session_->Run(host, port);
|
||||
bg_thread_ = std::jthread([this]() { ioc_.run(); });
|
||||
}
|
||||
|
||||
void Close() { ioc_.stop(); }
|
||||
|
||||
void AwaitClose() {
|
||||
MG_ASSERT(bg_thread_.joinable());
|
||||
bg_thread_.join();
|
||||
}
|
||||
|
||||
std::vector<std::string> GetReceivedMessages() { return received_messages_; }
|
||||
|
||||
private:
|
||||
std::vector<std::string> received_messages_;
|
||||
net::io_context ioc_;
|
||||
std::jthread bg_thread_;
|
||||
std::shared_ptr<Session<false>> session_;
|
||||
};
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
google::SetUsageMessage("Memgraph E2E websocket!");
|
||||
gflags::ParseCommandLineFlags(&argc, &argv, true);
|
||||
MG_ASSERT(FLAGS_bolt_port != 0);
|
||||
logging::RedirectToStderr();
|
||||
|
||||
mg::Client::Init();
|
||||
auto mg_client = GetBoltClient(static_cast<uint16_t>(FLAGS_bolt_port), false);
|
||||
|
||||
RunTestCases<WebsocketClient>(mg_client);
|
||||
|
||||
return 0;
|
||||
}
|
76
tests/e2e/websocket/websocket_ssl.cpp
Normal file
76
tests/e2e/websocket/websocket_ssl.cpp
Normal file
@ -0,0 +1,76 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// 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.
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <thread>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <fmt/core.h>
|
||||
#include <gflags/gflags.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "common.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
|
||||
DEFINE_uint64(bolt_port, 7687, "Bolt port");
|
||||
|
||||
class WebsocketSSLClient {
|
||||
public:
|
||||
WebsocketSSLClient() : session_{std::make_shared<Session<true>>(ioc_, ctx_, received_messages_)} {}
|
||||
|
||||
explicit WebsocketSSLClient(Credentials creds) {
|
||||
session_ = std::make_shared<Session<true>>(creds, ioc_, ctx_, received_messages_);
|
||||
}
|
||||
|
||||
void Connect(const std::string_view host, const std::string_view port) {
|
||||
session_->Run(host, port);
|
||||
bg_thread_ = std::jthread([this]() { ioc_.run(); });
|
||||
}
|
||||
|
||||
void Close() { ioc_.stop(); }
|
||||
|
||||
void AwaitClose() {
|
||||
MG_ASSERT(bg_thread_.joinable());
|
||||
bg_thread_.join();
|
||||
}
|
||||
|
||||
std::vector<std::string> GetReceivedMessages() { return received_messages_; }
|
||||
|
||||
private:
|
||||
std::vector<std::string> received_messages_;
|
||||
ssl::context ctx_{ssl::context::tlsv12_client};
|
||||
net::io_context ioc_;
|
||||
std::jthread bg_thread_;
|
||||
std::shared_ptr<Session<true>> session_;
|
||||
};
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
google::SetUsageMessage("Memgraph E2E websocket SSL!");
|
||||
gflags::ParseCommandLineFlags(&argc, &argv, true);
|
||||
MG_ASSERT(FLAGS_bolt_port != 0);
|
||||
logging::RedirectToStderr();
|
||||
|
||||
auto mg_client = GetBoltClient(static_cast<uint16_t>(FLAGS_bolt_port), true);
|
||||
mg::Client::Init();
|
||||
|
||||
RunTestCases<WebsocketSSLClient>(mg_client);
|
||||
|
||||
return 0;
|
||||
}
|
34
tests/e2e/websocket/workloads.yaml
Normal file
34
tests/e2e/websocket/workloads.yaml
Normal file
@ -0,0 +1,34 @@
|
||||
cert_file: &cert_file "$PROJECT_DIR/tests/e2e/websocket/memgraph-selfsigned.crt"
|
||||
key_file: &key_file "$PROJECT_DIR/tests/e2e/websocket/memgraph-selfsigned.key"
|
||||
bolt_port: &bolt_port "7687"
|
||||
template_cluster: &template_cluster
|
||||
cluster:
|
||||
websocket:
|
||||
args: ["--bolt-port=7687", "--log-level=TRACE", "--"]
|
||||
log_file: "websocket-e2e.log"
|
||||
template_cluster_ssl: &template_cluster_ssl
|
||||
cluster:
|
||||
websocket:
|
||||
args:
|
||||
[
|
||||
"--bolt-port",
|
||||
*bolt_port,
|
||||
"--log-level=TRACE",
|
||||
"--bolt-cert-file",
|
||||
*cert_file,
|
||||
"--bolt-key-file",
|
||||
*key_file,
|
||||
"--",
|
||||
]
|
||||
log_file: "websocket-ssl-e2e.log"
|
||||
ssl: true
|
||||
|
||||
workloads:
|
||||
- name: "Websocket"
|
||||
binary: "tests/e2e/websocket/memgraph__e2e__websocket"
|
||||
args: ["--bolt-port", *bolt_port]
|
||||
<<: *template_cluster
|
||||
- name: "Websocket SSL"
|
||||
binary: "tests/e2e/websocket/memgraph__e2e__websocket_ssl"
|
||||
args: ["--bolt-port", *bolt_port]
|
||||
<<: *template_cluster_ssl
|
Loading…
Reference in New Issue
Block a user