test_market_breadth_tools.pyβ’14 kB
"""μμ₯ ν μ§ν λꡬ ν
μ€νΈ"""
import pytest
from datetime import datetime, timedelta
from unittest.mock import AsyncMock
from src.tools.market_breadth_tools import MarketBreadthTool
from src.exceptions import DataValidationError, DatabaseConnectionError
class TestMarketBreadthTool:
"""μμ₯ ν μ§ν λꡬ ν
μ€νΈ"""
@pytest.fixture
def mock_db_manager(self):
"""Mock λ°μ΄ν°λ² μ΄μ€ λ§€λμ """
return AsyncMock()
@pytest.fixture
def mock_cache_manager(self):
"""Mock μΊμ λ§€λμ """
return AsyncMock()
@pytest.fixture
def breadth_tool(self, mock_db_manager, mock_cache_manager):
"""μμ₯ ν μ§ν λꡬ μΈμ€ν΄μ€"""
return MarketBreadthTool(mock_db_manager, mock_cache_manager)
@pytest.fixture
def sample_breadth_data(self):
"""μν μμ₯ ν λ°μ΄ν°"""
return [
{
"date": datetime.now().date(),
"market": "KOSPI",
"advancing": 850,
"declining": 680,
"unchanged": 120,
"total_issues": 1650,
"advance_decline_ratio": 1.25,
"advance_volume": 2800000000000,
"decline_volume": 1900000000000,
"timestamp": datetime.now()
},
{
"date": datetime.now().date() - timedelta(days=1),
"market": "KOSPI",
"advancing": 720,
"declining": 800,
"unchanged": 130,
"total_issues": 1650,
"advance_decline_ratio": 0.90,
"advance_volume": 2100000000000,
"decline_volume": 2700000000000,
"timestamp": datetime.now() - timedelta(days=1)
}
]
def test_tool_initialization(self, breadth_tool, mock_db_manager, mock_cache_manager):
"""λꡬ μ΄κΈ°ν ν
μ€νΈ"""
assert breadth_tool.name == "get_market_breadth"
assert breadth_tool.description is not None
assert "μμ₯ ν" in breadth_tool.description
assert breadth_tool.db_manager == mock_db_manager
assert breadth_tool.cache_manager == mock_cache_manager
def test_tool_definition(self, breadth_tool):
"""λꡬ μ μ ν
μ€νΈ"""
definition = breadth_tool.get_tool_definition()
assert definition.name == "get_market_breadth"
assert definition.description is not None
assert definition.inputSchema is not None
# μ
λ ₯ μ€ν€λ§ κ²μ¦
schema = definition.inputSchema
assert schema["type"] == "object"
assert "properties" in schema
properties = schema["properties"]
assert "market" in properties
assert "period" in properties
assert "include_volume_analysis" in properties
# market νλΌλ―Έν° κ²μ¦
market_prop = properties["market"]
assert market_prop["type"] == "string"
assert "KOSPI" in market_prop["enum"]
assert "KOSDAQ" in market_prop["enum"]
assert "ALL" in market_prop["enum"]
# period νλΌλ―Έν° κ²μ¦
period_prop = properties["period"]
assert period_prop["type"] == "string"
assert "1d" in period_prop["enum"]
assert "1w" in period_prop["enum"]
assert "1m" in period_prop["enum"]
@pytest.mark.asyncio
async def test_execute_daily_breadth(self, breadth_tool, sample_breadth_data):
"""μΌμΌ μμ₯ ν μ‘°ν ν
μ€νΈ"""
# μΊμ λ―Έμ€
breadth_tool.cache_manager.get.return_value = None
# λ°μ΄ν°λ² μ΄μ€ μλ΅ μ€μ
breadth_tool.db_manager.fetch_all.return_value = sample_breadth_data[:1]
# μ€ν
result = await breadth_tool.execute({
"market": "KOSPI",
"period": "1d",
"include_volume_analysis": False
})
# κ²°κ³Ό κ²μ¦
assert len(result) == 1
content = result[0]
assert content.type == "text"
# JSON νμ±νμ¬ λ΄μ© νμΈ
import json
data = json.loads(content.text)
assert "timestamp" in data
assert "period" in data
assert "market" in data
assert "breadth_data" in data
assert "summary" in data
# μμ₯ ν λ°μ΄ν° κ²μ¦
breadth_data = data["breadth_data"]
assert len(breadth_data) == 1
today_data = breadth_data[0]
assert today_data["advancing"] == 850
assert today_data["declining"] == 680
assert today_data["unchanged"] == 120
assert today_data["advance_decline_ratio"] == 1.25
# μμ½ μ 보 κ²μ¦
summary = data["summary"]
assert "avg_advance_decline_ratio" in summary
assert "market_sentiment" in summary
assert summary["market_sentiment"] == "μμΉμΈ" # ratio > 1.0
@pytest.mark.asyncio
async def test_execute_weekly_trend(self, breadth_tool, sample_breadth_data):
"""μ£Όκ° μμ₯ ν νΈλ λ λΆμ ν
μ€νΈ"""
# μΌμ£ΌμΌ κ°μ λ°μ΄ν°
weekly_data = []
for i in range(7):
date = datetime.now().date() - timedelta(days=i)
advancing = 800 + (i * 20) # νΈλ λ μμ±
declining = 700 - (i * 10)
weekly_data.append({
"date": date,
"market": "KOSPI",
"advancing": advancing,
"declining": declining,
"unchanged": 150,
"total_issues": 1650,
"advance_decline_ratio": advancing / declining,
"advance_volume": 2500000000000,
"decline_volume": 2000000000000,
"timestamp": datetime.now() - timedelta(days=i)
})
breadth_tool.cache_manager.get.return_value = None
breadth_tool.db_manager.fetch_all.return_value = weekly_data
# μ€ν
result = await breadth_tool.execute({
"market": "KOSPI",
"period": "1w"
})
# κ²°κ³Ό κ²μ¦
content = result[0]
import json
data = json.loads(content.text)
assert data["period"] == "1w"
assert len(data["breadth_data"]) == 7
# νΈλ λ λΆμ νμΈ
assert "trend_analysis" in data
trend = data["trend_analysis"]
assert "direction" in trend
assert "strength" in trend
@pytest.mark.asyncio
async def test_volume_analysis(self, breadth_tool, sample_breadth_data):
"""κ±°λλ λΆμ ν¬ν¨ ν
μ€νΈ"""
breadth_tool.cache_manager.get.return_value = None
breadth_tool.db_manager.fetch_all.return_value = sample_breadth_data
# μ€ν
result = await breadth_tool.execute({
"market": "KOSPI",
"period": "1d",
"include_volume_analysis": True
})
# κ²°κ³Ό κ²μ¦
content = result[0]
import json
data = json.loads(content.text)
assert "volume_analysis" in data
volume_analysis = data["volume_analysis"]
assert "advance_volume" in volume_analysis
assert "decline_volume" in volume_analysis
assert "volume_ratio" in volume_analysis
assert "volume_trend" in volume_analysis
@pytest.mark.asyncio
async def test_market_sentiment_calculation(self, breadth_tool):
"""μμ₯ μ¬λ¦¬ κ³μ° ν
μ€νΈ"""
# κ°ν μμΉμΈ λ°μ΄ν°
bullish_data = [{
"date": datetime.now().date(),
"market": "KOSPI",
"advancing": 1200,
"declining": 400,
"unchanged": 50,
"total_issues": 1650,
"advance_decline_ratio": 3.0,
"advance_volume": 4000000000000,
"decline_volume": 1000000000000,
"timestamp": datetime.now()
}]
breadth_tool.cache_manager.get.return_value = None
breadth_tool.db_manager.fetch_all.return_value = bullish_data
result = await breadth_tool.execute({
"market": "KOSPI",
"period": "1d"
})
content = result[0]
import json
data = json.loads(content.text)
summary = data["summary"]
assert summary["market_sentiment"] == "κ°ν μμΉμΈ" # ratio >= 2.0
assert summary["avg_advance_decline_ratio"] == 3.0
def test_advance_decline_ratio_calculation(self, breadth_tool):
"""μμΉνλ½λΉμ¨ κ³μ° ν
μ€νΈ"""
# μ μμ μΈ κ²½μ°
ratio = breadth_tool._calculate_ad_ratio(800, 600)
assert abs(ratio - 1.33) < 0.01
# νλ½ μ’
λͺ©μ΄ 0μΈ κ²½μ°
ratio = breadth_tool._calculate_ad_ratio(800, 0)
assert ratio == float('inf')
# μμΉ μ’
λͺ©μ΄ 0μΈ κ²½μ°
ratio = breadth_tool._calculate_ad_ratio(0, 600)
assert ratio == 0.0
def test_market_sentiment_interpretation(self, breadth_tool):
"""μμ₯ μ¬λ¦¬ ν΄μ ν
μ€νΈ"""
# κ°ν μμΉμΈ
sentiment = breadth_tool._interpret_market_sentiment(2.5)
assert sentiment == "κ°ν μμΉμΈ"
# μμΉμΈ
sentiment = breadth_tool._interpret_market_sentiment(1.3)
assert sentiment == "μμΉμΈ"
# 보ν©
sentiment = breadth_tool._interpret_market_sentiment(0.95)
assert sentiment == "보ν©"
# νλ½μΈ
sentiment = breadth_tool._interpret_market_sentiment(0.6)
assert sentiment == "νλ½μΈ"
# κ°ν νλ½μΈ
sentiment = breadth_tool._interpret_market_sentiment(0.3)
assert sentiment == "κ°ν νλ½μΈ"
@pytest.mark.asyncio
async def test_cache_functionality(self, breadth_tool):
"""μΊμ κΈ°λ₯ ν
μ€νΈ"""
# μΊμ ννΈ μλ리μ€
cached_data = {
"timestamp": datetime.now().isoformat(),
"period": "1d",
"market": "KOSPI",
"breadth_data": []
}
breadth_tool.cache_manager.get.return_value = cached_data
# μ€ν
result = await breadth_tool.execute({
"market": "KOSPI",
"period": "1d"
})
# μΊμμμ λ°μ΄ν° λ°ν νμΈ
content = result[0]
import json
data = json.loads(content.text)
assert data == cached_data
# λ°μ΄ν°λ² μ΄μ€ νΈμΆ μμ νμΈ
breadth_tool.db_manager.fetch_all.assert_not_called()
@pytest.mark.asyncio
async def test_error_handling(self, breadth_tool):
"""μλ¬ μ²λ¦¬ ν
μ€νΈ"""
breadth_tool.cache_manager.get.return_value = None
breadth_tool.db_manager.fetch_all.side_effect = DatabaseConnectionError("DB μ°κ²° μ€ν¨")
with pytest.raises(DatabaseConnectionError):
await breadth_tool.execute({
"market": "KOSPI",
"period": "1d"
})
@pytest.mark.asyncio
async def test_invalid_parameters(self, breadth_tool):
"""μλͺ»λ νλΌλ―Έν° ν
μ€νΈ"""
# μλͺ»λ μμ₯
with pytest.raises(ValueError, match="Invalid market"):
await breadth_tool.execute({
"market": "INVALID",
"period": "1d"
})
# μλͺ»λ κΈ°κ°
with pytest.raises(ValueError, match="Invalid period"):
await breadth_tool.execute({
"market": "KOSPI",
"period": "invalid"
})
@pytest.mark.asyncio
async def test_empty_data_handling(self, breadth_tool):
"""λΉ λ°μ΄ν° μ²λ¦¬ ν
μ€νΈ"""
breadth_tool.cache_manager.get.return_value = None
breadth_tool.db_manager.fetch_all.return_value = []
result = await breadth_tool.execute({
"market": "KOSPI",
"period": "1d"
})
content = result[0]
import json
data = json.loads(content.text)
assert data["breadth_data"] == []
assert "message" in data
assert "λ°μ΄ν°κ° μμ΅λλ€" in data["message"] or "no data" in data["message"].lower()
@pytest.mark.asyncio
async def test_trend_analysis_calculation(self, breadth_tool):
"""νΈλ λ λΆμ κ³μ° ν
μ€νΈ"""
# μμΉ νΈλ λ λ°μ΄ν°
trend_data = []
for i in range(5):
ratio = 0.8 + (i * 0.1) # 0.8 -> 1.2λ‘ μμΉ νΈλ λ
trend_data.append({
"date": datetime.now().date() - timedelta(days=4-i),
"market": "KOSPI",
"advancing": int(800 * ratio),
"declining": 800,
"unchanged": 50,
"total_issues": 1650,
"advance_decline_ratio": ratio,
"advance_volume": 2000000000000,
"decline_volume": 2000000000000,
"timestamp": datetime.now() - timedelta(days=4-i)
})
breadth_tool.cache_manager.get.return_value = None
breadth_tool.db_manager.fetch_all.return_value = trend_data
result = await breadth_tool.execute({
"market": "KOSPI",
"period": "1w"
})
content = result[0]
import json
data = json.loads(content.text)
assert "trend_analysis" in data
trend = data["trend_analysis"]
assert trend["direction"] == "μμΉ"
assert "strength" in trend
assert trend["days_analyzed"] == 5