mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-09 15:19:10 +02:00
262 lines
9.1 KiB
Python
262 lines
9.1 KiB
Python
"""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()
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_console():
|
|
"""Mock the rich console to test output."""
|
|
with patch("fastmcp.cli.cli.console") as mock_console:
|
|
yield mock_console
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_logger():
|
|
"""Mock the logger to test logging."""
|
|
with patch("fastmcp.cli.cli.logger") as mock_logger:
|
|
yield mock_logger
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_exit():
|
|
"""Mock sys.exit to prevent tests from exiting."""
|
|
with patch("sys.exit") as mock_exit:
|
|
yield mock_exit
|
|
|
|
|
|
@pytest.fixture
|
|
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
|
|
|
|
|
|
@pytest.fixture
|
|
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"
|
|
)
|