test_valuation_metrics.pyβ’11.3 kB
"""
Unit tests for valuation metrics functionality
TDD Red Phase: Write failing tests for valuation analysis
"""
import pytest
import asyncio
from unittest.mock import Mock, AsyncMock, patch
from datetime import datetime, date
from typing import Any, Dict, List, Optional
# Test imports - initially will fail (TDD Red phase)
from src.server import MCPStockDetailsServer
from src.tools.valuation_tools import ValuationAnalyzer
from src.exceptions import MCPStockDetailsError, InsufficientDataError
class TestValuationMetrics:
"""Test cases for valuation metrics functionality"""
@pytest.fixture
def server_with_valuation(self):
"""Create server instance with valuation analyzer"""
server = MCPStockDetailsServer()
# This will fail initially - Red phase
server.valuation_analyzer = ValuationAnalyzer()
return server
@pytest.fixture
def sample_market_data(self):
"""Sample market data for valuation calculations"""
return {
"005930": { # Samsung Electronics
"current_price": 73000,
"market_cap": 435_000_000_000_000, # 435 trillion KRW
"shares_outstanding": 5_969_782_550,
"52_week_high": 80000,
"52_week_low": 65000,
"trading_volume": 15_000_000,
"financial_data": {
"2023": {
"revenue": 258_774_000_000_000,
"net_profit": 15_349_000_000_000,
"book_value": 319_684_000_000_000,
"ebitda": 28_500_000_000_000,
"enterprise_value": 420_000_000_000_000,
"free_cash_flow": 29_163_000_000_000,
"total_debt": 85_000_000_000_000,
"cash_and_equivalents": 25_000_000_000_000
}
}
}
}
@pytest.mark.asyncio
async def test_get_valuation_metrics_tool_registration(self, server_with_valuation):
"""Test that get_valuation_metrics tool is properly registered"""
tools = await server_with_valuation.list_tools()
tool_names = [tool.name for tool in tools]
assert "get_valuation_metrics" in tool_names
# Check tool description and parameters
valuation_tool = next(tool for tool in tools if tool.name == "get_valuation_metrics")
assert "valuation" in valuation_tool.description.lower()
assert "company_code" in valuation_tool.inputSchema["properties"]
@pytest.mark.asyncio
async def test_basic_price_multiples_calculation(self, server_with_valuation, sample_market_data):
"""Test basic price multiples (PER, PBR, PSR)"""
company_code = "005930"
with patch.object(server_with_valuation.valuation_analyzer, 'get_market_data') as mock_market:
mock_market.return_value = sample_market_data[company_code]
result = await server_with_valuation._handle_get_valuation_metrics({
"company_code": company_code,
"include_multiples": True
})
assert result is not None
assert len(result) > 0
content = result[0].text
# Should include price multiples
assert "VALUATION MULTIPLES" in content
assert "PER" in content or "P/E Ratio" in content
assert "PBR" in content or "P/B Ratio" in content
assert "PSR" in content or "P/S Ratio" in content
# Should show calculated values
assert "73,000" in content or "73000" in content # Current price
@pytest.mark.asyncio
async def test_enterprise_value_multiples(self, server_with_valuation, sample_market_data):
"""Test enterprise value multiples (EV/EBITDA, EV/Sales)"""
company_code = "005930"
with patch.object(server_with_valuation.valuation_analyzer, 'get_market_data') as mock_market:
mock_market.return_value = sample_market_data[company_code]
result = await server_with_valuation._handle_get_valuation_metrics({
"company_code": company_code,
"include_ev_multiples": True
})
assert result is not None
content = result[0].text
# Should include EV multiples
assert "ENTERPRISE VALUE MULTIPLES" in content
assert "EV/EBITDA" in content
assert "EV/Sales" in content or "EV/Revenue" in content
assert "Enterprise Value" in content
@pytest.mark.asyncio
async def test_historical_valuation_bands(self, server_with_valuation):
"""Test historical valuation bands analysis"""
company_code = "005930"
result = await server_with_valuation._handle_get_valuation_metrics({
"company_code": company_code,
"include_historical_bands": True,
"band_period": "3Y" # 3 years
})
assert result is not None
content = result[0].text
# Should include historical analysis
assert "HISTORICAL VALUATION BANDS" in content
assert "52-week" in content or "52 week" in content
assert "Average" in content or "Mean" in content
assert "Current vs Historical" in content
@pytest.mark.asyncio
async def test_peer_valuation_comparison(self, server_with_valuation):
"""Test peer group valuation comparison"""
company_code = "005930"
result = await server_with_valuation._handle_get_valuation_metrics({
"company_code": company_code,
"include_peer_comparison": True,
"industry_code": "26211" # Semiconductor
})
assert result is not None
content = result[0].text
# Should include peer comparison
assert "PEER VALUATION COMPARISON" in content
assert "Industry Average" in content or "Peer Average" in content
assert "Percentile" in content or "Ranking" in content
assert "vs Peers" in content
@pytest.mark.asyncio
async def test_valuation_summary_metrics(self, server_with_valuation, sample_market_data):
"""Test comprehensive valuation summary"""
company_code = "005930"
with patch.object(server_with_valuation.valuation_analyzer, 'get_market_data') as mock_market:
mock_market.return_value = sample_market_data[company_code]
result = await server_with_valuation._handle_get_valuation_metrics({
"company_code": company_code,
"analysis_type": "comprehensive"
})
assert result is not None
content = result[0].text
# Should include comprehensive analysis
assert "VALUATION SUMMARY" in content
assert "Market Capitalization" in content
assert "Fair Value Range" in content or "Valuation Range" in content
assert "Investment Recommendation" in content or "Valuation Assessment" in content
@pytest.mark.asyncio
async def test_dividend_yield_metrics(self, server_with_valuation):
"""Test dividend yield and payout analysis"""
company_code = "005930"
result = await server_with_valuation._handle_get_valuation_metrics({
"company_code": company_code,
"include_dividend_analysis": True
})
assert result is not None
content = result[0].text
# Should include dividend analysis
assert "DIVIDEND ANALYSIS" in content
assert "Dividend Yield" in content
assert "Payout Ratio" in content
assert "Dividend History" in content or "Payment History" in content
@pytest.mark.asyncio
async def test_valuation_metrics_error_handling(self, server_with_valuation):
"""Test error handling for valuation metrics"""
# Test invalid company code
with pytest.raises(MCPStockDetailsError):
await server_with_valuation._handle_get_valuation_metrics({
"company_code": "",
"include_multiples": True
})
# Test insufficient market data
with pytest.raises(InsufficientDataError):
await server_with_valuation._handle_get_valuation_metrics({
"company_code": "999999", # Non-existent company
"include_multiples": True
})
@pytest.mark.asyncio
async def test_valuation_metrics_output_formats(self, server_with_valuation, sample_market_data):
"""Test different output formats for valuation metrics"""
company_code = "005930"
with patch.object(server_with_valuation.valuation_analyzer, 'get_market_data') as mock_market:
mock_market.return_value = sample_market_data[company_code]
# Test detailed format
detailed = await server_with_valuation._handle_get_valuation_metrics({
"company_code": company_code,
"output_format": "detailed"
})
# Test summary format
summary = await server_with_valuation._handle_get_valuation_metrics({
"company_code": company_code,
"output_format": "summary"
})
assert detailed is not None and summary is not None
detailed_content = detailed[0].text
summary_content = summary[0].text
# Detailed should be more comprehensive
assert len(detailed_content) > len(summary_content)
assert "DETAILED VALUATION ANALYSIS" in detailed_content
assert "VALUATION SUMMARY" in summary_content
@pytest.mark.asyncio
async def test_valuation_metrics_performance(self, server_with_valuation, sample_market_data):
"""Test performance requirements for valuation metrics"""
company_code = "005930"
with patch.object(server_with_valuation.valuation_analyzer, 'get_market_data') as mock_market:
mock_market.return_value = sample_market_data[company_code]
import time
start_time = time.time()
result = await server_with_valuation._handle_get_valuation_metrics({
"company_code": company_code,
"analysis_type": "comprehensive",
"include_multiples": True,
"include_ev_multiples": True,
"include_historical_bands": True,
"include_peer_comparison": True
})
execution_time = time.time() - start_time
# Should complete within 3 seconds
assert execution_time < 3.0
assert result is not None
# Should have processing time info
content = result[0].text
assert "Processing Time" in content or len(content) > 500