mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-24 06:24:18 +02:00
Fix static issues and platform dependencies
This commit is contained in:
parent
68f4887d13
commit
a4f5b32972
1 changed files with 177 additions and 121 deletions
|
|
@ -1,6 +1,5 @@
|
|||
"""Tests for the CLI module."""
|
||||
|
||||
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from unittest.mock import MagicMock, Mock, patch
|
||||
|
|
@ -10,7 +9,6 @@ from typer.testing import CliRunner
|
|||
|
||||
from fastmcp.cli import cli
|
||||
|
||||
|
||||
# Set up test runner
|
||||
runner = CliRunner()
|
||||
|
||||
|
|
@ -107,7 +105,10 @@ class TestHelperFunctions:
|
|||
"""Test parsing valid environment variables."""
|
||||
assert cli._parse_env_var("KEY=VALUE") == ("KEY", "VALUE")
|
||||
assert cli._parse_env_var("KEY=") == ("KEY", "")
|
||||
assert cli._parse_env_var("KEY=VALUE=WITH=EQUALS") == ("KEY", "VALUE=WITH=EQUALS")
|
||||
assert cli._parse_env_var("KEY=VALUE=WITH=EQUALS") == (
|
||||
"KEY",
|
||||
"VALUE=WITH=EQUALS",
|
||||
)
|
||||
assert cli._parse_env_var(" KEY = VALUE ") == ("KEY", "VALUE")
|
||||
|
||||
def test_build_uv_command_basic(self):
|
||||
|
|
@ -117,96 +118,125 @@ class TestHelperFunctions:
|
|||
|
||||
def test_build_uv_command_with_editable(self):
|
||||
"""Test building uv command with editable flag."""
|
||||
cmd = cli._build_uv_command("file.py", with_editable=Path("/path/to/project"))
|
||||
project_path = Path("/path/to/project")
|
||||
cmd = cli._build_uv_command("file.py", with_editable=project_path)
|
||||
assert cmd == [
|
||||
"uv", "run", "--with", "fastmcp",
|
||||
"--with-editable", "/path/to/project",
|
||||
"fastmcp", "run", "file.py"
|
||||
"uv",
|
||||
"run",
|
||||
"--with",
|
||||
"fastmcp",
|
||||
"--with-editable",
|
||||
str(project_path),
|
||||
"fastmcp",
|
||||
"run",
|
||||
"file.py",
|
||||
]
|
||||
|
||||
def test_build_uv_command_with_packages(self):
|
||||
"""Test building uv command with additional packages."""
|
||||
cmd = cli._build_uv_command("file.py", with_packages=["pkg1", "pkg2"])
|
||||
assert cmd == [
|
||||
"uv", "run", "--with", "fastmcp",
|
||||
"--with", "pkg1", "--with", "pkg2",
|
||||
"fastmcp", "run", "file.py"
|
||||
"uv",
|
||||
"run",
|
||||
"--with",
|
||||
"fastmcp",
|
||||
"--with",
|
||||
"pkg1",
|
||||
"--with",
|
||||
"pkg2",
|
||||
"fastmcp",
|
||||
"run",
|
||||
"file.py",
|
||||
]
|
||||
|
||||
def test_build_uv_command_full(self):
|
||||
"""Test building full uv command with all options."""
|
||||
project_path = Path("/path/to/project")
|
||||
cmd = cli._build_uv_command(
|
||||
"file.py:server",
|
||||
with_editable=Path("/path/to/project"),
|
||||
"file.py:server",
|
||||
with_editable=project_path,
|
||||
with_packages=["pkg1", "pkg2"],
|
||||
)
|
||||
assert cmd == [
|
||||
"uv", "run", "--with", "fastmcp",
|
||||
"--with-editable", "/path/to/project",
|
||||
"--with", "pkg1", "--with", "pkg2",
|
||||
"fastmcp", "run", "file.py:server"
|
||||
"uv",
|
||||
"run",
|
||||
"--with",
|
||||
"fastmcp",
|
||||
"--with-editable",
|
||||
str(project_path),
|
||||
"--with",
|
||||
"pkg1",
|
||||
"--with",
|
||||
"pkg2",
|
||||
"fastmcp",
|
||||
"run",
|
||||
"file.py:server",
|
||||
]
|
||||
|
||||
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:
|
||||
|
||||
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 = cli._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:
|
||||
|
||||
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 = cli._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:
|
||||
|
||||
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 = cli._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, mock_logger):
|
||||
"""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:
|
||||
|
||||
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 = False
|
||||
mock_expanduser.return_value = Path("directory")
|
||||
mock_resolve.return_value = Path("directory")
|
||||
|
||||
|
||||
cli._parse_file_path("directory")
|
||||
mock_logger.error.assert_called_once()
|
||||
mock_exit.assert_called_once_with(1)
|
||||
|
|
@ -228,12 +258,13 @@ class TestDevCommand:
|
|||
|
||||
def test_dev_command_success(self, temp_python_file, mock_logger):
|
||||
"""Test successful dev command execution."""
|
||||
with patch("fastmcp.cli.cli._parse_file_path") as mock_parse, \
|
||||
patch("fastmcp.cli.cli._import_server") as mock_import, \
|
||||
patch("fastmcp.cli.cli._get_npx_command") as mock_get_npx, \
|
||||
patch("fastmcp.cli.cli._build_uv_command") as mock_build_uv, \
|
||||
patch("subprocess.run") as mock_run:
|
||||
|
||||
with (
|
||||
patch("fastmcp.cli.cli._parse_file_path") as mock_parse,
|
||||
patch("fastmcp.cli.cli._import_server") as mock_import,
|
||||
patch("fastmcp.cli.cli._get_npx_command") as mock_get_npx,
|
||||
patch("fastmcp.cli.cli._build_uv_command") as mock_build_uv,
|
||||
patch("subprocess.run") as mock_run,
|
||||
):
|
||||
mock_parse.return_value = (temp_python_file, None)
|
||||
mock_server = MagicMock()
|
||||
mock_server.dependencies = ["extra_dep"]
|
||||
|
|
@ -241,35 +272,36 @@ class TestDevCommand:
|
|||
mock_get_npx.return_value = "npx"
|
||||
mock_build_uv.return_value = ["uv", "command"]
|
||||
mock_run.return_value = MagicMock(returncode=0)
|
||||
|
||||
|
||||
result = runner.invoke(cli.app, ["dev", str(temp_python_file)])
|
||||
assert result.exit_code == 0
|
||||
mock_run.assert_called_once()
|
||||
|
||||
|
||||
# Check dependencies were passed correctly
|
||||
mock_build_uv.assert_called_once_with(
|
||||
str(temp_python_file),
|
||||
None,
|
||||
["extra_dep"]
|
||||
str(temp_python_file), None, ["extra_dep"]
|
||||
)
|
||||
|
||||
def test_dev_command_with_ui_port(self, temp_python_file):
|
||||
"""Test dev command with UI port."""
|
||||
with patch("fastmcp.cli.cli._parse_file_path") as mock_parse, \
|
||||
patch("fastmcp.cli.cli._import_server") as mock_import, \
|
||||
patch("fastmcp.cli.cli._get_npx_command") as mock_get_npx, \
|
||||
patch("fastmcp.cli.cli._build_uv_command") as mock_build_uv, \
|
||||
patch("subprocess.run") as mock_run:
|
||||
|
||||
with (
|
||||
patch("fastmcp.cli.cli._parse_file_path") as mock_parse,
|
||||
patch("fastmcp.cli.cli._import_server") as mock_import,
|
||||
patch("fastmcp.cli.cli._get_npx_command") as mock_get_npx,
|
||||
patch("fastmcp.cli.cli._build_uv_command") as mock_build_uv,
|
||||
patch("subprocess.run") as mock_run,
|
||||
):
|
||||
mock_parse.return_value = (temp_python_file, None)
|
||||
mock_import.return_value = MagicMock(dependencies=[])
|
||||
mock_get_npx.return_value = "npx"
|
||||
mock_build_uv.return_value = ["uv", "command"]
|
||||
mock_run.return_value = MagicMock(returncode=0)
|
||||
|
||||
result = runner.invoke(cli.app, ["dev", str(temp_python_file), "--ui-port", "3000"])
|
||||
|
||||
result = runner.invoke(
|
||||
cli.app, ["dev", str(temp_python_file), "--ui-port", "3000"]
|
||||
)
|
||||
assert result.exit_code == 0
|
||||
|
||||
|
||||
# Check environment variables were set
|
||||
env = mock_run.call_args[1]["env"]
|
||||
assert "CLIENT_PORT" in env
|
||||
|
|
@ -277,21 +309,24 @@ class TestDevCommand:
|
|||
|
||||
def test_dev_command_with_server_port(self, temp_python_file):
|
||||
"""Test dev command with server port."""
|
||||
with patch("fastmcp.cli.cli._parse_file_path") as mock_parse, \
|
||||
patch("fastmcp.cli.cli._import_server") as mock_import, \
|
||||
patch("fastmcp.cli.cli._get_npx_command") as mock_get_npx, \
|
||||
patch("fastmcp.cli.cli._build_uv_command") as mock_build_uv, \
|
||||
patch("subprocess.run") as mock_run:
|
||||
|
||||
with (
|
||||
patch("fastmcp.cli.cli._parse_file_path") as mock_parse,
|
||||
patch("fastmcp.cli.cli._import_server") as mock_import,
|
||||
patch("fastmcp.cli.cli._get_npx_command") as mock_get_npx,
|
||||
patch("fastmcp.cli.cli._build_uv_command") as mock_build_uv,
|
||||
patch("subprocess.run") as mock_run,
|
||||
):
|
||||
mock_parse.return_value = (temp_python_file, None)
|
||||
mock_import.return_value = MagicMock(dependencies=[])
|
||||
mock_get_npx.return_value = "npx"
|
||||
mock_build_uv.return_value = ["uv", "command"]
|
||||
mock_run.return_value = MagicMock(returncode=0)
|
||||
|
||||
result = runner.invoke(cli.app, ["dev", str(temp_python_file), "--server-port", "8080"])
|
||||
|
||||
result = runner.invoke(
|
||||
cli.app, ["dev", str(temp_python_file), "--server-port", "8080"]
|
||||
)
|
||||
assert result.exit_code == 0
|
||||
|
||||
|
||||
# Check environment variables were set
|
||||
env = mock_run.call_args[1]["env"]
|
||||
assert "SERVER_PORT" in env
|
||||
|
|
@ -299,24 +334,24 @@ class TestDevCommand:
|
|||
|
||||
def test_dev_command_inspector_version(self, temp_python_file):
|
||||
"""Test dev command with specific inspector version."""
|
||||
with patch("fastmcp.cli.cli._parse_file_path") as mock_parse, \
|
||||
patch("fastmcp.cli.cli._import_server") as mock_import, \
|
||||
patch("fastmcp.cli.cli._get_npx_command") as mock_get_npx, \
|
||||
patch("fastmcp.cli.cli._build_uv_command") as mock_build_uv, \
|
||||
patch("subprocess.run") as mock_run:
|
||||
|
||||
with (
|
||||
patch("fastmcp.cli.cli._parse_file_path") as mock_parse,
|
||||
patch("fastmcp.cli.cli._import_server") as mock_import,
|
||||
patch("fastmcp.cli.cli._get_npx_command") as mock_get_npx,
|
||||
patch("fastmcp.cli.cli._build_uv_command") as mock_build_uv,
|
||||
patch("subprocess.run") as mock_run,
|
||||
):
|
||||
mock_parse.return_value = (temp_python_file, None)
|
||||
mock_import.return_value = MagicMock(dependencies=[])
|
||||
mock_get_npx.return_value = "npx"
|
||||
mock_build_uv.return_value = ["uv", "command"]
|
||||
mock_run.return_value = MagicMock(returncode=0)
|
||||
|
||||
result = runner.invoke(cli.app, [
|
||||
"dev", str(temp_python_file),
|
||||
"--inspector-version", "1.0.0"
|
||||
])
|
||||
|
||||
result = runner.invoke(
|
||||
cli.app, ["dev", str(temp_python_file), "--inspector-version", "1.0.0"]
|
||||
)
|
||||
assert result.exit_code == 0
|
||||
|
||||
|
||||
# Check inspector version was used
|
||||
inspector_cmd = mock_run.call_args[0][0][1]
|
||||
assert inspector_cmd == "@modelcontextprotocol/inspector@1.0.0"
|
||||
|
|
@ -327,96 +362,117 @@ class TestRunCommand:
|
|||
|
||||
def test_run_command_success(self, temp_python_file, mock_logger):
|
||||
"""Test successful run command execution."""
|
||||
with patch("fastmcp.cli.cli._parse_file_path") as mock_parse, \
|
||||
patch("fastmcp.cli.cli._import_server") as mock_import:
|
||||
|
||||
with (
|
||||
patch("fastmcp.cli.cli._parse_file_path") as mock_parse,
|
||||
patch("fastmcp.cli.cli._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)])
|
||||
assert result.exit_code == 0
|
||||
mock_server.run.assert_called_once_with()
|
||||
mock_logger.info.assert_called_with(f'Found server "test_server" in {temp_python_file}')
|
||||
mock_logger.info.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.cli._parse_file_path") as mock_parse, \
|
||||
patch("fastmcp.cli.cli._import_server") as mock_import:
|
||||
|
||||
with (
|
||||
patch("fastmcp.cli.cli._parse_file_path") as mock_parse,
|
||||
patch("fastmcp.cli.cli._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"])
|
||||
|
||||
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.cli._parse_file_path") as mock_parse, \
|
||||
patch("fastmcp.cli.cli._import_server") as mock_import:
|
||||
|
||||
with (
|
||||
patch("fastmcp.cli.cli._parse_file_path") as mock_parse,
|
||||
patch("fastmcp.cli.cli._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"])
|
||||
|
||||
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.cli._parse_file_path") as mock_parse, \
|
||||
patch("fastmcp.cli.cli._import_server") as mock_import:
|
||||
|
||||
with (
|
||||
patch("fastmcp.cli.cli._parse_file_path") as mock_parse,
|
||||
patch("fastmcp.cli.cli._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"])
|
||||
|
||||
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.cli._parse_file_path") as mock_parse, \
|
||||
patch("fastmcp.cli.cli._import_server") as mock_import:
|
||||
|
||||
with (
|
||||
patch("fastmcp.cli.cli._parse_file_path") as mock_parse,
|
||||
patch("fastmcp.cli.cli._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"])
|
||||
|
||||
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.cli._parse_file_path") as mock_parse, \
|
||||
patch("fastmcp.cli.cli._import_server") as mock_import:
|
||||
|
||||
with (
|
||||
patch("fastmcp.cli.cli._parse_file_path") as mock_parse,
|
||||
patch("fastmcp.cli.cli._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"
|
||||
])
|
||||
|
||||
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"
|
||||
transport="sse", host="0.0.0.0", port=8080, log_level="DEBUG"
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue