Skip to main content
Glama

MCP Market Statistics Server

by whdghk1907
test_market_tools.pyβ€’11.9 kB
"""μ‹œμž₯ κ°œμš” 도ꡬ ν…ŒμŠ€νŠΈ""" import pytest from datetime import datetime, timedelta from unittest.mock import Mock, AsyncMock, patch from src.tools.market_tools import MarketOverviewTool from src.exceptions import DataValidationError, DatabaseConnectionError class TestMarketOverviewTool: """μ‹œμž₯ κ°œμš” 도ꡬ ν…ŒμŠ€νŠΈ""" @pytest.fixture def mock_db_manager(self): """Mock λ°μ΄ν„°λ² μ΄μŠ€ λ§€λ‹ˆμ €""" db_manager = AsyncMock() return db_manager @pytest.fixture def mock_cache_manager(self): """Mock μΊμ‹œ λ§€λ‹ˆμ €""" cache_manager = AsyncMock() return cache_manager @pytest.fixture def market_tool(self, mock_db_manager, mock_cache_manager): """μ‹œμž₯ κ°œμš” 도ꡬ μΈμŠ€ν„΄μŠ€""" return MarketOverviewTool(mock_db_manager, mock_cache_manager) @pytest.fixture def sample_market_data(self): """μƒ˜ν”Œ μ‹œμž₯ 데이터""" return [ { "index_code": "KOSPI", "current_value": 2650.50, "change": 15.30, "change_rate": 0.58, "volume": 450000000, "market_cap": 1850000000000000, "timestamp": datetime.now() }, { "index_code": "KOSDAQ", "current_value": 850.25, "change": -5.75, "change_rate": -0.67, "volume": 180000000, "market_cap": 285000000000000, "timestamp": datetime.now() } ] def test_tool_initialization(self, market_tool, mock_db_manager, mock_cache_manager): """도ꡬ μ΄ˆκΈ°ν™” ν…ŒμŠ€νŠΈ""" assert market_tool.name == "get_market_overview" assert market_tool.description is not None assert "μ‹œμž₯" in market_tool.description assert market_tool.db_manager == mock_db_manager assert market_tool.cache_manager == mock_cache_manager def test_tool_definition(self, market_tool): """도ꡬ μ •μ˜ ν…ŒμŠ€νŠΈ""" definition = market_tool.get_tool_definition() assert definition.name == "get_market_overview" 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 "include_details" in properties # market νŒŒλΌλ―Έν„° 검증 market_prop = properties["market"] assert market_prop["type"] == "string" assert "enum" in market_prop assert "KOSPI" in market_prop["enum"] assert "KOSDAQ" in market_prop["enum"] assert "ALL" in market_prop["enum"] @pytest.mark.asyncio async def test_execute_basic_overview_with_cache_miss(self, market_tool, sample_market_data): """μΊμ‹œ 미슀 μ‹œ κΈ°λ³Έ μ‹œμž₯ κ°œμš” 쑰회 ν…ŒμŠ€νŠΈ""" # μΊμ‹œ 미슀 μ„€μ • market_tool.cache_manager.get.return_value = None # λ°μ΄ν„°λ² μ΄μŠ€ 응닡 μ„€μ • market_tool.db_manager.fetch_all.return_value = sample_market_data # μ‹€ν–‰ result = await market_tool.execute({"market": "ALL", "include_details": 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 "market_summary" in data assert "indices" in data # μ§€μˆ˜ 데이터 검증 indices = data["indices"] assert len(indices) == 2 kospi_data = next((idx for idx in indices if idx["index_code"] == "KOSPI"), None) assert kospi_data is not None assert kospi_data["current_value"] == 2650.50 assert kospi_data["change"] == 15.30 assert kospi_data["change_rate"] == 0.58 # μΊμ‹œ μ €μž₯ 확인 market_tool.cache_manager.set.assert_called_once() # λ°μ΄ν„°λ² μ΄μŠ€ 쿼리 확인 market_tool.db_manager.fetch_all.assert_called_once() @pytest.mark.asyncio async def test_execute_with_cache_hit(self, market_tool): """μΊμ‹œ 히트 μ‹œ ν…ŒμŠ€νŠΈ""" # μΊμ‹œ 데이터 μ„€μ • cached_data = { "timestamp": datetime.now().isoformat(), "market_summary": {"total_market_cap": 2000000000000000}, "indices": [{"index_code": "KOSPI", "current_value": 2650.50}] } market_tool.cache_manager.get.return_value = cached_data # μ‹€ν–‰ result = await market_tool.execute({"market": "KOSPI"}) # κ²°κ³Ό 검증 assert len(result) == 1 content = result[0] import json data = json.loads(content.text) assert data == cached_data # λ°μ΄ν„°λ² μ΄μŠ€ 호좜이 μ—†μ–΄μ•Ό 함 market_tool.db_manager.fetch_all.assert_not_called() # μΊμ‹œ μ €μž₯ 호좜이 μ—†μ–΄μ•Ό 함 market_tool.cache_manager.set.assert_not_called() @pytest.mark.asyncio async def test_execute_with_details(self, market_tool, sample_market_data): """상세 정보 포함 쑰회 ν…ŒμŠ€νŠΈ""" # μΊμ‹œ 미슀 market_tool.cache_manager.get.return_value = None # 메인 데이터 market_tool.db_manager.fetch_all.return_value = sample_market_data # μΆ”κ°€ 상세 데이터 Mock market_tool.db_manager.fetch_one.side_effect = [ {"advancing": 850, "declining": 680, "unchanged": 120}, # μ‹œμž₯ 폭 데이터 {"new_highs": 45, "new_lows": 23}, # μ‹ κ³ κ°€/μ‹ μ €κ°€ 데이터 ] # μ‹€ν–‰ result = await market_tool.execute({ "market": "ALL", "include_details": True }) # κ²°κ³Ό 검증 content = result[0] import json data = json.loads(content.text) assert "market_breadth" in data assert "new_highs_lows" in data # 상세 데이터 검증 breadth = data["market_breadth"] assert breadth["advancing"] == 850 assert breadth["declining"] == 680 highs_lows = data["new_highs_lows"] assert highs_lows["new_highs"] == 45 assert highs_lows["new_lows"] == 23 # 닀쀑 λ°μ΄ν„°λ² μ΄μŠ€ 쿼리 확인 assert market_tool.db_manager.fetch_all.call_count == 1 assert market_tool.db_manager.fetch_one.call_count == 2 @pytest.mark.asyncio async def test_execute_specific_market(self, market_tool, sample_market_data): """νŠΉμ • μ‹œμž₯ 쑰회 ν…ŒμŠ€νŠΈ""" # KOSPI만 ν•„ν„°λ§λœ 데이터 kospi_data = [data for data in sample_market_data if data["index_code"] == "KOSPI"] market_tool.cache_manager.get.return_value = None market_tool.db_manager.fetch_all.return_value = kospi_data # μ‹€ν–‰ result = await market_tool.execute({"market": "KOSPI"}) # κ²°κ³Ό 검증 content = result[0] import json data = json.loads(content.text) indices = data["indices"] assert len(indices) == 1 assert indices[0]["index_code"] == "KOSPI" # μ˜¬λ°”λ₯Έ WHERE 절둜 μΏΌλ¦¬λ˜μ—ˆλŠ”μ§€ 확인 call_args = market_tool.db_manager.fetch_all.call_args[0] query = call_args[0] assert "WHERE" in query.upper() assert "KOSPI" in call_args[1] or "kospi" in str(call_args).lower() @pytest.mark.asyncio async def test_database_error_handling(self, market_tool): """λ°μ΄ν„°λ² μ΄μŠ€ 였λ₯˜ 처리 ν…ŒμŠ€νŠΈ""" market_tool.cache_manager.get.return_value = None market_tool.db_manager.fetch_all.side_effect = DatabaseConnectionError("DB μ—°κ²° μ‹€νŒ¨") with pytest.raises(DatabaseConnectionError): await market_tool.execute({"market": "ALL"}) @pytest.mark.asyncio async def test_empty_data_handling(self, market_tool): """빈 데이터 처리 ν…ŒμŠ€νŠΈ""" market_tool.cache_manager.get.return_value = None market_tool.db_manager.fetch_all.return_value = [] result = await market_tool.execute({"market": "ALL"}) content = result[0] import json data = json.loads(content.text) assert data["indices"] == [] assert "message" in data assert "데이터가 μ—†μŠ΅λ‹ˆλ‹€" in data["message"] or "no data" in data["message"].lower() @pytest.mark.asyncio async def test_invalid_market_parameter(self, market_tool): """잘λͺ»λœ μ‹œμž₯ νŒŒλΌλ―Έν„° ν…ŒμŠ€νŠΈ""" with pytest.raises(ValueError, match="Invalid market"): await market_tool.execute({"market": "INVALID_MARKET"}) @pytest.mark.asyncio async def test_cache_key_generation(self, market_tool): """μΊμ‹œ ν‚€ 생성 ν…ŒμŠ€νŠΈ""" market_tool.cache_manager.get.return_value = None market_tool.db_manager.fetch_all.return_value = [] await market_tool.execute({"market": "KOSPI", "include_details": True}) # μΊμ‹œ 쑰회 μ‹œ μ˜¬λ°”λ₯Έ ν‚€ μ‚¬μš© 확인 cache_key = market_tool.cache_manager.get.call_args[0][0] assert "market_overview" in cache_key assert "KOSPI" in cache_key assert "True" in cache_key # include_details @pytest.mark.asyncio async def test_data_freshness_validation(self, market_tool): """데이터 신선도 검증 ν…ŒμŠ€νŠΈ""" # 5λΆ„ μ „ 데이터 (λ§Œλ£Œλ˜μ§€ μ•ŠμŒ) fresh_time = datetime.now() - timedelta(minutes=3) market_tool.cache_manager.get.return_value = { "timestamp": fresh_time.isoformat(), "indices": [] } result = await market_tool.execute({"market": "ALL"}) # μΊμ‹œ 데이터 μ‚¬μš©λ¨ market_tool.db_manager.fetch_all.assert_not_called() # 10λΆ„ μ „ 데이터 (만료됨) - μƒˆλ‘œμš΄ 도ꡬ μΈμŠ€ν„΄μŠ€λ‘œ ν…ŒμŠ€νŠΈ market_tool.cache_manager.reset_mock() market_tool.db_manager.reset_mock() stale_time = datetime.now() - timedelta(minutes=10) market_tool.cache_manager.get.return_value = { "timestamp": stale_time.isoformat(), "indices": [] } market_tool.db_manager.fetch_all.return_value = [] await market_tool.execute({"market": "ALL"}) # λ°μ΄ν„°λ² μ΄μŠ€μ—μ„œ μƒˆλ‘œ 쑰회됨 market_tool.db_manager.fetch_all.assert_called_once() @pytest.mark.asyncio async def test_concurrent_execution_safety(self, market_tool, sample_market_data): """λ™μ‹œ μ‹€ν–‰ μ•ˆμ „μ„± ν…ŒμŠ€νŠΈ""" import asyncio market_tool.cache_manager.get.return_value = None market_tool.db_manager.fetch_all.return_value = sample_market_data # λ™μ‹œμ— μ—¬λŸ¬ μš”μ²­ μ‹€ν–‰ tasks = [ market_tool.execute({"market": "ALL"}) for _ in range(5) ] results = await asyncio.gather(*tasks) # λͺ¨λ“  κ²°κ³Όκ°€ 동일해야 함 assert len(results) == 5 for result in results: assert len(result) == 1 content = result[0] import json data = json.loads(content.text) assert len(data["indices"]) == 2

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-market-statistics'

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