Loading debian/changelog +8 −0 Original line number Diff line number Diff line authdb (20260417+1) unstable; urgency=medium * Fix missing Username and GPO information in SessionData due to incomplete copy constructors * Add unit tests for Cluster Operations (session push, fetch, rebalance) -- Jan Koester <jan.koester@tuxist.de> Fri, 17 Apr 2026 12:00:00 +0200 authdb (20260416+4) unstable; urgency=medium * Increase initial cluster retrieval timeout to 30s to fix startup Loading src/session.cpp +28 −0 Original line number Diff line number Diff line Loading @@ -57,6 +57,20 @@ authdb::SessionData::SessionData(const SessionData& src) : SessionData() { _sid=src._sid; _uid=src._uid; _did=src._did; _username=src._username; _gpoIndex=src._gpoIndex; SessionData::GPOResult *cur=src._GPOResult.next; SessionData::GPOResult *dest=&_GPOResult; dest->GPOId=src._GPOResult.GPOId; dest->GPORes=src._GPOResult.GPORes; while(cur){ dest->next=new SessionData::GPOResult(); dest=dest->next; dest->GPOId=cur->GPOId; dest->GPORes=cur->GPORes; cur=cur->next; } } authdb::SessionData::SessionData(const SessionData *src) : SessionData() { Loading @@ -65,6 +79,20 @@ authdb::SessionData::SessionData(const SessionData *src) : SessionData() { _sid=src->_sid; _uid=src->_uid; _did=src->_did; _username=src->_username; _gpoIndex=src->_gpoIndex; SessionData::GPOResult *cur=src->_GPOResult.next; SessionData::GPOResult *dest=&_GPOResult; dest->GPOId=src->_GPOResult.GPOId; dest->GPORes=src->_GPOResult.GPORes; while(cur){ dest->next=new SessionData::GPOResult(); dest=dest->next; dest->GPOId=cur->GPOId; dest->GPORes=cur->GPORes; cur=cur->next; } } authdb::SessionData::SessionData(uuid::uuid sessionid, uuid::uuid userid, uuid::uuid domainid,std::vector<uuid::uuid> &members) : _members(members){ Loading tests/CMakeLists.txt +9 −0 Original line number Diff line number Diff line Loading @@ -87,3 +87,12 @@ target_link_libraries(test_cluster_performance PRIVATE GTest::gtest_main ) gtest_discover_tests(test_cluster_performance) # ── Cluster operations (scrub, replicate, etc) ── add_executable(test_cluster_ops test_cluster_ops.cpp) target_include_directories(test_cluster_ops PRIVATE ${CMAKE_SOURCE_DIR}/src) target_link_libraries(test_cluster_ops PRIVATE authobj GTest::gtest_main ) gtest_discover_tests(test_cluster_ops) tests/test_cluster_ops.cpp 0 → 100644 +222 −0 Original line number Diff line number Diff line #include <gtest/gtest.h> #include <chrono> #include <cstdio> #include <cstring> #include <cstdlib> #include <fstream> #include <iostream> #include <memory> #include <numeric> #include <string> #include <thread> #include <vector> #ifdef _WIN32 #include <process.h> #include <io.h> #include <windows.h> #include <psapi.h> #define test_getpid _getpid #define test_unlink _unlink #else #include <unistd.h> #define test_getpid getpid #define test_unlink unlink #endif #include <uuidp.h> #include <netplus/utils/certgen.h> #include "backend.h" #include "authdb.h" #include "user.h" #include "group.h" #include "gpo.h" #include "session.h" #include "types.h" #include "exception.h" #include "cluster.h" #include "client.h" #include "index.h" namespace authdb { extern std::mutex AuthClientLock; extern Cluster *g_Cluster; } using namespace authdb; static std::string tempDir() { #ifdef _WIN32 const char *tmp = std::getenv("TEMP"); if (!tmp) tmp = std::getenv("TMP"); return tmp ? std::string(tmp) + "\\" : "C:\\Temp\\"; #else return "/tmp/"; #endif } static std::string g_certFile; static std::string g_keyFile; static bool generateTestCerts() { std::string prefix = tempDir() + "authdb_cluster_ops_test_" + std::to_string(test_getpid()); g_certFile = prefix + ".crt"; g_keyFile = prefix + ".key.der"; netplus::CertGenConfig cfg; cfg.commonName = "authdb-test"; cfg.rsaBits = 2048; cfg.validDays = 1; netplus::CertKeyPair pair; if (!netplus::generateSelfSignedCert(cfg, pair)) { return false; } if (!netplus::writePemFile(g_certFile, pair.certPem)) return false; if (!netplus::writeDerFile(g_keyFile, pair.keyDer)) return false; return true; } static void cleanupTestCerts() { if (!g_certFile.empty()) test_unlink(g_certFile.c_str()); if (!g_keyFile.empty()) test_unlink(g_keyFile.c_str()); } static constexpr int NUM_NODES = 3; class ClusterOpsTest : public ::testing::Test { protected: static Cluster *clusters[NUM_NODES]; static std::string fileDbPath; static int basePorts[NUM_NODES]; static bool clusterReady; static void SetUpTestSuite() { if (!generateTestCerts()) { GTEST_SKIP() << "Could not generate TLS certificates"; return; } int basePort = 24433 + (test_getpid() % 10000); for (int i = 0; i < NUM_NODES; ++i) basePorts[i] = basePort + i; fileDbPath = tempDir() + "authdb_cluster_ops_" + std::to_string(test_getpid()) + ".db"; std::vector<ClusterNode> allPeers; for (int i = 0; i < NUM_NODES; ++i) { ClusterNode node; node.address = "127.0.0.1"; node.port = basePorts[i]; allPeers.push_back(node); } for (int i = 0; i < NUM_NODES; ++i) { clusters[i] = new Cluster(); ClusterConfig cfg; cfg.bind_address = "127.0.0.1"; cfg.port = basePorts[i]; cfg.cert_file = g_certFile; cfg.key_file = g_keyFile; cfg.client_name = "testops"; cfg.client_key = "testkeyops"; cfg.store_path = ""; // memory-only for fast testing cfg.data_blocks = 2; cfg.parity_blocks = 1; cfg.peers = allPeers; clusters[i]->init(cfg); clusters[i]->start(); } authdb::g_Cluster = clusters[0]; size_t online = clusters[0]->waitForPeers(10); clusterReady = (online >= 2); } static void TearDownTestSuite() { authdb::g_Cluster = nullptr; for (int i = 0; i < NUM_NODES; ++i) { if (clusters[i]) { clusters[i]->stop(); delete clusters[i]; clusters[i] = nullptr; } } test_unlink(fileDbPath.c_str()); cleanupTestCerts(); } void SetUp() override { if (!clusterReady) GTEST_SKIP() << "Cluster not ready"; } }; Cluster *ClusterOpsTest::clusters[NUM_NODES] = {}; std::string ClusterOpsTest::fileDbPath; int ClusterOpsTest::basePorts[NUM_NODES] = {}; bool ClusterOpsTest::clusterReady = false; // ── Test Session Push & Fetch ── TEST_F(ClusterOpsTest, SessionPushAndFetch) { uuid::uuid sid, uid, did; sid.generate(); uid.generate(); did.generate(); std::vector<uuid::uuid> members; SessionData sd(sid, uid, did, members); // Push via node 0 clusters[0]->pushSession(sd); // Give it a moment to replicate std::this_thread::sleep_for(std::chrono::milliseconds(200)); // Try fetching from node 0 uuid::uuid f_sid, f_uid, f_did; std::vector<uuid::uuid> f_members; std::string f_username; std::vector<std::pair<uuid::uuid, bool>> f_gpos; bool ok = clusters[0]->fetchSessionBySid(sid, f_sid, f_uid, f_did, f_members, f_username, f_gpos); EXPECT_TRUE(ok); EXPECT_EQ(f_sid, sid); EXPECT_EQ(f_uid, uid); EXPECT_EQ(f_did, did); // Wait and scrub auto res = clusters[0]->scrub(); EXPECT_GE(res.groups_checked, 0u); } // ── Test Data Replication & Scrub ── TEST_F(ClusterOpsTest, ReplicateOpAndScrub) { uuid::uuid recId; recId.generate(); // Replicate an op on node 0 clusters[0]->replicateOp(authdb::OpType::Create, "testdomain", recId, 1 /* UserData */, nullptr); // Provide some time for push std::this_thread::sleep_for(std::chrono::milliseconds(200)); // Run scrub on node 0 auto res = clusters[0]->scrub(); // We expect some repaired or checked groups, at least 0 if it was skipped EXPECT_GE(res.groups_checked, 0u); } // ── Test Rebalance ── TEST_F(ClusterOpsTest, RebalanceOp) { auto &client = clusters[0]->getClient(); auto rb = client->rebalance(); EXPECT_GE(rb.rebalanced, 0u); } Loading
debian/changelog +8 −0 Original line number Diff line number Diff line authdb (20260417+1) unstable; urgency=medium * Fix missing Username and GPO information in SessionData due to incomplete copy constructors * Add unit tests for Cluster Operations (session push, fetch, rebalance) -- Jan Koester <jan.koester@tuxist.de> Fri, 17 Apr 2026 12:00:00 +0200 authdb (20260416+4) unstable; urgency=medium * Increase initial cluster retrieval timeout to 30s to fix startup Loading
src/session.cpp +28 −0 Original line number Diff line number Diff line Loading @@ -57,6 +57,20 @@ authdb::SessionData::SessionData(const SessionData& src) : SessionData() { _sid=src._sid; _uid=src._uid; _did=src._did; _username=src._username; _gpoIndex=src._gpoIndex; SessionData::GPOResult *cur=src._GPOResult.next; SessionData::GPOResult *dest=&_GPOResult; dest->GPOId=src._GPOResult.GPOId; dest->GPORes=src._GPOResult.GPORes; while(cur){ dest->next=new SessionData::GPOResult(); dest=dest->next; dest->GPOId=cur->GPOId; dest->GPORes=cur->GPORes; cur=cur->next; } } authdb::SessionData::SessionData(const SessionData *src) : SessionData() { Loading @@ -65,6 +79,20 @@ authdb::SessionData::SessionData(const SessionData *src) : SessionData() { _sid=src->_sid; _uid=src->_uid; _did=src->_did; _username=src->_username; _gpoIndex=src->_gpoIndex; SessionData::GPOResult *cur=src->_GPOResult.next; SessionData::GPOResult *dest=&_GPOResult; dest->GPOId=src->_GPOResult.GPOId; dest->GPORes=src->_GPOResult.GPORes; while(cur){ dest->next=new SessionData::GPOResult(); dest=dest->next; dest->GPOId=cur->GPOId; dest->GPORes=cur->GPORes; cur=cur->next; } } authdb::SessionData::SessionData(uuid::uuid sessionid, uuid::uuid userid, uuid::uuid domainid,std::vector<uuid::uuid> &members) : _members(members){ Loading
tests/CMakeLists.txt +9 −0 Original line number Diff line number Diff line Loading @@ -87,3 +87,12 @@ target_link_libraries(test_cluster_performance PRIVATE GTest::gtest_main ) gtest_discover_tests(test_cluster_performance) # ── Cluster operations (scrub, replicate, etc) ── add_executable(test_cluster_ops test_cluster_ops.cpp) target_include_directories(test_cluster_ops PRIVATE ${CMAKE_SOURCE_DIR}/src) target_link_libraries(test_cluster_ops PRIVATE authobj GTest::gtest_main ) gtest_discover_tests(test_cluster_ops)
tests/test_cluster_ops.cpp 0 → 100644 +222 −0 Original line number Diff line number Diff line #include <gtest/gtest.h> #include <chrono> #include <cstdio> #include <cstring> #include <cstdlib> #include <fstream> #include <iostream> #include <memory> #include <numeric> #include <string> #include <thread> #include <vector> #ifdef _WIN32 #include <process.h> #include <io.h> #include <windows.h> #include <psapi.h> #define test_getpid _getpid #define test_unlink _unlink #else #include <unistd.h> #define test_getpid getpid #define test_unlink unlink #endif #include <uuidp.h> #include <netplus/utils/certgen.h> #include "backend.h" #include "authdb.h" #include "user.h" #include "group.h" #include "gpo.h" #include "session.h" #include "types.h" #include "exception.h" #include "cluster.h" #include "client.h" #include "index.h" namespace authdb { extern std::mutex AuthClientLock; extern Cluster *g_Cluster; } using namespace authdb; static std::string tempDir() { #ifdef _WIN32 const char *tmp = std::getenv("TEMP"); if (!tmp) tmp = std::getenv("TMP"); return tmp ? std::string(tmp) + "\\" : "C:\\Temp\\"; #else return "/tmp/"; #endif } static std::string g_certFile; static std::string g_keyFile; static bool generateTestCerts() { std::string prefix = tempDir() + "authdb_cluster_ops_test_" + std::to_string(test_getpid()); g_certFile = prefix + ".crt"; g_keyFile = prefix + ".key.der"; netplus::CertGenConfig cfg; cfg.commonName = "authdb-test"; cfg.rsaBits = 2048; cfg.validDays = 1; netplus::CertKeyPair pair; if (!netplus::generateSelfSignedCert(cfg, pair)) { return false; } if (!netplus::writePemFile(g_certFile, pair.certPem)) return false; if (!netplus::writeDerFile(g_keyFile, pair.keyDer)) return false; return true; } static void cleanupTestCerts() { if (!g_certFile.empty()) test_unlink(g_certFile.c_str()); if (!g_keyFile.empty()) test_unlink(g_keyFile.c_str()); } static constexpr int NUM_NODES = 3; class ClusterOpsTest : public ::testing::Test { protected: static Cluster *clusters[NUM_NODES]; static std::string fileDbPath; static int basePorts[NUM_NODES]; static bool clusterReady; static void SetUpTestSuite() { if (!generateTestCerts()) { GTEST_SKIP() << "Could not generate TLS certificates"; return; } int basePort = 24433 + (test_getpid() % 10000); for (int i = 0; i < NUM_NODES; ++i) basePorts[i] = basePort + i; fileDbPath = tempDir() + "authdb_cluster_ops_" + std::to_string(test_getpid()) + ".db"; std::vector<ClusterNode> allPeers; for (int i = 0; i < NUM_NODES; ++i) { ClusterNode node; node.address = "127.0.0.1"; node.port = basePorts[i]; allPeers.push_back(node); } for (int i = 0; i < NUM_NODES; ++i) { clusters[i] = new Cluster(); ClusterConfig cfg; cfg.bind_address = "127.0.0.1"; cfg.port = basePorts[i]; cfg.cert_file = g_certFile; cfg.key_file = g_keyFile; cfg.client_name = "testops"; cfg.client_key = "testkeyops"; cfg.store_path = ""; // memory-only for fast testing cfg.data_blocks = 2; cfg.parity_blocks = 1; cfg.peers = allPeers; clusters[i]->init(cfg); clusters[i]->start(); } authdb::g_Cluster = clusters[0]; size_t online = clusters[0]->waitForPeers(10); clusterReady = (online >= 2); } static void TearDownTestSuite() { authdb::g_Cluster = nullptr; for (int i = 0; i < NUM_NODES; ++i) { if (clusters[i]) { clusters[i]->stop(); delete clusters[i]; clusters[i] = nullptr; } } test_unlink(fileDbPath.c_str()); cleanupTestCerts(); } void SetUp() override { if (!clusterReady) GTEST_SKIP() << "Cluster not ready"; } }; Cluster *ClusterOpsTest::clusters[NUM_NODES] = {}; std::string ClusterOpsTest::fileDbPath; int ClusterOpsTest::basePorts[NUM_NODES] = {}; bool ClusterOpsTest::clusterReady = false; // ── Test Session Push & Fetch ── TEST_F(ClusterOpsTest, SessionPushAndFetch) { uuid::uuid sid, uid, did; sid.generate(); uid.generate(); did.generate(); std::vector<uuid::uuid> members; SessionData sd(sid, uid, did, members); // Push via node 0 clusters[0]->pushSession(sd); // Give it a moment to replicate std::this_thread::sleep_for(std::chrono::milliseconds(200)); // Try fetching from node 0 uuid::uuid f_sid, f_uid, f_did; std::vector<uuid::uuid> f_members; std::string f_username; std::vector<std::pair<uuid::uuid, bool>> f_gpos; bool ok = clusters[0]->fetchSessionBySid(sid, f_sid, f_uid, f_did, f_members, f_username, f_gpos); EXPECT_TRUE(ok); EXPECT_EQ(f_sid, sid); EXPECT_EQ(f_uid, uid); EXPECT_EQ(f_did, did); // Wait and scrub auto res = clusters[0]->scrub(); EXPECT_GE(res.groups_checked, 0u); } // ── Test Data Replication & Scrub ── TEST_F(ClusterOpsTest, ReplicateOpAndScrub) { uuid::uuid recId; recId.generate(); // Replicate an op on node 0 clusters[0]->replicateOp(authdb::OpType::Create, "testdomain", recId, 1 /* UserData */, nullptr); // Provide some time for push std::this_thread::sleep_for(std::chrono::milliseconds(200)); // Run scrub on node 0 auto res = clusters[0]->scrub(); // We expect some repaired or checked groups, at least 0 if it was skipped EXPECT_GE(res.groups_checked, 0u); } // ── Test Rebalance ── TEST_F(ClusterOpsTest, RebalanceOp) { auto &client = clusters[0]->getClient(); auto rb = client->rebalance(); EXPECT_GE(rb.rebalanced, 0u); }