133 lines
5.4 KiB
Python
133 lines
5.4 KiB
Python
import os
|
|
import unittest
|
|
from unittest.mock import patch, MagicMock
|
|
from datetime import datetime
|
|
|
|
import gitea_mcp_server
|
|
|
|
class TestMcpStaleRuntime(unittest.TestCase):
|
|
@patch("subprocess.run")
|
|
@patch("os.path.getmtime")
|
|
@patch("os.path.exists")
|
|
@patch("os.getpid")
|
|
@patch.dict("os.environ", {"GITEA_FORCE_MCP_RUNTIME_CHECK": "1"})
|
|
def test_stale_and_missing_runtimes(self, mock_getpid, mock_exists, mock_getmtime, mock_run):
|
|
# Setup mocks
|
|
mock_getpid.return_value = 12345
|
|
mock_exists.return_value = True
|
|
|
|
# Code modification time: Jul 8 2026, 14:00:00
|
|
code_time = datetime(2026, 7, 8, 14, 0, 0)
|
|
mock_getmtime.return_value = code_time.timestamp()
|
|
|
|
# Mock ps -ax output
|
|
# PID 12345 is self (started at 13:00:00 - stale)
|
|
# PID 54321 is prgs-author (started at 15:00:00 - fresh)
|
|
# prgs-reviewer is missing
|
|
ps_output = (
|
|
" PID LSTART COMMAND\n"
|
|
"12345 Wed Jul 8 13:00:00 2026 /path/to/python mcp_server.py\n"
|
|
"54321 Wed Jul 8 15:00:00 2026 /path/to/python mcp_server.py\n"
|
|
)
|
|
|
|
mock_run_ps = MagicMock()
|
|
mock_run_ps.stdout = ps_output
|
|
|
|
# Mock env output for ps eww
|
|
mock_run_env12345 = MagicMock()
|
|
mock_run_env12345.stdout = "GITEA_MCP_PROFILE=prgs-reconciler"
|
|
|
|
mock_run_env54321 = MagicMock()
|
|
mock_run_env54321.stdout = "GITEA_MCP_PROFILE=prgs-author"
|
|
|
|
def side_effect(args, **kwargs):
|
|
if args[0] == "ps" and "eww" in args:
|
|
pid = args[2]
|
|
if pid == "12345":
|
|
return mock_run_env12345
|
|
elif pid == "54321":
|
|
return mock_run_env54321
|
|
elif args[0] == "ps":
|
|
return mock_run_ps
|
|
raise ValueError(f"Unexpected subprocess run args: {args}")
|
|
|
|
mock_run.side_effect = side_effect
|
|
|
|
# Test 1: required reviewer role matching prgs-reviewer (which is missing)
|
|
reasons = gitea_mcp_server._check_mcp_runtimes_diagnostics("review_pr", ["prgs-reviewer"])
|
|
|
|
self.assertTrue(any("stale-runtime: The active Gitea MCP server process is stale" in r for r in reasons))
|
|
self.assertTrue(any("stale-runtime: None of the matching profiles for task" in r for r in reasons))
|
|
|
|
# Test 2: required author role matching prgs-author (which is fresh)
|
|
reasons_author = gitea_mcp_server._check_mcp_runtimes_diagnostics("create_issue", ["prgs-author"])
|
|
|
|
# Still contains self stale error
|
|
self.assertTrue(any("stale-runtime: The active Gitea MCP server process is stale" in r for r in reasons_author))
|
|
# But does NOT contain missing author profile error
|
|
self.assertFalse(any("None of the matching profiles for task" in r for r in reasons_author))
|
|
|
|
@patch("subprocess.run")
|
|
@patch("os.path.getmtime")
|
|
@patch("os.path.exists")
|
|
@patch("os.getpid")
|
|
@patch.dict("os.environ", {"GITEA_FORCE_MCP_RUNTIME_CHECK": "1"})
|
|
def test_git_head_change_triggers_staleness(self, mock_getpid, mock_exists, mock_getmtime, mock_run):
|
|
# Set boot SHA to FAKE1 and current to FAKE2
|
|
gitea_mcp_server._process_boot_head_sha = "FAKE1"
|
|
mock_getpid.return_value = 12345
|
|
mock_exists.return_value = True
|
|
|
|
# Code time is in the past (fresh process chronologically)
|
|
code_time = datetime(2026, 7, 8, 11, 0, 0)
|
|
mock_getmtime.return_value = code_time.timestamp()
|
|
|
|
ps_output = (
|
|
" PID LSTART COMMAND\n"
|
|
"12345 Wed Jul 8 12:00:00 2026 /path/to/python mcp_server.py\n"
|
|
)
|
|
|
|
mock_run_ps = MagicMock()
|
|
mock_run_ps.stdout = ps_output
|
|
|
|
mock_run_env = MagicMock()
|
|
mock_run_env.stdout = "GITEA_MCP_PROFILE=prgs-author"
|
|
|
|
mock_run_git = MagicMock()
|
|
mock_run_git.stdout = "FAKE2" # different SHA
|
|
|
|
def side_effect(args, **kwargs):
|
|
if args[0] == "ps" and "eww" in args:
|
|
return mock_run_env
|
|
elif args[0] == "ps":
|
|
return mock_run_ps
|
|
elif args[0] == "git" and "rev-parse" in args:
|
|
return mock_run_git
|
|
raise ValueError(f"Unexpected: {args}")
|
|
|
|
mock_run.side_effect = side_effect
|
|
|
|
# Stale even though process started AFTER code mtime, because git HEAD changed
|
|
reasons = gitea_mcp_server._check_mcp_runtimes_diagnostics("create_issue", ["prgs-author"])
|
|
self.assertTrue(any("stale-runtime: The active Gitea MCP server process is stale" in r for r in reasons))
|
|
|
|
@patch("threading.Thread")
|
|
@patch("os.utime")
|
|
@patch("os.path.exists")
|
|
@patch.dict("os.environ", {"MCP_CONFIG_PATH": "/tmp/mcp_config.json"})
|
|
def test_auto_restart_trigger_touches_and_spawns(self, mock_exists, mock_utime, mock_thread):
|
|
mock_exists.return_value = True
|
|
gitea_mcp_server._restart_triggered = False
|
|
|
|
# Ensure we are not skipped in test mode for testing purposes
|
|
with patch("gitea_mcp_server._preflight_in_test_mode", return_value=False):
|
|
gitea_mcp_server._trigger_mcp_auto_restart()
|
|
|
|
mock_utime.assert_called_once_with("/tmp/mcp_config.json", None)
|
|
mock_thread.assert_called_once()
|
|
self.assertTrue(gitea_mcp_server._restart_triggered)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|