Spaces:
Running
Running
| """Tests for the CLI module.""" | |
| from pathlib import Path | |
| from unittest.mock import MagicMock, patch | |
| import pytest | |
| from typer.testing import CliRunner | |
| import fastmcp.cli.run | |
| from fastmcp.cli import cli | |
| # Set up test runner | |
| runner = CliRunner() | |
| def mock_console(): | |
| """Mock the rich console to test output.""" | |
| with patch("fastmcp.cli.cli.console") as mock_console: | |
| yield mock_console | |
| def mock_logger(): | |
| """Mock the logger to test logging.""" | |
| with patch("fastmcp.cli.cli.logger") as mock_logger: | |
| yield mock_logger | |
| def mock_exit(): | |
| """Mock sys.exit to prevent tests from exiting.""" | |
| with patch("sys.exit") as mock_exit: | |
| yield mock_exit | |
| def temp_python_file(tmp_path): | |
| """Create a temporary Python file with a test server.""" | |
| server_code = """ | |
| from mcp import Server | |
| class TestServer(Server): | |
| name = "test_server" | |
| dependencies = ["package1", "package2"] | |
| def run(self, **kwargs): | |
| print("Running server with", kwargs) | |
| mcp = TestServer() | |
| server = TestServer() | |
| app = TestServer() | |
| custom_server = TestServer() | |
| """ | |
| file_path = tmp_path / "test_server.py" | |
| file_path.write_text(server_code) | |
| return file_path | |
| def temp_env_file(tmp_path): | |
| """Create a temporary .env file.""" | |
| env_content = """ | |
| TEST_VAR1=value1 | |
| TEST_VAR2=value2 | |
| """ | |
| env_path = tmp_path / ".env" | |
| env_path.write_text(env_content) | |
| return env_path | |
| class TestHelperFunctions: | |
| def test_parse_file_path_simple(self): | |
| """Test parsing simple file path.""" | |
| with ( | |
| patch("pathlib.Path.exists") as mock_exists, | |
| patch("pathlib.Path.is_file") as mock_is_file, | |
| patch("pathlib.Path.expanduser") as mock_expanduser, | |
| patch("pathlib.Path.resolve") as mock_resolve, | |
| ): | |
| mock_exists.return_value = True | |
| mock_is_file.return_value = True | |
| mock_expanduser.return_value = Path("file.py") | |
| mock_resolve.return_value = Path("file.py") | |
| path, obj = fastmcp.cli.run.parse_file_path("file.py") | |
| assert path == Path("file.py") | |
| assert obj is None | |
| def test_parse_file_path_with_object(self): | |
| """Test parsing file path with object.""" | |
| with ( | |
| patch("pathlib.Path.exists") as mock_exists, | |
| patch("pathlib.Path.is_file") as mock_is_file, | |
| patch("pathlib.Path.expanduser") as mock_expanduser, | |
| patch("pathlib.Path.resolve") as mock_resolve, | |
| ): | |
| mock_exists.return_value = True | |
| mock_is_file.return_value = True | |
| mock_expanduser.return_value = Path("file.py") | |
| mock_resolve.return_value = Path("file.py") | |
| path, obj = fastmcp.cli.run.parse_file_path("file.py:server") | |
| assert path == Path("file.py") | |
| assert obj == "server" | |
| def test_parse_file_path_windows(self): | |
| """Test parsing Windows file path.""" | |
| with ( | |
| patch("pathlib.Path.exists") as mock_exists, | |
| patch("pathlib.Path.is_file") as mock_is_file, | |
| patch("pathlib.Path.expanduser") as mock_expanduser, | |
| patch("pathlib.Path.resolve") as mock_resolve, | |
| ): | |
| mock_exists.return_value = True | |
| mock_is_file.return_value = True | |
| mock_expanduser.return_value = Path("C:/path/file.py") | |
| mock_resolve.return_value = Path("C:/path/file.py") | |
| path, obj = fastmcp.cli.run.parse_file_path("C:/path/file.py:server") | |
| assert path == Path("C:/path/file.py") | |
| assert obj == "server" | |
| def test_parse_file_path_not_file(self, mock_exit): | |
| """Test parsing path that is not a file.""" | |
| with ( | |
| patch("pathlib.Path.exists") as mock_exists, | |
| patch("pathlib.Path.is_file") as mock_is_file, | |
| patch("pathlib.Path.expanduser") as mock_expanduser, | |
| patch("pathlib.Path.resolve") as mock_resolve, | |
| patch("fastmcp.cli.run.logger") as mock_logger, | |
| ): | |
| mock_exists.return_value = True | |
| mock_is_file.return_value = False | |
| mock_expanduser.return_value = Path("directory") | |
| mock_resolve.return_value = Path("directory") | |
| fastmcp.cli.run.parse_file_path("directory") | |
| mock_logger.error.assert_called_once() | |
| mock_exit.assert_called_once_with(1) | |
| class TestRunCommand: | |
| """Tests for the run command.""" | |
| def test_run_command_success(self, temp_python_file): | |
| """Test successful run command execution.""" | |
| with ( | |
| patch("fastmcp.cli.run.parse_file_path") as mock_parse, | |
| patch("fastmcp.cli.run.import_server") as mock_import, | |
| patch("fastmcp.cli.run.logger") as mock_logger, | |
| ): | |
| mock_parse.return_value = (temp_python_file, None) | |
| mock_server = MagicMock() | |
| mock_server.name = "test_server" | |
| mock_import.return_value = mock_server | |
| result = runner.invoke(cli.app, ["run", str(temp_python_file)]) | |
| assert result.exit_code == 0 | |
| mock_server.run.assert_called_once_with() | |
| mock_logger.debug.assert_called_with( | |
| f'Found server "test_server" in {temp_python_file}' | |
| ) | |
| def test_run_command_with_transport(self, temp_python_file): | |
| """Test run command with transport option.""" | |
| with ( | |
| patch("fastmcp.cli.run.parse_file_path") as mock_parse, | |
| patch("fastmcp.cli.run.import_server") as mock_import, | |
| ): | |
| mock_parse.return_value = (temp_python_file, None) | |
| mock_server = MagicMock() | |
| mock_server.name = "test_server" | |
| mock_import.return_value = mock_server | |
| result = runner.invoke( | |
| cli.app, ["run", str(temp_python_file), "--transport", "sse"] | |
| ) | |
| assert result.exit_code == 0 | |
| mock_server.run.assert_called_once_with(transport="sse") | |
| def test_run_command_with_host(self, temp_python_file): | |
| """Test run command with host option.""" | |
| with ( | |
| patch("fastmcp.cli.run.parse_file_path") as mock_parse, | |
| patch("fastmcp.cli.run.import_server") as mock_import, | |
| ): | |
| mock_parse.return_value = (temp_python_file, None) | |
| mock_server = MagicMock() | |
| mock_server.name = "test_server" | |
| mock_import.return_value = mock_server | |
| result = runner.invoke( | |
| cli.app, ["run", str(temp_python_file), "--host", "0.0.0.0"] | |
| ) | |
| assert result.exit_code == 0 | |
| mock_server.run.assert_called_once_with(host="0.0.0.0") | |
| def test_run_command_with_port(self, temp_python_file): | |
| """Test run command with port option.""" | |
| with ( | |
| patch("fastmcp.cli.run.parse_file_path") as mock_parse, | |
| patch("fastmcp.cli.run.import_server") as mock_import, | |
| ): | |
| mock_parse.return_value = (temp_python_file, None) | |
| mock_server = MagicMock() | |
| mock_server.name = "test_server" | |
| mock_import.return_value = mock_server | |
| result = runner.invoke( | |
| cli.app, ["run", str(temp_python_file), "--port", "8080"] | |
| ) | |
| assert result.exit_code == 0 | |
| mock_server.run.assert_called_once_with(port=8080) | |
| def test_run_command_with_log_level(self, temp_python_file): | |
| """Test run command with log level option.""" | |
| with ( | |
| patch("fastmcp.cli.run.parse_file_path") as mock_parse, | |
| patch("fastmcp.cli.run.import_server") as mock_import, | |
| ): | |
| mock_parse.return_value = (temp_python_file, None) | |
| mock_server = MagicMock() | |
| mock_server.name = "test_server" | |
| mock_import.return_value = mock_server | |
| result = runner.invoke( | |
| cli.app, ["run", str(temp_python_file), "--log-level", "DEBUG"] | |
| ) | |
| assert result.exit_code == 0 | |
| mock_server.run.assert_called_once_with(log_level="DEBUG") | |
| def test_run_command_with_multiple_options(self, temp_python_file): | |
| """Test run command with multiple options.""" | |
| with ( | |
| patch("fastmcp.cli.run.parse_file_path") as mock_parse, | |
| patch("fastmcp.cli.run.import_server") as mock_import, | |
| ): | |
| mock_parse.return_value = (temp_python_file, None) | |
| mock_server = MagicMock() | |
| mock_server.name = "test_server" | |
| mock_import.return_value = mock_server | |
| result = runner.invoke( | |
| cli.app, | |
| [ | |
| "run", | |
| str(temp_python_file), | |
| "--transport", | |
| "sse", | |
| "--host", | |
| "0.0.0.0", | |
| "--port", | |
| "8080", | |
| "--log-level", | |
| "DEBUG", | |
| ], | |
| ) | |
| assert result.exit_code == 0 | |
| mock_server.run.assert_called_once_with( | |
| transport="sse", host="0.0.0.0", port=8080, log_level="DEBUG" | |
| ) | |
| class TestImportServerWithArgs: | |
| """Tests for the import_server_with_args function.""" | |
| def test_import_server_with_args_no_args(self, temp_python_file): | |
| """Test importing server without arguments.""" | |
| with patch("fastmcp.cli.run.import_server") as mock_import: | |
| mock_server = MagicMock() | |
| mock_import.return_value = mock_server | |
| result = fastmcp.cli.run.import_server_with_args( | |
| temp_python_file, None, None | |
| ) | |
| assert result == mock_server | |
| mock_import.assert_called_once_with(temp_python_file, None) | |
| def test_import_server_with_args_with_args(self, temp_python_file): | |
| """Test importing server with arguments.""" | |
| import sys | |
| with patch("fastmcp.cli.run.import_server") as mock_import: | |
| mock_server = MagicMock() | |
| mock_import.return_value = mock_server | |
| original_argv = sys.argv[:] | |
| result = fastmcp.cli.run.import_server_with_args( | |
| temp_python_file, "custom_server", ["--config", "test.json", "--debug"] | |
| ) | |
| assert result == mock_server | |
| mock_import.assert_called_once_with(temp_python_file, "custom_server") | |
| # Verify sys.argv was restored | |
| assert sys.argv == original_argv | |