mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-16 02:29:11 +02:00
249 lines
7.5 KiB
Python
249 lines
7.5 KiB
Python
"""Tests for the run_with_uv function and related functionality."""
|
|
|
|
import subprocess
|
|
from pathlib import Path
|
|
from unittest.mock import Mock, patch
|
|
|
|
import pytest
|
|
|
|
from fastmcp.cli.run import run_with_uv
|
|
|
|
|
|
class TestRunWithUv:
|
|
"""Test the run_with_uv function."""
|
|
|
|
@patch("subprocess.run")
|
|
def test_run_with_uv_basic(self, mock_run):
|
|
"""Test basic run_with_uv execution."""
|
|
mock_run.return_value = Mock(returncode=0)
|
|
|
|
with pytest.raises(SystemExit) as exc_info:
|
|
run_with_uv("server.py")
|
|
|
|
assert exc_info.value.code == 0
|
|
|
|
# Check the command that was called
|
|
mock_run.assert_called_once()
|
|
cmd = mock_run.call_args[0][0]
|
|
env = mock_run.call_args.kwargs.get("env", {})
|
|
|
|
# With no environment config, the command should be returned unchanged
|
|
expected = [
|
|
"fastmcp",
|
|
"run",
|
|
"server.py",
|
|
]
|
|
assert cmd == expected
|
|
# Check that the environment marker is set
|
|
assert env.get("FASTMCP_UV_SPAWNED") == "1"
|
|
|
|
@patch("subprocess.run")
|
|
def test_run_with_uv_python_version(self, mock_run):
|
|
"""Test run_with_uv with Python version."""
|
|
mock_run.return_value = Mock(returncode=0)
|
|
|
|
with pytest.raises(SystemExit) as exc_info:
|
|
run_with_uv("server.py", python_version="3.11")
|
|
|
|
assert exc_info.value.code == 0
|
|
|
|
cmd = mock_run.call_args[0][0]
|
|
expected = [
|
|
"uv",
|
|
"run",
|
|
"--python",
|
|
"3.11",
|
|
"fastmcp",
|
|
"run",
|
|
"server.py",
|
|
]
|
|
assert cmd == expected
|
|
|
|
@patch("subprocess.run")
|
|
def test_run_with_uv_project(self, mock_run):
|
|
"""Test run_with_uv with project directory."""
|
|
mock_run.return_value = Mock(returncode=0)
|
|
# Use an absolute path that works on all platforms
|
|
project_path = Path.cwd() / "my" / "project"
|
|
|
|
with pytest.raises(SystemExit) as exc_info:
|
|
run_with_uv("server.py", project=project_path)
|
|
|
|
assert exc_info.value.code == 0
|
|
|
|
cmd = mock_run.call_args[0][0]
|
|
# Check the basic structure
|
|
assert cmd[:3] == ["uv", "run", "--project"]
|
|
# Check that the project path is absolute
|
|
assert Path(cmd[3]).is_absolute()
|
|
# Check the rest of the command
|
|
assert cmd[4:] == [
|
|
"fastmcp",
|
|
"run",
|
|
"server.py",
|
|
]
|
|
|
|
@patch("subprocess.run")
|
|
def test_run_with_uv_with_packages(self, mock_run):
|
|
"""Test run_with_uv with additional packages."""
|
|
mock_run.return_value = Mock(returncode=0)
|
|
|
|
with pytest.raises(SystemExit) as exc_info:
|
|
run_with_uv("server.py", with_packages=["pandas", "numpy"])
|
|
|
|
assert exc_info.value.code == 0
|
|
|
|
cmd = mock_run.call_args[0][0]
|
|
expected = [
|
|
"uv",
|
|
"run",
|
|
"--with",
|
|
"pandas", # original order preserved
|
|
"--with",
|
|
"numpy", # original order preserved
|
|
"fastmcp",
|
|
"run",
|
|
"server.py",
|
|
]
|
|
assert cmd == expected
|
|
|
|
@patch("subprocess.run")
|
|
def test_run_with_uv_with_requirements(self, mock_run):
|
|
"""Test run_with_uv with requirements file."""
|
|
mock_run.return_value = Mock(returncode=0)
|
|
req_path = Path("requirements.txt")
|
|
|
|
with pytest.raises(SystemExit) as exc_info:
|
|
run_with_uv("server.py", with_requirements=req_path)
|
|
|
|
assert exc_info.value.code == 0
|
|
|
|
cmd = mock_run.call_args[0][0]
|
|
expected = [
|
|
"uv",
|
|
"run",
|
|
"--with-requirements",
|
|
str(req_path.resolve()), # auto-resolved to absolute path
|
|
"fastmcp",
|
|
"run",
|
|
"server.py",
|
|
]
|
|
assert cmd == expected
|
|
|
|
@patch("subprocess.run")
|
|
def test_run_with_uv_transport_options(self, mock_run):
|
|
"""Test run_with_uv with transport-related options."""
|
|
mock_run.return_value = Mock(returncode=0)
|
|
|
|
with pytest.raises(SystemExit) as exc_info:
|
|
run_with_uv(
|
|
"server.py",
|
|
transport="http",
|
|
host="localhost",
|
|
port=8080,
|
|
path="/api",
|
|
log_level="DEBUG",
|
|
show_banner=False,
|
|
)
|
|
|
|
assert exc_info.value.code == 0
|
|
|
|
cmd = mock_run.call_args[0][0]
|
|
# With no environment config, no uv run prefix
|
|
expected = [
|
|
"fastmcp",
|
|
"run",
|
|
"server.py",
|
|
"--transport",
|
|
"http",
|
|
"--host",
|
|
"localhost",
|
|
"--port",
|
|
"8080",
|
|
"--path",
|
|
"/api",
|
|
"--log-level",
|
|
"DEBUG",
|
|
"--no-banner",
|
|
]
|
|
assert cmd == expected
|
|
|
|
@patch("subprocess.run")
|
|
def test_run_with_uv_all_options(self, mock_run):
|
|
"""Test run_with_uv with all options combined."""
|
|
mock_run.return_value = Mock(returncode=0)
|
|
|
|
# Use an absolute path that works on all platforms
|
|
project_path = Path.cwd() / "workspace"
|
|
|
|
with pytest.raises(SystemExit) as exc_info:
|
|
run_with_uv(
|
|
"server.py",
|
|
python_version="3.10",
|
|
project=project_path,
|
|
with_packages=["pandas"],
|
|
with_requirements=Path("reqs.txt"),
|
|
transport="http",
|
|
port=9000,
|
|
show_banner=False,
|
|
)
|
|
|
|
assert exc_info.value.code == 0
|
|
|
|
cmd = mock_run.call_args[0][0]
|
|
|
|
# When project is specified, Python version is not included
|
|
# Build expected command step by step
|
|
expected_start = ["uv", "run", "--project"]
|
|
|
|
# Check start and that project path is absolute
|
|
assert cmd[:3] == expected_start
|
|
assert Path(cmd[3]).is_absolute()
|
|
|
|
# Find the index where packages and requirements start
|
|
next_idx = 4
|
|
assert cmd[next_idx : next_idx + 2] == ["--with", "pandas"]
|
|
next_idx += 2
|
|
assert cmd[next_idx : next_idx + 1] == ["--with-requirements"]
|
|
next_idx += 1
|
|
# Check requirements path is absolute
|
|
assert Path(cmd[next_idx]).is_absolute()
|
|
assert Path(cmd[next_idx]).name == "reqs.txt"
|
|
next_idx += 1
|
|
|
|
# Rest should be the fastmcp command with options
|
|
assert cmd[next_idx:] == [
|
|
"fastmcp",
|
|
"run",
|
|
"server.py",
|
|
"--transport",
|
|
"http",
|
|
"--port",
|
|
"9000",
|
|
"--no-banner",
|
|
]
|
|
|
|
@patch("subprocess.run")
|
|
def test_run_with_uv_error_handling(self, mock_run):
|
|
"""Test run_with_uv error handling."""
|
|
mock_run.side_effect = subprocess.CalledProcessError(1, ["uv", "run"])
|
|
|
|
with pytest.raises(SystemExit) as exc_info:
|
|
run_with_uv("server.py")
|
|
|
|
assert exc_info.value.code == 1
|
|
|
|
@patch("fastmcp.cli.run.logger")
|
|
@patch("subprocess.run")
|
|
def test_run_with_uv_logging(self, mock_run, mock_logger):
|
|
"""Test that run_with_uv logs the command."""
|
|
mock_run.return_value = Mock(returncode=0)
|
|
|
|
with pytest.raises(SystemExit):
|
|
run_with_uv("server.py", python_version="3.11")
|
|
|
|
# Check that debug logging was called with the command
|
|
mock_logger.debug.assert_called()
|
|
call_args = mock_logger.debug.call_args[0][0]
|
|
assert "Running command:" in call_args
|
|
assert "uv run --python 3.11" in call_args
|