From a4f5b3297221547ece6dbd3fac1c243810a68eca Mon Sep 17 00:00:00 2001 From: Jeremiah Lowin <153965+jlowin@users.noreply.github.com> Date: Sat, 10 May 2025 09:05:53 -0400 Subject: [PATCH] Fix static issues and platform dependencies --- tests/cli/test_cli.py | 298 +++++++++++++++++++++++++----------------- 1 file changed, 177 insertions(+), 121 deletions(-) diff --git a/tests/cli/test_cli.py b/tests/cli/test_cli.py index 3ce4d5af1..30510506f 100644 --- a/tests/cli/test_cli.py +++ b/tests/cli/test_cli.py @@ -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" )