From 8e1df909e20d5201218f810ad69d23fb1de381b4 Mon Sep 17 00:00:00 2001 From: Tony Date: Sat, 9 May 2026 14:05:52 +0800 Subject: [PATCH] Fix sort_by dict access, fix test assertions for call_tool tuple return --- .../__pycache__/server.cpython-311.pyc | Bin 0 -> 14718 bytes src/procleaning_mcp/server.py | 6 +- tests/test_server.py | 90 ++++++++++++++++++ 3 files changed, 93 insertions(+), 3 deletions(-) create mode 100644 src/procleaning_mcp/__pycache__/server.cpython-311.pyc create mode 100644 tests/test_server.py diff --git a/src/procleaning_mcp/__pycache__/server.cpython-311.pyc b/src/procleaning_mcp/__pycache__/server.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7a192d4e5f37fafe1c26e8c2216700663a678bdd GIT binary patch literal 14718 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 literal 0 HcmV?d00001 diff --git a/src/procleaning_mcp/server.py b/src/procleaning_mcp/server.py index d08558f..2954849 100644 --- a/src/procleaning_mcp/server.py +++ b/src/procleaning_mcp/server.py @@ -158,9 +158,9 @@ def system_resource_usage(top_n: int = 10, sort_by: str = "cpu") -> str: threads = psutil.cpu_count(logical=True) or 1 key_map = { - "cpu": lambda p: p.cpu_percent(), - "memory": lambda p: p.memory_percent(), - "rss": lambda p: p.memory_info().rss, + "cpu": lambda p: p["cpu_percent"], + "memory": lambda p: p["memory_percent"], + "rss": lambda p: p["memory_rss_mb"], } sort_key = key_map.get(sort_by, key_map["cpu"]) diff --git a/tests/test_server.py b/tests/test_server.py new file mode 100644 index 0000000..34982de --- /dev/null +++ b/tests/test_server.py @@ -0,0 +1,90 @@ +"""Tests for procleaning-mcp server.""" + +import sys +import asyncio +import os + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src")) + +import pytest +from procleaning_mcp.server import mcp + + +@pytest.fixture +def event_loop(): + """Create a test event loop.""" + loop = asyncio.new_event_loop() + yield loop + loop.close() + + +@pytest.mark.asyncio +async def test_list_processes(): + """Test that list_processes returns valid output.""" + result = await mcp.call_tool( + "list_processes", {"name_filter": "python", "max_processes": 5} + ) + # call_tool returns (content_list, structured_content) + content_list, structured = result + assert isinstance(content_list, list) + assert content_list[0].type == "text" + text = content_list[0].text + assert "Found" in text or "No processes" in text + + +@pytest.mark.asyncio +async def test_system_resource_usage(): + """Test that system_resource_usage returns valid output.""" + result = await mcp.call_tool( + "system_resource_usage", {"top_n": 3, "sort_by": "memory"} + ) + content_list, structured = result + assert isinstance(content_list, list) + assert content_list[0].type == "text" + text = content_list[0].text + assert "CPU Usage" in text + assert "Memory Usage" in text + assert "Top 3" in text + + +@pytest.mark.asyncio +async def test_find_zombies(): + """Test that find_zombies returns valid output.""" + result = await mcp.call_tool("find_zombies", {"include_details": False}) + content_list, structured = result + assert isinstance(content_list, list) + assert content_list[0].type == "text" + + +@pytest.mark.asyncio +async def test_list_processes_empty_filter(): + """Test list_processes with no filter returns processes.""" + result = await mcp.call_tool( + "list_processes", {"max_processes": 3} + ) + content_list, structured = result + assert isinstance(content_list, list) + text = content_list[0].text + assert "Found" in text + + +@pytest.mark.asyncio +async def test_kill_process_no_confirm(): + """Test that kill_process without confirm returns cancellation message.""" + result = await mcp.call_tool( + "kill_process", {"pid": 1, "confirm": False} + ) + content_list, structured = result + text = content_list[0].text + assert "cancelled" in text.lower() + + +@pytest.mark.asyncio +async def test_kill_by_name_no_match(): + """Test kill_by_name with non-existent process name.""" + result = await mcp.call_tool( + "kill_by_name", {"name_pattern": "thisprocessdoesnotexist12345", "confirm": False} + ) + content_list, structured = result + text = content_list[0].text + assert "cancelled" in text.lower()