Skip to main content
Glama

MCP Sheet Parser

by yuqie6
MIT License
3
  • Apple
test_tools.py19.3 kB
import pytest import json from unittest.mock import MagicMock, AsyncMock, patch from src.models.tools import register_tools, _handle_convert_to_html, _handle_parse_sheet, _handle_apply_changes, _generate_next_steps_guidance from src.core_service import CoreService from mcp.server import Server from mcp.types import TextContent @pytest.fixture def mock_server(): """Fixture for a mocked Server instance.""" server = MagicMock(spec=Server) list_tools_decorator = MagicMock() call_tool_decorator = MagicMock() server.list_tools.return_value = list_tools_decorator server.call_tool.return_value = call_tool_decorator return server @pytest.fixture def mock_core_service(): """Fixture for a mocked CoreService instance.""" return MagicMock(spec=CoreService) def test_register_tools(mock_server): """Test that tools are registered correctly.""" register_tools(mock_server) mock_server.list_tools.assert_called_once() mock_server.call_tool.assert_called_once() mock_server.list_tools.return_value.assert_called_once() mock_server.call_tool.return_value.assert_called_once() @pytest.mark.asyncio async def test_handle_list_tools(mock_server): """Test the handle_list_tools function.""" register_tools(mock_server) list_tools_func = mock_server.list_tools.return_value.call_args[0][0] tools = await list_tools_func() assert len(tools) == 3 assert tools[0].name == "convert_to_html" assert tools[1].name == "parse_sheet" assert tools[2].name == "apply_changes" @pytest.mark.asyncio async def test_handle_call_tool_dispatch(mock_server, mock_core_service): """Test that handle_call_tool dispatches to the correct handler.""" register_tools(mock_server) call_tool_func = mock_server.call_tool.return_value.call_args[0][0] with patch('src.models.tools._handle_convert_to_html', new_callable=AsyncMock) as mock_convert: await call_tool_func("convert_to_html", {}) mock_convert.assert_called_once() with patch('src.models.tools._handle_parse_sheet', new_callable=AsyncMock) as mock_parse: await call_tool_func("parse_sheet", {}) mock_parse.assert_called_once() with patch('src.models.tools._handle_apply_changes', new_callable=AsyncMock) as mock_apply: await call_tool_func("apply_changes", {}) mock_apply.assert_called_once() @pytest.mark.asyncio async def test_handle_call_tool_unknown_tool(mock_server): """Test handle_call_tool with an unknown tool name.""" register_tools(mock_server) call_tool_func = mock_server.call_tool.return_value.call_args[0][0] result = await call_tool_func("unknown_tool", {}) assert len(result) == 1 assert isinstance(result[0], TextContent) assert "未知工具: unknown_tool" in result[0].text @pytest.mark.asyncio async def test_handle_call_tool_exception(mock_server): """Test handle_call_tool exception handling.""" register_tools(mock_server) call_tool_func = mock_server.call_tool.return_value.call_args[0][0] with patch('src.models.tools._handle_convert_to_html', new_callable=AsyncMock) as mock_convert: mock_convert.side_effect = Exception("Test Error") result = await call_tool_func("convert_to_html", {}) assert len(result) == 1 assert "错误: Test Error" in result[0].text # Tests for _handle_convert_to_html @pytest.mark.asyncio async def test_handle_convert_to_html_success(mock_core_service): """Test _handle_convert_to_html success case.""" mock_core_service.convert_to_html.return_value = [{"file_path": "test.html", "file_size_kb": 10}] args = {"file_path": "test.xlsx"} result = await _handle_convert_to_html(args, mock_core_service) response = json.loads(result[0].text) assert response["success"] is True assert response["operation"] == "convert_to_html" assert len(response["results"]) == 1 @pytest.mark.asyncio async def test_handle_convert_to_html_file_not_found(mock_core_service): """Test _handle_convert_to_html with FileNotFoundError.""" mock_core_service.convert_to_html.side_effect = FileNotFoundError("File not found") args = {"file_path": "nonexistent.xlsx"} result = await _handle_convert_to_html(args, mock_core_service) response = json.loads(result[0].text) assert response["success"] is False assert response["error_type"] == "file_not_found" # Tests for _handle_parse_sheet @pytest.mark.asyncio async def test_handle_parse_sheet_success(mock_core_service): """Test _handle_parse_sheet success case.""" mock_core_service.parse_sheet_optimized.return_value = {"metadata": {}, "preview_rows": []} args = {"file_path": "test.xlsx"} result = await _handle_parse_sheet(args, mock_core_service) response = json.loads(result[0].text) assert response["success"] is True assert response["operation"] == "parse_sheet" @pytest.mark.asyncio async def test_handle_parse_sheet_value_error(mock_core_service): """Test _handle_parse_sheet with ValueError.""" args = {"file_path": "test.xlsx", "preview_rows": -1} result = await _handle_parse_sheet(args, mock_core_service) response = json.loads(result[0].text) assert response["success"] is False assert response["error_type"] == "invalid_parameter" # Tests for _handle_apply_changes @pytest.mark.asyncio async def test_handle_apply_changes_success(mock_core_service): """Test _handle_apply_changes success case.""" mock_core_service.apply_changes.return_value = {"status": "success"} args = {"file_path": "test.xlsx", "table_model_json": {"sheet_name": "Sheet1", "headers": [], "rows": []}} result = await _handle_apply_changes(args, mock_core_service) response = json.loads(result[0].text) assert response["success"] is True assert response["operation"] == "apply_changes" @pytest.mark.asyncio async def test_handle_apply_changes_permission_error(mock_core_service): """Test _handle_apply_changes with PermissionError.""" mock_core_service.apply_changes.side_effect = PermissionError("Permission denied") args = {"file_path": "test.xlsx", "table_model_json": {"sheet_name": "Sheet1", "headers": [], "rows": []}} result = await _handle_apply_changes(args, mock_core_service) response = json.loads(result[0].text) assert response["success"] is False assert response["error_type"] == "permission_error" # Tests for _generate_next_steps_guidance def test_generate_next_steps_guidance(): """Test _generate_next_steps_guidance logic.""" # Case 1: Full data not included result_meta = {"metadata": {"total_rows": 100, "preview_rows": 5, "has_styles": False, "total_cells": 500}} guidance = _generate_next_steps_guidance(result_meta, False, False) assert "设置include_full_data=true获取完整数据" in guidance[0] # Case 2: Styles not included result_meta = {"metadata": {"total_rows": 10, "preview_rows": 10, "has_styles": True, "total_cells": 50}} guidance = _generate_next_steps_guidance(result_meta, True, False) assert "设置include_styles=true获取样式数据" in guidance[0] # Case 3: Large file result_meta = {"metadata": {"total_rows": 10, "preview_rows": 10, "has_styles": False, "total_cells": 2000}} guidance = _generate_next_steps_guidance(result_meta, True, True) assert "建议使用range_string参数获取特定范围" in guidance[0] # Case 4: All data loaded result_meta = {"metadata": {"total_rows": 10, "preview_rows": 10, "has_styles": False, "total_cells": 50}} guidance = _generate_next_steps_guidance(result_meta, True, True) assert "数据已完整加载" in guidance[0] class TestConvertToHtmlExceptionHandling: """测试convert_to_html的异常处理。""" @pytest.mark.asyncio async def test_handle_convert_to_html_permission_error(self, mock_core_service): """ TDD测试:_handle_convert_to_html应该处理PermissionError 这个测试覆盖第205-214行的PermissionError处理代码 """ mock_core_service.convert_to_html.side_effect = PermissionError("Permission denied") arguments = { "file_path": "test.xlsx", "output_path": "output.html" } result = await _handle_convert_to_html(arguments, mock_core_service) # 验证返回的错误响应 assert len(result) == 1 assert isinstance(result[0], TextContent) response = json.loads(result[0].text) assert response["success"] is False assert response["error_type"] == "permission_error" assert "权限不足" in response["error_message"] @pytest.mark.asyncio async def test_handle_convert_to_html_value_error(self, mock_core_service): """ TDD测试:_handle_convert_to_html应该处理ValueError 这个测试覆盖第215-224行的ValueError处理代码 """ mock_core_service.convert_to_html.side_effect = ValueError("Invalid parameter") arguments = { "file_path": "test.xlsx", "output_path": "output.html" } result = await _handle_convert_to_html(arguments, mock_core_service) # 验证返回的错误响应 assert len(result) == 1 assert isinstance(result[0], TextContent) response = json.loads(result[0].text) assert response["success"] is False assert response["error_type"] == "invalid_parameter" assert "参数错误" in response["error_message"] @pytest.mark.asyncio async def test_handle_convert_to_html_general_exception(self, mock_core_service): """ TDD测试:_handle_convert_to_html应该处理一般异常 这个测试覆盖第225行之后的一般异常处理代码 """ mock_core_service.convert_to_html.side_effect = RuntimeError("Unexpected error") arguments = { "file_path": "test.xlsx", "output_path": "output.html" } result = await _handle_convert_to_html(arguments, mock_core_service) # 验证返回的错误响应 assert len(result) == 1 assert isinstance(result[0], TextContent) response = json.loads(result[0].text) assert response["success"] is False assert response["error_type"] == "conversion_error" class TestParseSheetParameterValidation: """测试parse_sheet的参数验证。""" @pytest.mark.asyncio async def test_handle_parse_sheet_invalid_file_path(self, mock_core_service): """ TDD测试:_handle_parse_sheet应该验证file_path参数 这个测试覆盖第244行的file_path验证代码 """ arguments = { "file_path": "" # 空字符串 } result = await _handle_parse_sheet(arguments, mock_core_service) # 验证返回的错误响应 assert len(result) == 1 response = json.loads(result[0].text) assert response["success"] is False assert response["error_type"] == "invalid_parameter" assert "file_path必须是非空字符串" in response["error_message"] @pytest.mark.asyncio async def test_handle_parse_sheet_invalid_sheet_name(self, mock_core_service): """ TDD测试:_handle_parse_sheet应该验证sheet_name参数 这个测试覆盖第248行的sheet_name验证代码 """ arguments = { "file_path": "test.xlsx", "sheet_name": 123 # 非字符串 } result = await _handle_parse_sheet(arguments, mock_core_service) # 验证返回的错误响应 assert len(result) == 1 response = json.loads(result[0].text) assert response["success"] is False assert response["error_type"] == "invalid_parameter" assert "sheet_name必须是字符串" in response["error_message"] @pytest.mark.asyncio async def test_handle_parse_sheet_invalid_range_string(self, mock_core_service): """ TDD测试:_handle_parse_sheet应该验证range_string参数 这个测试覆盖第252行的range_string验证代码 """ arguments = { "file_path": "test.xlsx", "range_string": 123 # 非字符串 } result = await _handle_parse_sheet(arguments, mock_core_service) # 验证返回的错误响应 assert len(result) == 1 response = json.loads(result[0].text) assert response["success"] is False assert response["error_type"] == "invalid_parameter" assert "range_string必须是字符串" in response["error_message"] @pytest.mark.asyncio async def test_handle_parse_sheet_invalid_include_full_data(self, mock_core_service): """ TDD测试:_handle_parse_sheet应该验证include_full_data参数 这个测试覆盖第256行的include_full_data验证代码 """ arguments = { "file_path": "test.xlsx", "include_full_data": "true" # 非布尔值 } result = await _handle_parse_sheet(arguments, mock_core_service) # 验证返回的错误响应 assert len(result) == 1 response = json.loads(result[0].text) assert response["success"] is False assert response["error_type"] == "invalid_parameter" assert "include_full_data必须是布尔值" in response["error_message"] @pytest.mark.asyncio async def test_handle_parse_sheet_invalid_include_styles(self, mock_core_service): """ TDD测试:_handle_parse_sheet应该验证include_styles参数 这个测试覆盖第260行的include_styles验证代码 """ arguments = { "file_path": "test.xlsx", "include_styles": "false" # 非布尔值 } result = await _handle_parse_sheet(arguments, mock_core_service) # 验证返回的错误响应 assert len(result) == 1 response = json.loads(result[0].text) assert response["success"] is False assert response["error_type"] == "invalid_parameter" assert "include_styles必须是布尔值" in response["error_message"] @pytest.mark.asyncio async def test_handle_parse_sheet_invalid_max_rows(self, mock_core_service): """ TDD测试:_handle_parse_sheet应该验证max_rows参数 这个测试覆盖第268行的max_rows验证代码 """ arguments = { "file_path": "test.xlsx", "max_rows": -1 # 负数 } result = await _handle_parse_sheet(arguments, mock_core_service) # 验证返回的错误响应 assert len(result) == 1 response = json.loads(result[0].text) assert response["success"] is False assert response["error_type"] == "invalid_parameter" assert "max_rows必须是正整数或None" in response["error_message"] class TestParseSheetExceptionHandling: """测试parse_sheet的异常处理。""" @pytest.mark.asyncio async def test_handle_parse_sheet_file_not_found_error(self, mock_core_service): """ TDD测试:_handle_parse_sheet应该处理FileNotFoundError 这个测试覆盖第304行的FileNotFoundError处理代码 """ mock_core_service.parse_sheet_optimized.side_effect = FileNotFoundError("File not found") arguments = { "file_path": "nonexistent.xlsx" } result = await _handle_parse_sheet(arguments, mock_core_service) # 验证返回的错误响应 assert len(result) == 1 response = json.loads(result[0].text) assert response["success"] is False assert response["error_type"] == "file_not_found" assert "文件未找到" in response["error_message"] @pytest.mark.asyncio async def test_handle_parse_sheet_general_exception(self, mock_core_service): """ TDD测试:_handle_parse_sheet应该处理一般异常 这个测试覆盖第323-324行的一般异常处理代码 """ mock_core_service.parse_sheet_optimized.side_effect = RuntimeError("Parsing failed") arguments = { "file_path": "test.xlsx" } result = await _handle_parse_sheet(arguments, mock_core_service) # 验证返回的错误响应 assert len(result) == 1 response = json.loads(result[0].text) assert response["success"] is False assert response["error_type"] == "parsing_error" assert "解析失败" in response["error_message"] class TestApplyChangesExceptionHandling: """测试apply_changes的异常处理。""" @pytest.mark.asyncio async def test_handle_apply_changes_file_not_found_error(self, mock_core_service): """ TDD测试:_handle_apply_changes应该处理FileNotFoundError 这个测试覆盖第380行的FileNotFoundError处理代码 """ mock_core_service.apply_changes.side_effect = FileNotFoundError("File not found") arguments = { "file_path": "nonexistent.xlsx", "table_model_json": '{"sheet_name": "Sheet1", "headers": [], "rows": []}' } result = await _handle_apply_changes(arguments, mock_core_service) # 验证返回的错误响应 assert len(result) == 1 response = json.loads(result[0].text) assert response["success"] is False assert response["error_type"] == "file_not_found" assert "文件未找到" in response["error_message"] @pytest.mark.asyncio async def test_handle_apply_changes_value_error(self, mock_core_service): """ TDD测试:_handle_apply_changes应该处理ValueError 这个测试覆盖第399-408行的ValueError处理代码 """ mock_core_service.apply_changes.side_effect = ValueError("Invalid data format") arguments = { "file_path": "test.xlsx", "table_model_json": '{"invalid": "data"}' } result = await _handle_apply_changes(arguments, mock_core_service) # 验证返回的错误响应 assert len(result) == 1 response = json.loads(result[0].text) assert response["success"] is False assert response["error_type"] == "invalid_data" assert "数据格式错误" in response["error_message"] @pytest.mark.asyncio async def test_handle_apply_changes_general_exception(self, mock_core_service): """ TDD测试:_handle_apply_changes应该处理一般异常 这个测试覆盖第409-410行之后的一般异常处理代码 """ mock_core_service.apply_changes.side_effect = RuntimeError("Apply failed") arguments = { "file_path": "test.xlsx", "table_model_json": '{"sheet_name": "Sheet1", "headers": [], "rows": []}' } result = await _handle_apply_changes(arguments, mock_core_service) # 验证返回的错误响应 assert len(result) == 1 response = json.loads(result[0].text) assert response["success"] is False assert response["error_type"] == "write_error"

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/yuqie6/MCP-Sheet-Parser-cot'

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