Skip to content

storage/test_gcs_global_db.cpp

Lifecycle unit tests for the GCS GlobalDB component (Phase 3, Task 3). Uses a real GossipPubSub on port 0 (Tier 2 fixture pattern from SuperGenius/test/src/crdt/globaldb_integration.cpp) and the NEO-SWARM wait-condition template (condition_variable polling — no std::this_thread sleeps). More...

Namespaces

Name
sgns
sgns::neoswarm
sgns::neoswarm::storage
sgns::neoswarm::storage::test

Classes

Name
class sgns::neoswarm::storage::test::GcsGlobalDbTest
Fixture that owns a per-test temp directory under std::filesystem::temp_directory_path() and removes it in TearDown.

Functions

Name
TEST_F(GcsGlobalDbTest , DefaultConstructedIsNotRunning )
Default-constructed component: inert, IsRunning() false, Shutdown() a safe no-op.
TEST_F(GcsGlobalDbTest , InitializeWithoutSdkFails )
Initialize() without GeniusSDK initialized must fail with the specific Error::SdkNotInitialized code. This binary never calls GeniusSDKInit, so GeniusSDKGetNode() returns nullptr throughout the test.
TEST_F(GcsGlobalDbTest , LifecycleWithInjectedPubSub )
Full lifecycle with an injected pubsub: GlobalDB::New succeeds, Start is called, IsRunning() becomes true, the db directory appears on disk, and Shutdown() returns the component to a stopped state with the io thread joined.
TEST_F(GcsGlobalDbTest , DoubleInitializeFails )
Double Initialize() must fail the second call with Error::GcsDbError (programmer error — surfaced, not silently swallowed).

Detailed Description

Lifecycle unit tests for the GCS GlobalDB component (Phase 3, Task 3). Uses a real GossipPubSub on port 0 (Tier 2 fixture pattern from SuperGenius/test/src/crdt/globaldb_integration.cpp) and the NEO-SWARM wait-condition template (condition_variable polling — no std::this_thread sleeps).

Date: 2026-08-10

Functions Documentation

function TEST_F

TEST_F(
    GcsGlobalDbTest ,
    DefaultConstructedIsNotRunning 
)

Default-constructed component: inert, IsRunning() false, Shutdown() a safe no-op.

function TEST_F

TEST_F(
    GcsGlobalDbTest ,
    InitializeWithoutSdkFails 
)

Initialize() without GeniusSDK initialized must fail with the specific Error::SdkNotInitialized code. This binary never calls GeniusSDKInit, so GeniusSDKGetNode() returns nullptr throughout the test.

function TEST_F

TEST_F(
    GcsGlobalDbTest ,
    LifecycleWithInjectedPubSub 
)

Full lifecycle with an injected pubsub: GlobalDB::New succeeds, Start is called, IsRunning() becomes true, the db directory appears on disk, and Shutdown() returns the component to a stopped state with the io thread joined.

function TEST_F

TEST_F(
    GcsGlobalDbTest ,
    DoubleInitializeFails 
)

Double Initialize() must fail the second call with Error::GcsDbError (programmer error — surfaced, not silently swallowed).

Source code

#include "storage/gcs_global_db.hpp"

#include <chrono>
#include <condition_variable>
#include <cstdlib>
#include <filesystem>
#include <functional>
#include <mutex>
#include <string>

#include <gtest/gtest.h>

#include "crdt/globaldb/keypair_file_storage.hpp"

#include "ipfs_pubsub/gossip_pubsub.hpp"

#include <libp2p/log/configurator.hpp>
#include <libp2p/log/logger.hpp>

#include <soralog/impl/configurator_from_yaml.hpp>
#include <soralog/logging_system.hpp>

namespace
{
    constexpr std::chrono::milliseconds kWaitTimeout{ 25000 };
    constexpr std::chrono::milliseconds kPollInterval{ 10 };
    constexpr const char *kListenIp = "0.0.0.0";

    constexpr const char *kLoggingYaml = R"(
     sinks:
       - name: console
         type: console
         color: false
     groups:
       - name: gcs_global_db_test
         sink: console
         level: error
         children:
           - name: libp2p
           - name: Gossip
    )";

    bool WaitForCondition( const std::function<bool()> &predicate, std::chrono::milliseconds timeout )
    {
        std::mutex              mtx;
        std::condition_variable cv;
        std::unique_lock<std::mutex> lock( mtx );
        const auto deadline = std::chrono::steady_clock::now() + timeout;
        while ( std::chrono::steady_clock::now() < deadline )
        {
            if ( predicate() )
            {
                return true;
            }
            const auto remaining = std::chrono::duration_cast<std::chrono::milliseconds>(
                deadline - std::chrono::steady_clock::now() );
            const auto slice     = std::min( kPollInterval, remaining );
            cv.wait_for( lock, slice );
        }
        return predicate();
    }
} // namespace

namespace sgns::neoswarm::storage::test
{
    class GcsGlobalDbTest : public ::testing::Test
    {
    protected:
        static void SetUpTestSuite()
        {
            auto loggerConfigurator = std::make_shared<libp2p::log::Configurator>();
            auto configFromYaml     = std::make_shared<soralog::ConfiguratorFromYAML>( loggerConfigurator,
                                                                                       std::string{ kLoggingYaml } );
            auto loggingSystem      = std::make_shared<soralog::LoggingSystem>( configFromYaml );
            const auto confResult   = loggingSystem->configure();
            ASSERT_FALSE( confResult.has_error ) << "Could not configure test logging system";
            libp2p::log::setLoggingSystem( loggingSystem );
        }

        void SetUp() override
        {
            const auto *info       = ::testing::UnitTest::GetInstance()->current_test_info();
            const auto  uniqueSalt = std::chrono::steady_clock::now().time_since_epoch().count();
            m_tempPath = ( std::filesystem::temp_directory_path()
                           / ( std::string{ "gcs_global_db_" } + info->name() + "_" +
                               std::to_string( uniqueSalt ) ) )
                             .string();
            std::filesystem::create_directories( m_tempPath );
        }

        void TearDown() override
        {
            std::error_code ec;
            std::filesystem::remove_all( m_tempPath, ec );
        }

        std::shared_ptr<sgns::ipfs_pubsub::GossipPubSub> MakeStartedPubSub( const std::string &keyDir )
        {
            sgns::crdt::KeyPairFileStorage keyStore( keyDir );
            auto                           keyPairResult = keyStore.GetKeyPair();
            EXPECT_FALSE( keyPairResult.has_error() );
            if ( keyPairResult.has_error() )
            {
                return nullptr;
            }
            auto pubsub = std::make_shared<sgns::ipfs_pubsub::GossipPubSub>( keyPairResult.value() );
            auto startFuture = pubsub->Start( 0, {}, kListenIp, {} );
            auto startError  = startFuture.get();
            EXPECT_FALSE( startError ) << "Could not start GossipPubSub: " << startError.message();
            if ( startError )
            {
                return nullptr;
            }
            return pubsub;
        }

        std::string m_tempPath; 
    };

    TEST_F( GcsGlobalDbTest, DefaultConstructedIsNotRunning )
    {
        GcsGlobalDb::Config cfg{};
        cfg.m_dbPath = m_tempPath + "/db";
        GcsGlobalDb db( cfg );

        EXPECT_FALSE( db.IsRunning() );
        db.Shutdown(); // must not crash, must not throw
        EXPECT_FALSE( db.IsRunning() );
    }

    TEST_F( GcsGlobalDbTest, InitializeWithoutSdkFails )
    {
        GcsGlobalDb::Config cfg{};
        cfg.m_dbPath = m_tempPath + "/db";
        GcsGlobalDb db( cfg );

        auto res = db.Initialize();
        ASSERT_FALSE( res.has_value() );
        EXPECT_EQ( res.error(), Error::SdkNotInitialized );
        EXPECT_FALSE( db.IsRunning() );
    }

    TEST_F( GcsGlobalDbTest, LifecycleWithInjectedPubSub )
    {
        auto pubsub = MakeStartedPubSub( m_tempPath + "/key" );
        ASSERT_NE( pubsub, nullptr );

        GcsGlobalDb::Config cfg{};
        cfg.m_dbPath = m_tempPath + "/db";
        GcsGlobalDb db( cfg );

        auto res = db.Initialize( pubsub );
        ASSERT_TRUE( res.has_value() );
        EXPECT_TRUE( db.IsRunning() );

        // The db directory may be created lazily — wait for it via the wait-condition template.
        const auto dbPath = cfg.m_dbPath;
        EXPECT_TRUE( WaitForCondition( [&dbPath]() { return std::filesystem::exists( dbPath ); },
                                       kWaitTimeout ) );

        db.Shutdown();
        EXPECT_FALSE( db.IsRunning() );

        pubsub->Stop();
    }

    TEST_F( GcsGlobalDbTest, DoubleInitializeFails )
    {
        auto pubsub = MakeStartedPubSub( m_tempPath + "/key" );
        ASSERT_NE( pubsub, nullptr );

        GcsGlobalDb::Config cfg{};
        cfg.m_dbPath = m_tempPath + "/db";
        GcsGlobalDb db( cfg );

        auto first = db.Initialize( pubsub );
        ASSERT_TRUE( first.has_value() );
        EXPECT_TRUE( db.IsRunning() );

        auto second = db.Initialize( pubsub );
        ASSERT_FALSE( second.has_value() );
        EXPECT_EQ( second.error(), Error::GcsDbError );

        db.Shutdown();
        pubsub->Stop();
    }

} // namespace sgns::neoswarm::storage::test

Updated on 2026-10-09 at 03:59:43 +0000