Skip to main content
Glama

BOJ MCP Server

test_solvedac_api.py6.54 kB
import pytest import pytest_asyncio from unittest.mock import MagicMock, patch from solvedac_server.server import create_server, UserShowResponse from solvedac_server.search_query import ProblemSearchResponse from fastmcp import FastMCP, Client from mcp.shared.exceptions import McpError # 1. Fixtures @pytest.fixture(scope="module") def mcp_app(): """테스트용 FastMCP 서버 인스턴스를 생성하는 픽스처""" return create_server() @pytest_asyncio.fixture async def mcp_client(mcp_app: FastMCP): """앱의 생명주기를 자동으로 관리하는 인메모리 클라이언트를 제공합니다.""" async with Client(mcp_app._fastmcp) as client: yield client # 2. Mock Helper def mock_response(status_code, json_data=None): """httpx 응답 객체를 Mock합니다.""" mock = MagicMock() mock.status_code = status_code mock.json.return_value = json_data if json_data is not None else {} if not (200 <= status_code < 300): from httpx import HTTPStatusError mock.raise_for_status.side_effect = HTTPStatusError( message=f"Status code {status_code}", request=MagicMock(), response=mock ) return mock # 3. Resource Tests @pytest.mark.asyncio async def test_resource_get_user_info_success(mcp_client: Client): """get_user_info 리소스의 성공 케이스를 테스트합니다.""" expected_data = {"handle": "testuser", "tier": 15, "rating": 1500, "solvedCount": 100} with patch("httpx.AsyncClient.get") as mock_get: mock_get.return_value = mock_response(200, expected_data) result = await mcp_client.read_resource("solvedac://users/testuser") parsed_result = UserShowResponse.model_validate_json(result[0].text) assert isinstance(parsed_result, UserShowResponse) assert parsed_result.handle == "testuser" mock_get.assert_called_once_with("/user/show", params={"handle": "testuser"}) @pytest.mark.asyncio async def test_resource_get_user_info_404_error(mcp_client: Client): """get_user_info 리소스의 404 에러 케이스를 테스트합니다.""" with patch("httpx.AsyncClient.get") as mock_get: mock_get.return_value = mock_response(404) with pytest.raises(McpError, match=r"사용자 핸들 'nonexistent'을\(를\) 찾을 수 없습니다."): await mcp_client.read_resource("solvedac://users/nonexistent") @pytest.mark.xfail(reason="fastmcp.Client.read_resource는 현재 URI 경로 외 파라미터 전달을 지원하지 않는 것으로 보임") @pytest.mark.asyncio async def test_resource_search_problems_success(mcp_client: Client): """search_problems 리소스의 성공 케이스를 테스트합니다.""" problem_data = {"problemId": 1000, "titleKo": "A+B", "level": 1, "isSolvable": True} expected_data = {"count": 1, "items": [problem_data]} with patch("httpx.AsyncClient.get") as mock_get: mock_get.return_value = mock_response(200, expected_data) # 참고: 이 API 호출은 현재 프레임워크에서 동작하지 않음 result = await mcp_client.read_resource("solvedac://problems/search/_?query=tier:b5&page=1") parsed_result = ProblemSearchResponse.model_validate_json(result[0].text) assert isinstance(parsed_result, ProblemSearchResponse) assert parsed_result.count == 1 assert parsed_result.items[0].problemId == 1000 mock_get.assert_called_once_with("/search/problem", params={"query": "tier:b5", "page": 1}) # 4. Tool Tests @pytest.mark.asyncio async def test_tool_get_user_info(mcp_client: Client): """solvedac_get_user_info 툴 호출을 테스트합니다.""" expected_data = {"handle": "testtool", "tier": 15, "rating": 1500, "solvedCount": 100} with patch("httpx.AsyncClient.get") as mock_get: mock_get.return_value = mock_response(200, expected_data) result = await mcp_client.call_tool("solvedac_get_user_info", {"handle": "testtool"}) # call_tool의 결과는 dict이므로, Pydantic 모델로 변환하여 검증합니다. parsed_result = UserShowResponse.model_validate(result.data) assert isinstance(parsed_result, UserShowResponse) assert parsed_result.handle == "testtool" @pytest.mark.asyncio async def test_tool_search_problems_limit(mcp_client: Client): """solvedac_search_problems 툴의 limit 기능을 테스트합니다.""" mock_problems = [{"problemId": i, "titleKo": f"Problem {i}", "level": 10, "isSolvable": True} for i in range(1, 6)] mock_data = {"count": 50, "items": mock_problems * 10} with patch("httpx.AsyncClient.get") as mock_get: mock_get.return_value = mock_response(200, mock_data) result = await mcp_client.call_tool("solvedac_search_problems", {"query": "tier:d1", "limit": 3}) parsed_result = ProblemSearchResponse.model_validate(result.data) assert isinstance(parsed_result, ProblemSearchResponse) assert len(parsed_result.items) == 3 assert parsed_result.count == 3 @pytest.mark.asyncio async def test_tool_search_problems_default_limit(mcp_client: Client): """solvedac_search_problems 툴의 기본 limit 기능을 테스트합니다.""" mock_problems = [{"problemId": i, "titleKo": f"Problem {i}", "level": 10, "isSolvable": True} for i in range(1, 11)] mock_data = {"count": 50, "items": mock_problems * 5} with patch("httpx.AsyncClient.get") as mock_get: mock_get.return_value = mock_response(200, mock_data) result = await mcp_client.call_tool("solvedac_search_problems", {"query": "tier:d1"}) parsed_result = ProblemSearchResponse.model_validate(result.data) assert isinstance(parsed_result, ProblemSearchResponse) assert len(parsed_result.items) == 5 assert parsed_result.count == 5 # 5. Prompt Test @pytest.mark.asyncio async def test_prompt_search_workflow(mcp_client: Client): """search_workflow_prompt 프롬프트의 구조를 테스트합니다.""" natural_req = "실버~골드 사이 DP 5문제" page_num = 2 result = await mcp_client.get_prompt("solvedac.search-workflow", {"natural_request": natural_req, "page": page_num}) assert isinstance(result.messages, list) assert len(result.messages) == 2 assert result.messages[0].role == "assistant" assert "Convert the user's request" in result.messages[0].content.text assert result.messages[1].role == "user" assert f"요청(자연어): {natural_req}" in result.messages[1].content.text assert f"페이지: {page_num}" in result.messages[1].content.text

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/Junwoo-Seo-1998/boj-mcp-server'

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