Skip to content

specialists/test_math_specialist.cpp

Unit tests for MathSpecialist — happy, unhappy paths. More...

Functions

Name
TEST(MathSpecialist , Process_LoadedEngine_ReturnsResult )
TEST(MathSpecialist , GetName_ReturnsCorrectName )
TEST(MathSpecialist , IsLoaded_InitiallyFalse )
TEST(MathSpecialist , GetConfidence_InitiallyZero )
TEST(MathSpecialist , Process_NotLoaded_SymbolicFallbackWins )
TEST(MathSpecialist , Process_NotLoaded_NonMath_ReturnsInputUnchanged )
TEST(MathSpecialist , Process_InferenceFails_ReturnsInputUnchanged )
TEST(MathSpecialist , Load_NoEngine_ReturnsError )

Detailed Description

Unit tests for MathSpecialist — happy, unhappy paths.

Date: 2026-06-16

Functions Documentation

function TEST

TEST(
    MathSpecialist ,
    Process_LoadedEngine_ReturnsResult 
)

function TEST

TEST(
    MathSpecialist ,
    GetName_ReturnsCorrectName 
)

function TEST

TEST(
    MathSpecialist ,
    IsLoaded_InitiallyFalse 
)

function TEST

TEST(
    MathSpecialist ,
    GetConfidence_InitiallyZero 
)

function TEST

TEST(
    MathSpecialist ,
    Process_NotLoaded_SymbolicFallbackWins 
)

function TEST

TEST(
    MathSpecialist ,
    Process_NotLoaded_NonMath_ReturnsInputUnchanged 
)

function TEST

TEST(
    MathSpecialist ,
    Process_InferenceFails_ReturnsInputUnchanged 
)

function TEST

TEST(
    MathSpecialist ,
    Load_NoEngine_ReturnsError 
)

Source code

#include "specialists/math_specialist.hpp"
#include "core/engine/inference_engine.hpp"
#include <functional>
#include <gtest/gtest.h>
#include <memory>

using namespace sgns::neoswarm;

namespace
{
    class MockEngine : public core::InferenceEngine
    {
    public:
        outcome::result<InferenceResponse> Infer( const Task& task ) override
        {
            InferenceResponse resp;
            resp.m_output = "42";
            resp.m_perplexity = 0.5f;
            resp.m_success = true;
            resp.m_taskId = task.m_id;
            return outcome::success( resp );
        }
        outcome::result<void> StreamInfer( const Task&,
                                            std::function<void( const std::string& )> ) override
        {
            return outcome::success();
        }
        outcome::result<void> LoadModel( const std::string& ) override
        {
            return outcome::success();
        }
        bool IsLoaded() const override
        {
            return true;
        }
        std::string BackendName() const override
        {
            return "mock";
        }
    };

    class FailingMockEngine : public core::InferenceEngine
    {
    public:
        outcome::result<InferenceResponse> Infer( const Task& ) override
        {
            return outcome::failure( Error::InferenceFailed );
        }
        outcome::result<void> StreamInfer( const Task&,
                                            std::function<void( const std::string& )> ) override
        {
            return outcome::failure( Error::InferenceFailed );
        }
        outcome::result<void> LoadModel( const std::string& ) override
        {
            return outcome::failure( Error::ModelLoadFailed );
        }
        bool IsLoaded() const override
        {
            return false;
        }
        std::string BackendName() const override
        {
            return "mock";
        }
    };
} // namespace

// =======================================================================
// Happy path
// =======================================================================

TEST( MathSpecialist, Process_LoadedEngine_ReturnsResult )
{
    auto engine = std::make_shared<MockEngine>();
    specialists::MathSpecialist specialist( engine );
    ASSERT_TRUE( specialist.Load( "dummy" ).has_value() );
    ASSERT_TRUE( specialist.IsLoaded() );

    auto result = specialist.Process( "what is 2 + 3" );
    ASSERT_TRUE( result.has_value() );
    EXPECT_FALSE( result.value().empty() );
}

TEST( MathSpecialist, GetName_ReturnsCorrectName )
{
    specialists::MathSpecialist specialist;
    EXPECT_EQ( specialist.GetName(), "MathSpecialist" );
}

TEST( MathSpecialist, IsLoaded_InitiallyFalse )
{
    specialists::MathSpecialist specialist;
    EXPECT_FALSE( specialist.IsLoaded() );
}

TEST( MathSpecialist, GetConfidence_InitiallyZero )
{
    specialists::MathSpecialist specialist;
    EXPECT_FLOAT_EQ( specialist.GetConfidence(), 0.0f );
}

// =======================================================================
// Unhappy path — fail-close
// =======================================================================

TEST( MathSpecialist, Process_NotLoaded_SymbolicFallbackWins )
{
    auto engine = std::make_shared<MockEngine>();
    specialists::MathSpecialist specialist( engine );
    // NOT calling Load()

    // Symbolic fallback runs first — succeeds for pure arithmetic
    auto result = specialist.Process( "2 + 3" );
    ASSERT_TRUE( result.has_value() );
    EXPECT_EQ( result.value(), "= 5" );
    EXPECT_FLOAT_EQ( specialist.GetConfidence(), 1.0f );
}

TEST( MathSpecialist, Process_NotLoaded_NonMath_ReturnsInputUnchanged )
{
    auto engine = std::make_shared<MockEngine>();
    specialists::MathSpecialist specialist( engine );

    // Non-math input: symbolic fails, not-loaded check → returns input unchanged
    auto result = specialist.Process( "hello world" );
    ASSERT_TRUE( result.has_value() );
    EXPECT_EQ( result.value(), "hello world" );
    EXPECT_FLOAT_EQ( specialist.GetConfidence(), 0.0f );
}

TEST( MathSpecialist, Process_InferenceFails_ReturnsInputUnchanged )
{
    // No engine at all — Process will try symbolic first, fail, then return input unchanged
    specialists::MathSpecialist specialist;
    auto result = specialist.Process( "hello world" );
    ASSERT_TRUE( result.has_value() );
    EXPECT_EQ( result.value(), "hello world" );
    EXPECT_FLOAT_EQ( specialist.GetConfidence(), 0.0f );
}

TEST( MathSpecialist, Load_NoEngine_ReturnsError )
{
    specialists::MathSpecialist specialist;
    auto result = specialist.Load( "dummy" );
    EXPECT_FALSE( result.has_value() );
}

Updated on 2026-07-25 at 22:56:57 +0000