# coding=utf-8 """ @Time :2026/6/30 @Author :tao.chen """ import sys import types import pytest from fastapi import FastAPI from common import mcp_service from common.mcp_service import McpService, _filter_by_env, discover_and_filter # --- helpers ---------------------------------------------------------------- def _fake_app(title: str = "Fake") -> FastAPI: return FastAPI(title=title) def _make_module(monkeypatch, name: str, *, service: object = None) -> None: """Inject a fake module into sys.modules with the given SERVICE attribute. For "broken import" tests, the test author should set BUILTIN_SERVICES to a name that is NOT injected into sys.modules, simulating ImportError. """ mod = types.ModuleType(name) if service is not None: mod.SERVICE = service monkeypatch.setitem(sys.modules, name, mod) @pytest.fixture def reset_builtin(monkeypatch): """Restore BUILTIN_SERVICES to a known good state after each test.""" monkeypatch.setattr( mcp_service, "BUILTIN_SERVICES", ["fake_spark_executor", "fake_files"], ) @pytest.fixture def reset_env(monkeypatch): """Make sure MCP_SERVICES is unset at the start of each test.""" monkeypatch.delenv("MCP_SERVICES", raising=False) # --- TestDiscover ---------------------------------------------------------- class TestDiscover: def test_loads_builtin_services_in_order( self, monkeypatch, reset_builtin, reset_env ): a = McpService(name="fake_spark_executor", app=_fake_app(), mount_path="/a-mcp") b = McpService(name="fake_files", app=_fake_app(), mount_path="/b-mcp") _make_module(monkeypatch, "fake_spark_executor", service=a) _make_module(monkeypatch, "fake_files", service=b) result = discover_and_filter() assert [s.name for s in result] == ["fake_spark_executor", "fake_files"] def test_skips_broken_service_if_not_requested( self, monkeypatch, reset_builtin, reset_env, caplog ): # "fake_spark_executor" is not importable; env doesn't mention it. # Only "fake_files" is real. b = McpService(name="fake_files", app=_fake_app(), mount_path="/b-mcp") _make_module(monkeypatch, "fake_files", service=b) with caplog.at_level("WARNING", logger="common.logging"): result = discover_and_filter() assert [s.name for s in result] == ["fake_files"] assert any("fake_spark_executor" in r.message for r in caplog.records) def test_raises_on_broken_service_if_requested( self, monkeypatch, reset_builtin, reset_env ): # "fake_spark_executor" is not importable AND operator asked for it. monkeypatch.setenv("MCP_SERVICES", "fake_spark_executor") b = McpService(name="fake_files", app=_fake_app(), mount_path="/b-mcp") _make_module(monkeypatch, "fake_files", service=b) with pytest.raises(RuntimeError, match="requested via MCP_SERVICES"): discover_and_filter() def test_raises_when_service_module_lacks_SERVICE( self, monkeypatch, reset_builtin, reset_env ): # Module exists but has no SERVICE attribute. _make_module(monkeypatch, "fake_spark_executor", service=None) b = McpService(name="fake_files", app=_fake_app(), mount_path="/b-mcp") _make_module(monkeypatch, "fake_files", service=b) with pytest.raises(RuntimeError, match="does not export a SERVICE"): discover_and_filter() def test_raises_when_SERVICE_is_wrong_type( self, monkeypatch, reset_builtin, reset_env ): # SERVICE attribute exists but is not a McpService. _make_module(monkeypatch, "fake_spark_executor", service="not a McpService") b = McpService(name="fake_files", app=_fake_app(), mount_path="/b-mcp") _make_module(monkeypatch, "fake_files", service=b) with pytest.raises(RuntimeError, match="is not a McpService"): discover_and_filter() def test_raises_on_duplicate_service_names( self, monkeypatch, reset_builtin, reset_env ): a = McpService(name="dup", app=_fake_app(), mount_path="/a-mcp") b = McpService(name="dup", app=_fake_app(), mount_path="/b-mcp") _make_module(monkeypatch, "fake_spark_executor", service=a) _make_module(monkeypatch, "fake_files", service=b) with pytest.raises(RuntimeError, match="duplicate MCP service name"): discover_and_filter() def test_raises_on_duplicate_mount_paths( self, monkeypatch, reset_builtin, reset_env ): a = McpService(name="alpha", app=_fake_app(), mount_path="/same") b = McpService(name="beta", app=_fake_app(), mount_path="/same") _make_module(monkeypatch, "fake_spark_executor", service=a) _make_module(monkeypatch, "fake_files", service=b) with pytest.raises(RuntimeError, match="duplicate MCP mount_path"): discover_and_filter() # --- TestFilter (unit-tests the private _filter_by_env directly) ---------- class TestFilter: def test_unset_env_returns_all(self, monkeypatch): monkeypatch.delenv("MCP_SERVICES", raising=False) svcs = [ McpService(name="a", app=_fake_app(), mount_path="/a"), McpService(name="b", app=_fake_app(), mount_path="/b"), ] assert _filter_by_env(svcs) == svcs def test_empty_env_returns_zero(self, monkeypatch): monkeypatch.setenv("MCP_SERVICES", "") svcs = [ McpService(name="a", app=_fake_app(), mount_path="/a"), McpService(name="b", app=_fake_app(), mount_path="/b"), ] assert _filter_by_env(svcs) == [] def test_whitelist_filters_by_name(self, monkeypatch): monkeypatch.setenv("MCP_SERVICES", "a") svcs = [ McpService(name="a", app=_fake_app(), mount_path="/a"), McpService(name="b", app=_fake_app(), mount_path="/b"), ] result = _filter_by_env(svcs) assert [s.name for s in result] == ["a"] def test_whitelist_preserves_bulitin_order(self, monkeypatch): # env order: "b,a"; result order must follow svcs order, not env order. monkeypatch.setenv("MCP_SERVICES", "b,a") svcs = [ McpService(name="a", app=_fake_app(), mount_path="/a"), McpService(name="b", app=_fake_app(), mount_path="/b"), ] result = _filter_by_env(svcs) assert [s.name for s in result] == ["a", "b"] # --- TestEnvErrors --------------------------------------------------------- class TestEnvErrors: def test_unknown_name_in_env_raises_with_available_list( self, monkeypatch, reset_builtin, reset_env ): a = McpService(name="fake_spark_executor", app=_fake_app(), mount_path="/a-mcp") b = McpService(name="fake_files", app=_fake_app(), mount_path="/b-mcp") _make_module(monkeypatch, "fake_spark_executor", service=a) _make_module(monkeypatch, "fake_files", service=b) monkeypatch.setenv("MCP_SERVICES", "bogus") with pytest.raises(RuntimeError) as exc_info: discover_and_filter() msg = str(exc_info.value) assert "bogus" in msg assert "fake_files" in msg assert "fake_spark_executor" in msg