From 2187e47a6007e80f1f6601747753719d3dc2d53b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20Br=C3=BCbach?= <44814898+BruebachL@users.noreply.github.com> Date: Mon, 9 Nov 2026 16:02:20 +0100 Subject: [PATCH] [Tests] Add a shared in-memory server test fixture Add tests/support/server_test_fixture as a built-but-unshipped static target: an InMemoryServer_DatabaseInterface with configurable auth, a user table, session bookkeeping, ban state, id counters, and chat-log capture, plus FakeServer and ServerTestFixture. Migrate server_game_join_test to it, removing the first private copy of the mock. --- tests/CMakeLists.txt | 8 ++ tests/server_game_join_test.cpp | 46 +-------- tests/support/server_test_fixture.cpp | 139 ++++++++++++++++++++++++++ tests/support/server_test_fixture.h | 117 ++++++++++++++++++++++ 4 files changed, 266 insertions(+), 44 deletions(-) create mode 100644 tests/support/server_test_fixture.cpp create mode 100644 tests/support/server_test_fixture.h diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 6eb59fc72..bc6d7b8e8 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -171,6 +171,11 @@ endfunction() # --- Unit tests ------------------------------------------------------------------ +# Test-only server double and fixture, shared by the server tests. Built, never installed. +add_library(testsupport_server STATIC support/server_test_fixture.cpp) +target_link_libraries(testsupport_server PUBLIC libcockatrice_network_server_remote) +target_include_directories(testsupport_server PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) + cockatrice_add_test(NAME image_request_queue_test SOURCES image_request_queue_test.cpp) cockatrice_add_test(NAME image_redirect_chain_test SOURCES image_redirect_chain_test.cpp) cockatrice_add_test(NAME expression_test SOURCES expression_test.cpp LIBS libcockatrice_utility) @@ -377,6 +382,9 @@ cockatrice_add_test( LIBS libcockatrice_network_server_remote libcockatrice_rng + testsupport_server + INCLUDE_DIRS + ${CMAKE_CURRENT_SOURCE_DIR} LABELS server ) diff --git a/tests/server_game_join_test.cpp b/tests/server_game_join_test.cpp index 93895ab5c..d9d5fb80a 100644 --- a/tests/server_game_join_test.cpp +++ b/tests/server_game_join_test.cpp @@ -7,9 +7,8 @@ #include "game/role_info.h" #include "game/server_game.h" #include "libcockatrice/protocol/pb/response.pb.h" -#include "server.h" -#include "server_database_interface.h" #include "server_room.h" +#include "support/server_test_fixture.h" #include #include @@ -23,50 +22,9 @@ RNG_Abstract *rng = nullptr; // referenced by the server_remote library namespace { -class MockDatabaseInterface : public Server_DatabaseInterface -{ -public: - AuthenticationResult checkUserPassword(Server_ProtocolHandler *, - const QString &, - const QString &, - const QString &, - QString &, - int &, - bool) override - { - return NotLoggedIn; - } - int getNextReplayId() override - { - return 1; - } - int getNextGameId() override - { - return 1; - } - int getActiveUserCount(QString) override - { - return 0; - } - ServerInfo_User getUserData(const QString &, bool) override - { - return ServerInfo_User(); - } -}; - -class FakeServer : public Server -{ -public: - FakeServer() - { - setDatabaseInterface(new MockDatabaseInterface()); - } -}; - -class GameJoinOverrideTest : public ::testing::Test +class GameJoinOverrideTest : public ServerTestFixture { protected: - FakeServer server; Server_Room room{0, 0, "", "", "", "", false, "", {}, &server}; ServerInfo_User creator; ServerInfo_User plainUser; diff --git a/tests/support/server_test_fixture.cpp b/tests/support/server_test_fixture.cpp new file mode 100644 index 000000000..211e66c68 --- /dev/null +++ b/tests/support/server_test_fixture.cpp @@ -0,0 +1,139 @@ +/** @file server_test_fixture.cpp + * @brief Shared in-memory server database double and gtest fixture for server tests. + * @ingroup Tests + */ + +#include "support/server_test_fixture.h" + +#include "server_room.h" + +#include + +InMemoryServer_DatabaseInterface::InMemoryServer_DatabaseInterface(QObject *parent) : Server_DatabaseInterface(parent) +{ +} + +void InMemoryServer_DatabaseInterface::addUser(const ServerInfo_User &user) +{ + users.insert(QString::fromStdString(user.name()), user); +} + +AuthenticationResult InMemoryServer_DatabaseInterface::checkUserPassword(Server_ProtocolHandler *, + const QString &, + const QString &, + const QString &, + QString &, + int &, + bool) +{ + return authenticationResult; +} + +bool InMemoryServer_DatabaseInterface::checkUserIsBanned(const QString &, + const QString &, + const QString &, + QString &reason, + int &seconds) +{ + reason = banReason; + seconds = banSecondsRemaining; + return userIsBanned; +} + +bool InMemoryServer_DatabaseInterface::checkUserIsBanned(Server_ProtocolHandler *, QString &reason, int &seconds) +{ + reason = banReason; + seconds = banSecondsRemaining; + return userIsBanned; +} + +bool InMemoryServer_DatabaseInterface::activeUserExists(const QString &user) +{ + for (const QString &sessionUser : std::as_const(sessions)) { + if (sessionUser == user) { + return true; + } + } + return false; +} + +ServerInfo_User InMemoryServer_DatabaseInterface::getUserData(const QString &name, bool withId) +{ + ServerInfo_User user = users.value(name); + if (!withId) { + user.clear_id(); + } + return user; +} + +qint64 InMemoryServer_DatabaseInterface::startSession(const QString &userName, + const QString &, + const QString &, + const QString &) +{ + const qint64 sessionId = nextSessionId++; + sessions.insert(sessionId, userName); + return sessionId; +} + +void InMemoryServer_DatabaseInterface::endSession(qint64 sessionId) +{ + sessions.remove(sessionId); +} + +bool InMemoryServer_DatabaseInterface::userSessionExists(const QString &userName) +{ + return activeUserExists(userName); +} + +bool InMemoryServer_DatabaseInterface::getRequireRegistration() +{ + return requireRegistration; +} + +int InMemoryServer_DatabaseInterface::getNextGameId() +{ + return nextGameId; +} + +int InMemoryServer_DatabaseInterface::getNextReplayId() +{ + return nextReplayId; +} + +int InMemoryServer_DatabaseInterface::getActiveUserCount(QString) +{ + return activeUserCount; +} + +void InMemoryServer_DatabaseInterface::logMessage(const int, + const QString &senderName, + const QString &, + const QString &message, + LogMessage_TargetType targetType, + const int targetId, + const QString &targetName, + const QString &channel) +{ + logEntries.append(LogEntry{.senderName = senderName, + .message = message, + .targetType = targetType, + .targetId = targetId, + .targetName = targetName, + .channel = channel}); +} + +FakeServer::FakeServer() +{ + setDatabaseInterface(new InMemoryServer_DatabaseInterface(this)); +} + +InMemoryServer_DatabaseInterface *FakeServer::database() const +{ + return static_cast(getDatabaseInterface()); +} + +void FakeServer::insertRoom(Server_Room *newRoom) +{ + addRoom(newRoom); +} diff --git a/tests/support/server_test_fixture.h b/tests/support/server_test_fixture.h new file mode 100644 index 000000000..a69aa988c --- /dev/null +++ b/tests/support/server_test_fixture.h @@ -0,0 +1,117 @@ +/** @file server_test_fixture.h + * @brief Shared in-memory server database double and gtest fixture for server tests. + * @ingroup Tests + */ + +#ifndef COCKATRICE_TESTS_SUPPORT_SERVER_TEST_FIXTURE_H +#define COCKATRICE_TESTS_SUPPORT_SERVER_TEST_FIXTURE_H + +#include "server.h" +#include "server_database_interface.h" + +#include +#include +#include +#include +#include + +class Server_Room; + +/** + * @brief A Server_DatabaseInterface backed by in-memory state. + * + * Only the methods the server tests need are overridden; everything else keeps + * the abstract interface's defaults. The public members are the knobs a test + * turns to describe the state it wants. + */ +class InMemoryServer_DatabaseInterface : public Server_DatabaseInterface +{ +public: + explicit InMemoryServer_DatabaseInterface(QObject *parent = nullptr); + + struct LogEntry + { + QString senderName; + QString message; + LogMessage_TargetType targetType; + int targetId; + QString targetName; + QString channel; + }; + + AuthenticationResult authenticationResult = NotLoggedIn; + QHash users; + bool userIsBanned = false; + QString banReason; + int banSecondsRemaining = 0; + int activeUserCount = 0; + int nextGameId = 1; + int nextReplayId = 1; + bool requireRegistration = false; + QList logEntries; + + void addUser(const ServerInfo_User &user); + + AuthenticationResult checkUserPassword(Server_ProtocolHandler *handler, + const QString &user, + const QString &password, + const QString &clientId, + QString &reasonStr, + int &secondsLeft, + bool passwordNeedsHash) override; + bool checkUserIsBanned(const QString &ipAddress, + const QString &userName, + const QString &clientId, + QString &reason, + int &seconds) override; + bool checkUserIsBanned(Server_ProtocolHandler *session, QString &reason, int &seconds) override; + bool activeUserExists(const QString &user) override; + ServerInfo_User getUserData(const QString &name, bool withId = false) override; + qint64 startSession(const QString &userName, + const QString &address, + const QString &clientId, + const QString &connectionType) override; + void endSession(qint64 sessionId) override; + bool userSessionExists(const QString &userName) override; + bool getRequireRegistration() override; + int getNextGameId() override; + int getNextReplayId() override; + int getActiveUserCount(QString connectionType = QString()) override; + void logMessage(const int senderId, + const QString &senderName, + const QString &senderIp, + const QString &message, + LogMessage_TargetType targetType, + const int targetId, + const QString &targetName, + const QString &channel = QString()) override; + +private: + QHash sessions; + qint64 nextSessionId = 1; +}; + +/// A Server that always has its own InMemoryServer_DatabaseInterface attached. +class FakeServer : public Server +{ +public: + FakeServer(); + + InMemoryServer_DatabaseInterface *database() const; + + void insertRoom(Server_Room *newRoom); +}; + +/// A gtest fixture with a FakeServer and direct access to its database. +class ServerTestFixture : public ::testing::Test +{ +protected: + FakeServer server; + + InMemoryServer_DatabaseInterface *database() const + { + return server.database(); + } +}; + +#endif