Skip to main content
Glama

MCP Stock Details Server

by whdghk1907
test_shareholder_analyzer.pyβ€’14 kB
""" Unit tests for ShareholderAnalyzer class TDD Red Phase: Write failing tests for shareholder analysis engine """ import pytest import asyncio from unittest.mock import Mock, AsyncMock, patch from datetime import datetime, date, timedelta from typing import Dict, Any, List, Optional # Test imports - initially will fail (TDD Red phase) from src.tools.shareholder_tools import ShareholderAnalyzer from src.exceptions import MCPStockDetailsError, InsufficientDataError class TestShareholderAnalyzer: """Test cases for ShareholderAnalyzer class""" @pytest.fixture def shareholder_analyzer(self): """Create ShareholderAnalyzer instance for testing""" return ShareholderAnalyzer() @pytest.fixture def sample_shareholder_data(self): """Sample shareholder data for analysis""" return { "company_code": "005930", "major_shareholders": [ {"name": "κ΅­λ―Όμ—°κΈˆκ³΅λ‹¨", "shares": 148_712_953, "percentage": 9.57, "type": "institutional"}, {"name": "μ‚Όμ„±λ¬Όμ‚°", "shares": 133_284_628, "percentage": 8.58, "type": "corporate"}, {"name": "이재용", "shares": 18_352_174, "percentage": 1.18, "type": "individual"}, {"name": "홍라희", "shares": 15_248_921, "percentage": 0.98, "type": "individual"}, {"name": "이뢀진", "shares": 12_419_832, "percentage": 0.80, "type": "individual"} ], "total_shares": 5_969_782_550, "treasury_shares": 64_726_919, "dividend_history": [ {"year": 2023, "dividend_per_share": 1640, "total_dividend": 9787803622000}, {"year": 2022, "dividend_per_share": 1444, "total_dividend": 8617977438800}, {"year": 2021, "dividend_per_share": 1378, "total_dividend": 8225556394900} ] } @pytest.fixture def sample_governance_data(self): """Sample governance data for testing""" return { "board_composition": { "total_directors": 11, "independent_directors": 5, "inside_directors": 6, "female_directors": 2, "foreign_directors": 1 }, "committee_composition": { "audit_committee": {"total": 3, "independent": 3}, "compensation_committee": {"total": 4, "independent": 3}, "governance_committee": {"total": 5, "independent": 2} }, "board_meetings": { "annual_meetings": 8, "average_attendance": 92.5, "key_decisions": 24 } } @pytest.mark.asyncio async def test_analyze_major_shareholders(self, shareholder_analyzer, sample_shareholder_data): """Test major shareholders analysis""" analysis = await shareholder_analyzer.analyze_major_shareholders( shareholder_data=sample_shareholder_data, min_percentage=0.5 ) assert analysis is not None assert isinstance(analysis, dict) # Should contain major shareholders information assert "major_shareholders" in analysis assert "top_5_concentration" in analysis assert "ownership_summary" in analysis # Should have correct number of major shareholders major_shareholders = analysis["major_shareholders"] assert len(major_shareholders) >= 3 # Should calculate concentrations correctly top_5_concentration = analysis["top_5_concentration"] assert isinstance(top_5_concentration, float) assert 15 <= top_5_concentration <= 25 # Reasonable range @pytest.mark.asyncio async def test_calculate_ownership_structure(self, shareholder_analyzer, sample_shareholder_data): """Test ownership structure calculation""" ownership = await shareholder_analyzer.calculate_ownership_structure( shareholder_data=sample_shareholder_data, market_data={"foreign_ownership": 52.37, "institutional_ownership": 31.28} ) assert ownership is not None assert isinstance(ownership, dict) # Should contain key ownership metrics assert "free_float" in ownership assert "foreign_ownership" in ownership assert "institutional_ownership" in ownership assert "individual_ownership" in ownership assert "insider_ownership" in ownership # Ownership percentages should sum close to 100% total = (ownership["foreign_ownership"] + ownership["institutional_ownership"] + ownership["individual_ownership"]) assert 95 <= total <= 105 # Allow for rounding @pytest.mark.asyncio async def test_analyze_dividend_history(self, shareholder_analyzer, sample_shareholder_data): """Test dividend history analysis""" dividend_analysis = await shareholder_analyzer.analyze_dividend_history( dividend_data=sample_shareholder_data["dividend_history"], financial_metrics={"net_income": 65118000000000, "free_cash_flow": 45821000000000} ) assert dividend_analysis is not None assert isinstance(dividend_analysis, dict) # Should contain dividend analysis metrics assert "dividend_growth_rate" in dividend_analysis assert "average_yield" in dividend_analysis assert "dividend_consistency" in dividend_analysis assert "payout_ratio" in dividend_analysis assert "sustainability_score" in dividend_analysis # Growth rate should be reasonable growth_rate = dividend_analysis["dividend_growth_rate"] assert isinstance(growth_rate, float) assert -20 <= growth_rate <= 50 # Reasonable range @pytest.mark.asyncio async def test_calculate_governance_metrics(self, shareholder_analyzer, sample_governance_data): """Test governance metrics calculation""" governance = await shareholder_analyzer.calculate_governance_metrics( governance_data=sample_governance_data, company_metrics={"company_age": 54, "market_cap": 435000000000000} ) assert governance is not None assert isinstance(governance, dict) # Should contain governance metrics assert "board_independence_ratio" in governance assert "board_diversity_score" in governance assert "committee_effectiveness" in governance assert "overall_governance_score" in governance assert "governance_grade" in governance # Independence ratio should be calculated correctly independence_ratio = governance["board_independence_ratio"] expected_ratio = 5 / 11 * 100 # 5 independent out of 11 total assert abs(independence_ratio - expected_ratio) < 1 # Overall score should be 0-100 overall_score = governance["overall_governance_score"] assert 0 <= overall_score <= 100 # Grade should be letter grade assert governance["governance_grade"] in ["A", "B", "C", "D", "F"] @pytest.mark.asyncio async def test_analyze_shareholder_concentration(self, shareholder_analyzer, sample_shareholder_data): """Test shareholder concentration analysis""" concentration = await shareholder_analyzer.analyze_shareholder_concentration( shareholder_data=sample_shareholder_data, analysis_depth=10 ) assert concentration is not None assert isinstance(concentration, dict) # Should contain concentration metrics assert "hhi_index" in concentration assert "top_1_concentration" in concentration assert "top_5_concentration" in concentration assert "top_10_concentration" in concentration assert "concentration_level" in concentration # HHI should be calculated correctly hhi = concentration["hhi_index"] assert isinstance(hhi, float) assert 0 <= hhi <= 10000 # HHI range # Concentration level should be categorical assert concentration["concentration_level"] in ["low", "moderate", "high", "very_high"] @pytest.mark.asyncio async def test_track_shareholder_changes(self, shareholder_analyzer): """Test shareholder change tracking""" # Mock historical and current data historical_data = [ {"name": "κ΅­λ―Όμ—°κΈˆκ³΅λ‹¨", "percentage": 9.2, "date": "2023-06-30"}, {"name": "μ‚Όμ„±λ¬Όμ‚°", "percentage": 8.8, "date": "2023-06-30"} ] current_data = [ {"name": "κ΅­λ―Όμ—°κΈˆκ³΅λ‹¨", "percentage": 9.57, "date": "2023-12-31"}, {"name": "μ‚Όμ„±λ¬Όμ‚°", "percentage": 8.58, "date": "2023-12-31"} ] changes = await shareholder_analyzer.track_shareholder_changes( historical_data=historical_data, current_data=current_data, period="6M" ) assert changes is not None assert isinstance(changes, dict) # Should contain change tracking information assert "period" in changes assert "significant_changes" in changes assert "new_entrants" in changes assert "exits" in changes assert "net_changes" in changes # Should identify changes correctly significant_changes = changes["significant_changes"] assert len(significant_changes) >= 2 # Both shareholders changed @pytest.mark.asyncio async def test_analyze_voting_power(self, shareholder_analyzer, sample_shareholder_data): """Test voting power analysis""" voting_analysis = await shareholder_analyzer.analyze_voting_power( shareholder_data=sample_shareholder_data, voting_agreements=[{"parties": ["이재용", "홍라희", "이뢀진"], "type": "family_agreement"}] ) assert voting_analysis is not None assert isinstance(voting_analysis, dict) # Should contain voting power metrics assert "individual_voting_power" in voting_analysis assert "coalition_analysis" in voting_analysis assert "control_assessment" in voting_analysis assert "shareholder_rights" in voting_analysis # Should analyze family coalition coalition_analysis = voting_analysis["coalition_analysis"] assert len(coalition_analysis) > 0 @pytest.mark.asyncio async def test_generate_shareholder_insights(self, shareholder_analyzer, sample_shareholder_data): """Test shareholder insights generation""" insights = await shareholder_analyzer.generate_shareholder_insights( shareholder_data=sample_shareholder_data, market_context={"sector": "technology", "market_cap_tier": "large_cap"} ) assert insights is not None assert isinstance(insights, dict) # Should contain key insights assert "ownership_stability" in insights assert "governance_strengths" in insights assert "governance_concerns" in insights assert "dividend_policy_assessment" in insights assert "investment_implications" in insights # Each insight category should have content for category in insights.values(): assert isinstance(category, (str, list, dict)) @pytest.mark.asyncio async def test_comprehensive_shareholder_analysis(self, shareholder_analyzer, sample_shareholder_data): """Test comprehensive shareholder analysis""" comprehensive = await shareholder_analyzer.comprehensive_shareholder_analysis( company_code="005930", include_all_metrics=True ) assert comprehensive is not None assert isinstance(comprehensive, dict) # Should contain all major analysis components assert "ownership_analysis" in comprehensive assert "governance_analysis" in comprehensive assert "dividend_analysis" in comprehensive assert "concentration_analysis" in comprehensive assert "shareholder_summary" in comprehensive # Should have summary insights summary = comprehensive["shareholder_summary"] assert "key_findings" in summary assert "governance_score" in summary assert "ownership_quality" in summary @pytest.mark.asyncio async def test_error_handling_insufficient_data(self, shareholder_analyzer): """Test error handling for insufficient data""" # Test with insufficient shareholder data insufficient_data = {"company_code": "005930", "major_shareholders": []} with pytest.raises(InsufficientDataError): await shareholder_analyzer.analyze_major_shareholders(insufficient_data) @pytest.mark.asyncio async def test_error_handling_invalid_parameters(self, shareholder_analyzer, sample_shareholder_data): """Test error handling for invalid parameters""" # Test with invalid percentage threshold with pytest.raises(MCPStockDetailsError): await shareholder_analyzer.analyze_major_shareholders( shareholder_data=sample_shareholder_data, min_percentage=-1 # Invalid negative percentage ) # Test with invalid analysis depth with pytest.raises(MCPStockDetailsError): await shareholder_analyzer.analyze_shareholder_concentration( shareholder_data=sample_shareholder_data, analysis_depth=0 # Invalid zero depth )

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/whdghk1907/mcp-stock-details'

If you have feedback or need assistance with the MCP directory API, please join our Discord server