[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.
This commit is contained in:
Lukas Brübach 2026-11-09 16:02:20 +01:00
parent 3b379618c3
commit 2187e47a60
4 changed files with 266 additions and 44 deletions

View file

@ -171,6 +171,11 @@ endfunction()
# --- Unit tests ------------------------------------------------------------------ # --- 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_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 image_redirect_chain_test SOURCES image_redirect_chain_test.cpp)
cockatrice_add_test(NAME expression_test SOURCES expression_test.cpp LIBS libcockatrice_utility) cockatrice_add_test(NAME expression_test SOURCES expression_test.cpp LIBS libcockatrice_utility)
@ -377,6 +382,9 @@ cockatrice_add_test(
LIBS LIBS
libcockatrice_network_server_remote libcockatrice_network_server_remote
libcockatrice_rng libcockatrice_rng
testsupport_server
INCLUDE_DIRS
${CMAKE_CURRENT_SOURCE_DIR}
LABELS LABELS
server server
) )

View file

@ -7,9 +7,8 @@
#include "game/role_info.h" #include "game/role_info.h"
#include "game/server_game.h" #include "game/server_game.h"
#include "libcockatrice/protocol/pb/response.pb.h" #include "libcockatrice/protocol/pb/response.pb.h"
#include "server.h"
#include "server_database_interface.h"
#include "server_room.h" #include "server_room.h"
#include "support/server_test_fixture.h"
#include <QList> #include <QList>
#include <QString> #include <QString>
@ -23,50 +22,9 @@ RNG_Abstract *rng = nullptr; // referenced by the server_remote library
namespace namespace
{ {
class MockDatabaseInterface : public Server_DatabaseInterface class GameJoinOverrideTest : public ServerTestFixture
{
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
{ {
protected: protected:
FakeServer server;
Server_Room room{0, 0, "", "", "", "", false, "", {}, &server}; Server_Room room{0, 0, "", "", "", "", false, "", {}, &server};
ServerInfo_User creator; ServerInfo_User creator;
ServerInfo_User plainUser; ServerInfo_User plainUser;

View file

@ -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 <utility>
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<InMemoryServer_DatabaseInterface *>(getDatabaseInterface());
}
void FakeServer::insertRoom(Server_Room *newRoom)
{
addRoom(newRoom);
}

View file

@ -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 <QHash>
#include <QList>
#include <QString>
#include <gtest/gtest.h>
#include <libcockatrice/protocol/pb/serverinfo_user.pb.h>
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<QString, ServerInfo_User> users;
bool userIsBanned = false;
QString banReason;
int banSecondsRemaining = 0;
int activeUserCount = 0;
int nextGameId = 1;
int nextReplayId = 1;
bool requireRegistration = false;
QList<LogEntry> 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<qint64, QString> 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