mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-08-28 05:25:25 +00:00
* fix: start MCP bridge correctly when using npx * test: add MCPBridge command discovery and spawn tests; fix stdin isolation --------- Co-authored-by: genoshide <genoshide@users.noreply.github.com>
170 lines
6.4 KiB
Python
170 lines
6.4 KiB
Python
"""Tests for MCPBridge._find_gitnexus_command() and subprocess spawn."""
|
|
import subprocess
|
|
import unittest
|
|
from unittest.mock import MagicMock, call, patch
|
|
|
|
|
|
class TestFindGitnexusCommand(unittest.TestCase):
|
|
"""Verify _find_gitnexus_command() returns the correct command prefix."""
|
|
|
|
def _make_bridge(self):
|
|
from bridge.mcp_bridge import MCPBridge
|
|
return MCPBridge(repo_path="/fake/repo")
|
|
|
|
def _success(self):
|
|
r = MagicMock()
|
|
r.returncode = 0
|
|
return r
|
|
|
|
def _failure(self):
|
|
r = MagicMock()
|
|
r.returncode = 1
|
|
return r
|
|
|
|
def test_npx_path_returns_npx_gitnexus(self):
|
|
"""When npx probe succeeds, command prefix is ['npx', 'gitnexus']."""
|
|
with patch("subprocess.run", return_value=self._success()) as mock_run:
|
|
bridge = self._make_bridge()
|
|
result = bridge._find_gitnexus_command()
|
|
|
|
self.assertEqual(result, ["npx", "gitnexus"])
|
|
mock_run.assert_called_once()
|
|
args = mock_run.call_args[0][0]
|
|
self.assertEqual(args, ["npx", "gitnexus", "--version"])
|
|
|
|
def test_global_path_returns_gitnexus(self):
|
|
"""When npx probe fails but global install exists, prefix is ['gitnexus']."""
|
|
with patch("subprocess.run", side_effect=[self._failure(), self._success()]) as mock_run:
|
|
bridge = self._make_bridge()
|
|
result = bridge._find_gitnexus_command()
|
|
|
|
self.assertEqual(result, ["gitnexus"])
|
|
self.assertEqual(mock_run.call_count, 2)
|
|
|
|
def test_both_fail_returns_none(self):
|
|
"""When both probes fail, returns None."""
|
|
with patch("subprocess.run", return_value=self._failure()):
|
|
bridge = self._make_bridge()
|
|
result = bridge._find_gitnexus_command()
|
|
|
|
self.assertIsNone(result)
|
|
|
|
def test_npx_exception_falls_back_to_global(self):
|
|
"""When npx raises (not installed), falls back to global probe."""
|
|
with patch("subprocess.run", side_effect=[FileNotFoundError, self._success()]):
|
|
bridge = self._make_bridge()
|
|
result = bridge._find_gitnexus_command()
|
|
|
|
self.assertEqual(result, ["gitnexus"])
|
|
|
|
def test_both_raise_returns_none(self):
|
|
"""When both probes raise exceptions, returns None."""
|
|
with patch("subprocess.run", side_effect=FileNotFoundError):
|
|
bridge = self._make_bridge()
|
|
result = bridge._find_gitnexus_command()
|
|
|
|
self.assertIsNone(result)
|
|
|
|
def test_stdin_devnull_on_npx_probe(self):
|
|
"""npx probe must pass stdin=DEVNULL to prevent interactive blocking."""
|
|
with patch("subprocess.run", return_value=self._success()) as mock_run:
|
|
bridge = self._make_bridge()
|
|
bridge._find_gitnexus_command()
|
|
|
|
kwargs = mock_run.call_args[1]
|
|
self.assertEqual(kwargs.get("stdin"), subprocess.DEVNULL)
|
|
|
|
def test_stdin_devnull_on_global_probe(self):
|
|
"""global probe must pass stdin=DEVNULL to prevent interactive blocking."""
|
|
with patch("subprocess.run", side_effect=[self._failure(), self._success()]) as mock_run:
|
|
bridge = self._make_bridge()
|
|
bridge._find_gitnexus_command()
|
|
|
|
global_call_kwargs = mock_run.call_args_list[1][1]
|
|
self.assertEqual(global_call_kwargs.get("stdin"), subprocess.DEVNULL)
|
|
|
|
|
|
class TestStartSpawnCommand(unittest.TestCase):
|
|
"""Verify start() spawns Popen with the correct argv."""
|
|
|
|
def _make_bridge(self):
|
|
from bridge.mcp_bridge import MCPBridge
|
|
return MCPBridge(repo_path="/fake/repo")
|
|
|
|
def test_npx_path_spawns_npx_gitnexus_mcp(self):
|
|
"""When npx path found, Popen must receive ['npx', 'gitnexus', 'mcp']."""
|
|
bridge = self._make_bridge()
|
|
|
|
with patch.object(bridge, "_find_gitnexus_command", return_value=["npx", "gitnexus"]), \
|
|
patch("subprocess.Popen") as mock_popen, \
|
|
patch.object(bridge, "_send_request", return_value={"protocolVersion": "2024-11-05"}), \
|
|
patch.object(bridge, "_send_notification"):
|
|
|
|
mock_proc = MagicMock()
|
|
mock_proc.stdin = MagicMock()
|
|
mock_proc.stdout = MagicMock()
|
|
mock_proc.stderr = MagicMock()
|
|
mock_popen.return_value = mock_proc
|
|
|
|
bridge.start()
|
|
|
|
mock_popen.assert_called_once()
|
|
argv = mock_popen.call_args[0][0]
|
|
self.assertEqual(argv, ["npx", "gitnexus", "mcp"])
|
|
|
|
def test_global_path_spawns_gitnexus_mcp(self):
|
|
"""When global path found, Popen must receive ['gitnexus', 'mcp']."""
|
|
bridge = self._make_bridge()
|
|
|
|
with patch.object(bridge, "_find_gitnexus_command", return_value=["gitnexus"]), \
|
|
patch("subprocess.Popen") as mock_popen, \
|
|
patch.object(bridge, "_send_request", return_value={"protocolVersion": "2024-11-05"}), \
|
|
patch.object(bridge, "_send_notification"):
|
|
|
|
mock_proc = MagicMock()
|
|
mock_proc.stdin = MagicMock()
|
|
mock_proc.stdout = MagicMock()
|
|
mock_proc.stderr = MagicMock()
|
|
mock_popen.return_value = mock_proc
|
|
|
|
bridge.start()
|
|
|
|
mock_popen.assert_called_once()
|
|
argv = mock_popen.call_args[0][0]
|
|
self.assertEqual(argv, ["gitnexus", "mcp"])
|
|
|
|
def test_no_shell_true(self):
|
|
"""Popen must never be called with shell=True."""
|
|
bridge = self._make_bridge()
|
|
|
|
with patch.object(bridge, "_find_gitnexus_command", return_value=["npx", "gitnexus"]), \
|
|
patch("subprocess.Popen") as mock_popen, \
|
|
patch.object(bridge, "_send_request", return_value={"protocolVersion": "2024-11-05"}), \
|
|
patch.object(bridge, "_send_notification"):
|
|
|
|
mock_proc = MagicMock()
|
|
mock_proc.stdin = MagicMock()
|
|
mock_proc.stdout = MagicMock()
|
|
mock_proc.stderr = MagicMock()
|
|
mock_popen.return_value = mock_proc
|
|
|
|
bridge.start()
|
|
|
|
kwargs = mock_popen.call_args[1]
|
|
self.assertNotEqual(kwargs.get("shell"), True)
|
|
|
|
def test_gitnexus_not_found_returns_false(self):
|
|
"""start() returns False and does not call Popen when gitnexus not found."""
|
|
bridge = self._make_bridge()
|
|
|
|
with patch.object(bridge, "_find_gitnexus_command", return_value=None), \
|
|
patch("subprocess.Popen") as mock_popen:
|
|
|
|
result = bridge.start()
|
|
|
|
self.assertFalse(result)
|
|
mock_popen.assert_not_called()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|