Skip to main content
Glama

MCP Sheet Parser

by yuqie6
MIT License
3
  • Apple
test_validators.py13.5 kB
import pytest from unittest.mock import MagicMock, patch from pathlib import Path from src.validators import FileValidator, RangeValidator, DataValidator, validate_file_input from src.exceptions import ValidationError, FileNotFoundError, FileAccessError, UnsupportedFileTypeError # --- FileValidator Tests --- @patch('src.validators.Path.exists', return_value=True) @patch('src.validators.Path.is_file', return_value=True) @patch('os.access', return_value=True) def test_file_validator_path_success(mock_access, mock_is_file, mock_exists): """Test successful file path validation.""" with patch('src.validators.Path.resolve', return_value=Path("/test/dummy.xlsx")): path = FileValidator.validate_file_path("/test/dummy.xlsx") assert isinstance(path, Path) def test_file_validator_path_non_existent(): """Test file path that does not exist.""" with patch('src.validators.Path.resolve', return_value=Path("/nonexistent/dummy.xlsx")): with pytest.raises(FileNotFoundError): FileValidator.validate_file_path("/nonexistent/dummy.xlsx") def test_file_validator_dangerous_path(): """Test dangerous file path patterns.""" with pytest.raises(ValidationError): FileValidator.validate_file_path("../../etc/passwd") def test_file_validator_extension_supported(): """Test supported file extensions.""" assert FileValidator.validate_file_extension("test.csv") == "csv" assert FileValidator.validate_file_extension("test.XLSX") == "xlsx" def test_file_validator_extension_unsupported(): """Test unsupported file extensions.""" with pytest.raises(UnsupportedFileTypeError): FileValidator.validate_file_extension("test.txt") @patch('src.validators.Path.stat') @patch('src.validators.FileValidator.get_max_file_size_mb', return_value=10) def test_file_validator_size(mock_max_size, mock_stat): """Test file size validation.""" mock_stat.return_value.st_size = 5 * 1024 * 1024 # 5MB assert FileValidator.validate_file_size("small.xlsx") > 0 mock_stat.return_value.st_size = 15 * 1024 * 1024 # 15MB with pytest.raises(ValidationError): FileValidator.validate_file_size("large.xlsx") # --- RangeValidator Tests --- def test_range_validator_valid(): """Test valid range strings.""" assert RangeValidator.validate_range_string("A1:B10") == ("A1", "B10") assert RangeValidator.validate_range_string("C1") == ("C1", "C1") def test_range_validator_invalid(): """Test invalid range strings.""" with pytest.raises(ValidationError): RangeValidator.validate_range_string("A1:B") with pytest.raises(ValidationError): RangeValidator.validate_range_string("1A:2B") # --- DataValidator Tests --- def test_data_validator_sheet_name(): """Test sheet name validation.""" assert DataValidator.validate_sheet_name("Valid Name") == "Valid Name" with pytest.raises(ValidationError): DataValidator.validate_sheet_name("Invalid[Name]") with pytest.raises(ValidationError): DataValidator.validate_sheet_name("a" * 32) # --- Combined Validator Test --- @patch('src.validators.FileValidator.validate_file_path') @patch('src.validators.FileValidator.validate_file_extension') @patch('src.validators.FileValidator.validate_file_size') def test_validate_file_input(mock_size, mock_ext, mock_path): """Test the main validate_file_input function.""" mock_path.return_value = Path("valid.xlsx") mock_ext.return_value = "xlsx" mock_size.return_value = 1024 path, ext = validate_file_input("valid.xlsx") assert path == Path("valid.xlsx") assert ext == "xlsx" # --- Additional Tests for Better Coverage --- def test_file_validator_empty_path(): """Test validation with empty file path.""" with pytest.raises(ValidationError, match="文件路径不能为空且必须是字符串"): FileValidator.validate_file_path("") with pytest.raises(ValidationError, match="文件路径不能为空且必须是字符串"): FileValidator.validate_file_path(None) def test_file_validator_non_string_path(): """Test validation with non-string file path.""" with pytest.raises(ValidationError, match="文件路径不能为空且必须是字符串"): FileValidator.validate_file_path(123) def test_file_validator_invalid_path_format(): """Test validation with invalid path format.""" with patch('pathlib.Path.resolve', side_effect=OSError("Invalid path")): with pytest.raises(ValidationError, match="路径格式无效"): FileValidator.validate_file_path("invalid\x00path") @patch('src.validators.Path.exists', return_value=True) @patch('src.validators.Path.is_file', return_value=False) def test_file_validator_path_is_directory(mock_is_file, mock_exists): """Test validation when path points to a directory.""" with patch('src.validators.Path.resolve', return_value=Path("/test/directory")): with pytest.raises(ValidationError, match="路径必须指向一个文件,不能是目录"): FileValidator.validate_file_path("/test/directory") @patch('src.validators.Path.exists', return_value=True) @patch('src.validators.Path.is_file', return_value=True) @patch('os.access', return_value=False) def test_file_validator_no_read_permission(mock_access, mock_is_file, mock_exists): """Test validation when file has no read permission.""" with patch('src.validators.Path.resolve', return_value=Path("/test/noperm.xlsx")): with pytest.raises(FileAccessError): FileValidator.validate_file_path("/test/noperm.xlsx") def test_file_validator_windows_path_traversal(): """Test Windows path traversal detection.""" with pytest.raises(ValidationError, match="检测到危险路径模式"): FileValidator.validate_file_path("..\\..\\windows\\system32\\config") def test_file_validator_unix_system_path(): """Test Unix system path detection.""" with pytest.raises(ValidationError, match="检测到危险路径模式"): FileValidator.validate_file_path("/etc/passwd") def test_file_validator_get_max_file_size(): """Test getting max file size from config.""" with patch('src.unified_config.get_config') as mock_get_config: mock_config = MagicMock() mock_config.max_file_size_mb = 50 mock_get_config.return_value = mock_config max_size = FileValidator.get_max_file_size_mb() assert max_size == 50 def test_range_validator_invalid_format(): """Test range validation with invalid format.""" with pytest.raises(ValidationError, match="范围格式无效"): RangeValidator.validate_range_string("invalid_range") def test_range_validator_invalid_cell_reference(): """Test range validation with valid cell reference.""" result = RangeValidator.validate_range_string("A1:B10") assert result == ("A1", "B10") result = RangeValidator.validate_range_string("A1") assert result == ("A1", "A1") def test_data_validator_invalid_sheet_name(): """Test sheet name validation with invalid characters.""" with pytest.raises(ValidationError, match="工作表名称不能包含字符"): DataValidator.validate_sheet_name("Sheet[invalid]") with pytest.raises(ValidationError, match="工作表名称不能包含字符"): DataValidator.validate_sheet_name("Sheet*name") def test_data_validator_empty_sheet_name(): """Test sheet name validation with empty name.""" with pytest.raises(ValidationError, match="工作表名称不能为空"): DataValidator.validate_sheet_name("") def test_data_validator_whitespace_sheet_name(): """Test sheet name validation with whitespace name.""" # 空白字符串会被strip()处理,如果结果为空则抛出异常 result = DataValidator.validate_sheet_name(" valid ") assert result == "valid" def test_data_validator_long_sheet_name(): """Test sheet name validation with too long name.""" long_name = "a" * 32 # Excel限制是31个字符 with pytest.raises(ValidationError, match="工作表名称不能超过31个字符"): DataValidator.validate_sheet_name(long_name) def test_data_validator_validate_page_size(): """Test page size validation.""" result = DataValidator.validate_page_size(10) assert result == 10 with pytest.raises(ValidationError, match="分页大小必须是整数"): DataValidator.validate_page_size("10") with pytest.raises(ValidationError, match="分页大小必须大于0"): DataValidator.validate_page_size(0) with pytest.raises(ValidationError, match="分页大小不能超过10000"): DataValidator.validate_page_size(10001) def test_range_validator_empty_range(): """Test range validation with empty range.""" result = RangeValidator.validate_range_string("") assert result is None result = RangeValidator.validate_range_string(None) assert result is None def test_range_validator_non_string_range(): """Test range validation with non-string range.""" with pytest.raises(ValidationError, match="范围必须是字符串"): RangeValidator.validate_range_string(123) def test_data_validator_non_string_sheet_name(): """Test sheet name validation with non-string name.""" with pytest.raises(ValidationError, match="工作表名称必须是字符串"): DataValidator.validate_sheet_name(123) def test_data_validator_validate_page_number(): """Test page number validation.""" result = DataValidator.validate_page_number(1) assert result == 1 with pytest.raises(ValidationError, match="页码必须是整数"): DataValidator.validate_page_number("1") with pytest.raises(ValidationError, match="页码必须大于0"): DataValidator.validate_page_number(0) def test_data_validator_validate_output_path(): """Test output path validation.""" with patch('pathlib.Path.resolve') as mock_resolve: with patch('pathlib.Path.parent') as mock_parent: with patch('pathlib.Path.exists', return_value=True): with patch('os.access', return_value=True): mock_path = MagicMock() mock_resolve.return_value = mock_path mock_parent.return_value = mock_path result = DataValidator.validate_output_path("/test/output.txt") assert result == mock_path def test_data_validator_validate_output_path_empty(): """Test output path validation with empty path.""" with pytest.raises(ValidationError, match="输出路径不能为空且必须是字符串"): DataValidator.validate_output_path("") with pytest.raises(ValidationError, match="输出路径不能为空且必须是字符串"): DataValidator.validate_output_path(None) def test_data_validator_validate_output_path_invalid_format(): """Test output path validation with invalid format.""" with patch('pathlib.Path.resolve', side_effect=OSError("Invalid path")): with pytest.raises(ValidationError, match="路径格式无效"): DataValidator.validate_output_path("invalid\x00path") def test_data_validator_validate_output_path_create_dirs(tmp_path): """Test output path validation with directory creation.""" # 使用临时目录进行真实测试 output_file = tmp_path / "new" / "output.txt" result = DataValidator.validate_output_path(str(output_file), create_dirs=True) # 验证目录被创建 assert output_file.parent.exists() assert result == output_file def test_data_validator_validate_output_path_no_create_dirs(): """Test output path validation without directory creation.""" with patch('src.validators.Path') as mock_path_class: mock_path = MagicMock() mock_parent = MagicMock() mock_parent.exists.return_value = False mock_path.parent = mock_parent mock_path_class.return_value.resolve.return_value = mock_path with pytest.raises(ValidationError, match="目录不存在"): DataValidator.validate_output_path("/test/new/output.txt", create_dirs=False) def test_data_validator_validate_output_path_mkdir_error(): """Test output path validation with mkdir error.""" with patch('src.validators.Path') as mock_path_class: mock_path = MagicMock() mock_parent = MagicMock() mock_parent.exists.return_value = False mock_parent.mkdir.side_effect = OSError("Permission denied") mock_path.parent = mock_parent mock_path_class.return_value.resolve.return_value = mock_path with pytest.raises(ValidationError, match="无法创建目录"): DataValidator.validate_output_path("/test/new/output.txt", create_dirs=True) def test_data_validator_validate_output_path_no_write_permission(): """Test output path validation with no write permission.""" with patch('pathlib.Path.resolve') as mock_resolve: with patch('pathlib.Path.parent') as mock_parent: with patch('pathlib.Path.exists', return_value=True): with patch('os.access', return_value=False): mock_path = MagicMock() mock_resolve.return_value = mock_path mock_parent.return_value = mock_path with pytest.raises(FileAccessError): DataValidator.validate_output_path("/test/output.txt")

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