stock/tests/unit/test_debate_framework.py
2026-02-27 03:17:12 +08:00

392 lines
12 KiB
Python

"""Unit tests for debate framework.
Tests the DebateFramework, Argument, Rebuttal, and DebateResult classes.
"""
import pytest
from openclaw.debate.debate_framework import (
Argument,
ArgumentStrength,
ArgumentType,
DebateConfig,
DebateFramework,
DebateResult,
DebateRound,
Rebuttal,
)
class TestArgument:
"""Test Argument dataclass."""
def test_argument_creation(self):
"""Test creating an argument."""
arg = Argument(
agent_id="bull-1",
argument_type=ArgumentType.BULLISH,
claim="Revenue is growing rapidly",
evidence="20% YoY growth in Q3",
strength=ArgumentStrength.STRONG,
target_factors=["revenue", "growth"],
)
assert arg.agent_id == "bull-1"
assert arg.argument_type == ArgumentType.BULLISH
assert arg.claim == "Revenue is growing rapidly"
assert arg.strength == ArgumentStrength.STRONG
assert "revenue" in arg.target_factors
def test_argument_to_dict(self):
"""Test converting argument to dictionary."""
arg = Argument(
agent_id="bear-1",
argument_type=ArgumentType.BEARISH,
claim="Competition is increasing",
evidence="Market share declining 5%",
strength=ArgumentStrength.MODERATE,
)
d = arg.to_dict()
assert d["agent_id"] == "bear-1"
assert d["argument_type"] == "bearish"
assert "timestamp" in d
class TestRebuttal:
"""Test Rebuttal dataclass."""
@pytest.fixture
def target_argument(self):
"""Create a target argument for rebuttal."""
return Argument(
agent_id="bull-1",
argument_type=ArgumentType.BULLISH,
claim="PE ratio is reasonable",
evidence="PE is 15 vs industry 20",
strength=ArgumentStrength.MODERATE,
)
def test_rebuttal_creation(self, target_argument):
"""Test creating a rebuttal."""
rebuttal = Rebuttal(
agent_id="bear-1",
target_argument=target_argument,
counter_claim="PE doesn't account for debt",
reasoning="High debt load makes PE misleading",
effectiveness=0.7,
)
assert rebuttal.agent_id == "bear-1"
assert rebuttal.effectiveness == 0.7
assert rebuttal.target_argument == target_argument
def test_rebuttal_effectiveness_clamping(self, target_argument):
"""Test that effectiveness is clamped to 0-1 range."""
rebuttal_high = Rebuttal(
agent_id="bear-1",
target_argument=target_argument,
counter_claim="Test",
reasoning="Test",
effectiveness=1.5,
)
assert rebuttal_high.effectiveness == 1.0
rebuttal_low = Rebuttal(
agent_id="bear-1",
target_argument=target_argument,
counter_claim="Test",
reasoning="Test",
effectiveness=-0.5,
)
assert rebuttal_low.effectiveness == 0.0
class TestDebateRound:
"""Test DebateRound class."""
@pytest.fixture
def round_data(self):
"""Create a debate round."""
return DebateRound(round_number=1)
def test_add_argument(self, round_data):
"""Test adding arguments to a round."""
arg = Argument(
agent_id="bull-1",
argument_type=ArgumentType.BULLISH,
claim="Growth is strong",
evidence="20% YoY",
strength=ArgumentStrength.STRONG,
)
round_data.add_argument(arg)
assert len(round_data.arguments) == 1
assert round_data.arguments[0] == arg
def test_get_bullish_arguments(self, round_data):
"""Test filtering bullish arguments."""
bull_arg = Argument(
agent_id="bull-1",
argument_type=ArgumentType.BULLISH,
claim="Buy",
evidence="Growth",
strength=ArgumentStrength.STRONG,
)
bear_arg = Argument(
agent_id="bear-1",
argument_type=ArgumentType.BEARISH,
claim="Sell",
evidence="Risk",
strength=ArgumentStrength.MODERATE,
)
round_data.add_argument(bull_arg)
round_data.add_argument(bear_arg)
bullish = round_data.get_bullish_arguments()
assert len(bullish) == 1
assert bullish[0].argument_type == ArgumentType.BULLISH
class TestDebateConfig:
"""Test DebateConfig validation."""
def test_valid_config(self):
"""Test valid configuration."""
config = DebateConfig(max_rounds=5, min_rounds=2)
assert config.max_rounds == 5
assert config.min_rounds == 2
def test_invalid_max_rounds(self):
"""Test that max_rounds < min_rounds raises error."""
with pytest.raises(ValueError):
DebateConfig(max_rounds=1, min_rounds=2)
def test_invalid_consensus_threshold(self):
"""Test that invalid consensus threshold raises error."""
with pytest.raises(ValueError):
DebateConfig(consensus_threshold=1.5)
class TestDebateFramework:
"""Test DebateFramework functionality."""
@pytest.fixture
def framework(self):
"""Create a debate framework."""
config = DebateConfig(max_rounds=3, min_rounds=1)
return DebateFramework(config)
def test_start_debate(self, framework):
"""Test starting a debate."""
framework.start_debate("AAPL")
assert framework.symbol == "AAPL"
assert len(framework.rounds) == 0
def test_add_round(self, framework):
"""Test adding debate rounds."""
framework.start_debate("AAPL")
round1 = framework.add_round()
round2 = framework.add_round()
assert round1.round_number == 1
assert round2.round_number == 2
assert len(framework.rounds) == 2
def test_submit_argument(self, framework):
"""Test submitting arguments."""
framework.start_debate("AAPL")
argument = framework.submit_argument(
agent_id="bull-1",
argument_type=ArgumentType.BULLISH,
claim="Strong growth",
evidence="20% YoY",
strength=ArgumentStrength.STRONG,
)
assert argument.agent_id == "bull-1"
assert len(framework.rounds) == 1
assert len(framework.rounds[0].arguments) == 1
def test_calculate_scores(self, framework):
"""Test score calculation."""
framework.start_debate("AAPL")
framework.add_round()
# Add bullish argument
framework.submit_argument(
agent_id="bull-1",
argument_type=ArgumentType.BULLISH,
claim="Growth",
evidence="Data",
strength=ArgumentStrength.STRONG,
)
bull_score, bear_score = framework._calculate_scores()
assert bull_score > 0
assert bear_score == 0
def test_conclude_debate_bull_wins(self, framework):
"""Test concluding debate with bull win."""
framework.start_debate("AAPL")
framework.add_round()
# Strong bullish argument
framework.submit_argument(
agent_id="bull-1",
argument_type=ArgumentType.BULLISH,
claim="Strong growth",
evidence="20% YoY",
strength=ArgumentStrength.COMPELLING,
)
result = framework.conclude_debate()
assert result.symbol == "AAPL"
assert result.winner == "bull"
assert result.recommendation == "buy"
assert result.bull_score > result.bear_score
def test_conclude_debate_bear_wins(self, framework):
"""Test concluding debate with bear win."""
framework.start_debate("AAPL")
framework.add_round()
# Strong bearish argument
framework.submit_argument(
agent_id="bear-1",
argument_type=ArgumentType.BEARISH,
claim="High risk",
evidence="Debt increasing",
strength=ArgumentStrength.COMPELLING,
)
result = framework.conclude_debate()
assert result.winner == "bear"
assert result.recommendation == "sell"
def test_should_continue_max_rounds(self, framework):
"""Test should_continue respects max_rounds."""
framework.start_debate("AAPL")
framework.add_round()
framework.add_round()
framework.add_round()
assert not framework.should_continue()
def test_rebuttal_reduces_score(self, framework):
"""Test that rebuttals reduce target argument scores."""
framework.start_debate("AAPL")
framework.add_round()
# Submit bullish argument
argument = framework.submit_argument(
agent_id="bull-1",
argument_type=ArgumentType.BULLISH,
claim="Strong moat",
evidence="Market leader",
strength=ArgumentStrength.STRONG,
)
# Rebut it
framework.submit_rebuttal(
agent_id="bear-1",
target_argument=argument,
counter_claim="Moat is eroding",
reasoning="New competitors emerging",
effectiveness=0.8,
)
bull_score, _ = framework._calculate_scores()
# Score should be reduced by rebuttal
assert bull_score < 40 # Strong argument is 40, reduced by 80%
class TestDebateResult:
"""Test DebateResult dataclass."""
def test_result_creation(self):
"""Test creating a debate result."""
result = DebateResult(
symbol="AAPL",
winner="bull",
bull_score=100.0,
bear_score=50.0,
consensus_level=0.7,
recommendation="buy",
confidence=0.8,
)
assert result.symbol == "AAPL"
assert result.winner == "bull"
assert result.confidence == 0.8
def test_result_to_dict(self):
"""Test converting result to dictionary."""
result = DebateResult(
symbol="AAPL",
winner="bull",
bull_score=100.0,
bear_score=50.0,
consensus_level=0.7,
key_points=["Growth is strong"],
disagreements=["Valuation debate"],
)
d = result.to_dict()
assert d["symbol"] == "AAPL"
assert d["winner"] == "bull"
assert "timestamp" in d
class TestDebateIntegration:
"""Integration tests for full debate flow."""
def test_full_debate_flow(self):
"""Test a complete multi-round debate."""
config = DebateConfig(max_rounds=2, min_rounds=1)
framework = DebateFramework(config)
framework.start_debate("TSLA")
# Round 1: Bull presents strong case
round1 = framework.add_round()
framework.submit_argument(
agent_id="bull-1",
argument_type=ArgumentType.BULLISH,
claim="EV market leadership",
evidence="50% market share",
strength=ArgumentStrength.STRONG,
target_factors=["market_share", "growth"],
)
# Bear rebuts
bull_arg = round1.arguments[0]
framework.submit_rebuttal(
agent_id="bear-1",
target_argument=bull_arg,
counter_claim="Competition increasing",
reasoning="Legacy automakers entering",
effectiveness=0.6,
)
# Round 2: Bear presents case
framework.add_round()
framework.submit_argument(
agent_id="bear-1",
argument_type=ArgumentType.BEARISH,
claim="Valuation too high",
evidence="PE ratio 100x",
strength=ArgumentStrength.MODERATE,
target_factors=["valuation"],
)
result = framework.conclude_debate()
assert result.rounds_completed == 2
assert result.symbol == "TSLA"
assert len(result.key_points) >= 0
assert len(result.disagreements) >= 0