Skip to content

Commit 8c8f3e9

Browse files
test(cli): add failing tests for mcp show hosts
1 parent f7abe61 commit 8c8f3e9

File tree

1 file changed

+362
-0
lines changed

1 file changed

+362
-0
lines changed

tests/integration/cli/test_cli_reporter_integration.py

Lines changed: 362 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1553,3 +1553,365 @@ def test_env_list_servers_combined_filters(self):
15531553

15541554
# server-c should NOT appear (wrong env)
15551555
assert "server-c" not in output, "server-c should NOT appear"
1556+
1557+
1558+
class TestMCPShowHostsCommand:
1559+
"""Integration tests for hatch mcp show hosts command.
1560+
1561+
Reference: R11 §2.1 (11-enhancing_show_command_v0.md) - Show hosts specification
1562+
1563+
These tests verify that handle_mcp_show_hosts:
1564+
1. Shows detailed host configurations with hierarchical output
1565+
2. Supports --server filter for regex pattern matching
1566+
3. Omits hosts with no matching servers when filter applied
1567+
4. Shows horizontal separators between host sections
1568+
5. Highlights entity names with amber + bold
1569+
6. Supports --json output format
1570+
"""
1571+
1572+
def test_mcp_show_hosts_no_filter(self):
1573+
"""Command should show all hosts with detailed configuration.
1574+
1575+
Reference: R11 §2.1 - Output format without filter
1576+
"""
1577+
from hatch.cli.cli_mcp import handle_mcp_show_hosts
1578+
from hatch.mcp_host_config import MCPHostType, MCPServerConfig
1579+
from hatch.mcp_host_config.models import HostConfiguration
1580+
1581+
mock_env_manager = MagicMock()
1582+
mock_env_manager.list_environments.return_value = [{"name": "default"}]
1583+
mock_env_manager.get_environment_data.return_value = {
1584+
"packages": [
1585+
{
1586+
"name": "weather-server",
1587+
"version": "1.0.0",
1588+
"configured_hosts": {"claude-desktop": {"configured_at": "2026-01-30"}}
1589+
}
1590+
]
1591+
}
1592+
1593+
args = Namespace(
1594+
env_manager=mock_env_manager,
1595+
server=None, # No filter
1596+
json=False,
1597+
)
1598+
1599+
mock_host_config = HostConfiguration(servers={
1600+
"weather-server": MCPServerConfig(name="weather-server", command="uvx", args=["weather-mcp"]),
1601+
"custom-tool": MCPServerConfig(name="custom-tool", command="node", args=["custom.js"]),
1602+
})
1603+
1604+
with patch('hatch.cli.cli_mcp.MCPHostRegistry') as mock_registry:
1605+
mock_registry.detect_available_hosts.return_value = [MCPHostType.CLAUDE_DESKTOP]
1606+
mock_strategy = MagicMock()
1607+
mock_strategy.read_configuration.return_value = mock_host_config
1608+
mock_strategy.get_config_path.return_value = MagicMock(exists=lambda: True)
1609+
mock_registry.get_strategy.return_value = mock_strategy
1610+
1611+
with patch('hatch.mcp_host_config.strategies'):
1612+
captured_output = io.StringIO()
1613+
with patch('sys.stdout', captured_output):
1614+
result = handle_mcp_show_hosts(args)
1615+
1616+
output = captured_output.getvalue()
1617+
1618+
# Should show host header
1619+
assert "claude-desktop" in output, "Host name should appear"
1620+
1621+
# Should show both servers
1622+
assert "weather-server" in output, "weather-server should appear"
1623+
assert "custom-tool" in output, "custom-tool should appear"
1624+
1625+
# Should show server details
1626+
assert "Command:" in output or "uvx" in output, "Server command should appear"
1627+
1628+
def test_mcp_show_hosts_server_filter_exact(self):
1629+
"""--server filter should match exact server name.
1630+
1631+
Reference: R11 §2.1 - Server filter with exact match
1632+
"""
1633+
from hatch.cli.cli_mcp import handle_mcp_show_hosts
1634+
from hatch.mcp_host_config import MCPHostType, MCPServerConfig
1635+
from hatch.mcp_host_config.models import HostConfiguration
1636+
1637+
mock_env_manager = MagicMock()
1638+
mock_env_manager.list_environments.return_value = [{"name": "default"}]
1639+
mock_env_manager.get_environment_data.return_value = {"packages": []}
1640+
1641+
args = Namespace(
1642+
env_manager=mock_env_manager,
1643+
server="weather-server", # Exact match
1644+
json=False,
1645+
)
1646+
1647+
mock_host_config = HostConfiguration(servers={
1648+
"weather-server": MCPServerConfig(name="weather-server", command="uvx", args=["weather-mcp"]),
1649+
"fetch-server": MCPServerConfig(name="fetch-server", command="python", args=["fetch.py"]),
1650+
})
1651+
1652+
with patch('hatch.cli.cli_mcp.MCPHostRegistry') as mock_registry:
1653+
mock_registry.detect_available_hosts.return_value = [MCPHostType.CLAUDE_DESKTOP]
1654+
mock_strategy = MagicMock()
1655+
mock_strategy.read_configuration.return_value = mock_host_config
1656+
mock_strategy.get_config_path.return_value = MagicMock(exists=lambda: True)
1657+
mock_registry.get_strategy.return_value = mock_strategy
1658+
1659+
with patch('hatch.mcp_host_config.strategies'):
1660+
captured_output = io.StringIO()
1661+
with patch('sys.stdout', captured_output):
1662+
result = handle_mcp_show_hosts(args)
1663+
1664+
output = captured_output.getvalue()
1665+
1666+
# Should show matching server
1667+
assert "weather-server" in output, "weather-server should appear"
1668+
1669+
# Should NOT show non-matching server
1670+
assert "fetch-server" not in output, "fetch-server should NOT appear"
1671+
1672+
def test_mcp_show_hosts_server_filter_pattern(self):
1673+
"""--server filter should support regex patterns.
1674+
1675+
Reference: R11 §2.1 - Server filter with regex pattern
1676+
"""
1677+
from hatch.cli.cli_mcp import handle_mcp_show_hosts
1678+
from hatch.mcp_host_config import MCPHostType, MCPServerConfig
1679+
from hatch.mcp_host_config.models import HostConfiguration
1680+
1681+
mock_env_manager = MagicMock()
1682+
mock_env_manager.list_environments.return_value = [{"name": "default"}]
1683+
mock_env_manager.get_environment_data.return_value = {"packages": []}
1684+
1685+
args = Namespace(
1686+
env_manager=mock_env_manager,
1687+
server=".*-server", # Regex pattern
1688+
json=False,
1689+
)
1690+
1691+
mock_host_config = HostConfiguration(servers={
1692+
"weather-server": MCPServerConfig(name="weather-server", command="uvx", args=[]),
1693+
"fetch-server": MCPServerConfig(name="fetch-server", command="python", args=[]),
1694+
"custom-tool": MCPServerConfig(name="custom-tool", command="node", args=[]),
1695+
})
1696+
1697+
with patch('hatch.cli.cli_mcp.MCPHostRegistry') as mock_registry:
1698+
mock_registry.detect_available_hosts.return_value = [MCPHostType.CLAUDE_DESKTOP]
1699+
mock_strategy = MagicMock()
1700+
mock_strategy.read_configuration.return_value = mock_host_config
1701+
mock_strategy.get_config_path.return_value = MagicMock(exists=lambda: True)
1702+
mock_registry.get_strategy.return_value = mock_strategy
1703+
1704+
with patch('hatch.mcp_host_config.strategies'):
1705+
captured_output = io.StringIO()
1706+
with patch('sys.stdout', captured_output):
1707+
result = handle_mcp_show_hosts(args)
1708+
1709+
output = captured_output.getvalue()
1710+
1711+
# Should show matching servers
1712+
assert "weather-server" in output, "weather-server should appear"
1713+
assert "fetch-server" in output, "fetch-server should appear"
1714+
1715+
# Should NOT show non-matching server
1716+
assert "custom-tool" not in output, "custom-tool should NOT appear"
1717+
1718+
def test_mcp_show_hosts_omits_empty_hosts(self):
1719+
"""Hosts with no matching servers should be omitted.
1720+
1721+
Reference: R11 §2.1 - Empty host omission
1722+
"""
1723+
from hatch.cli.cli_mcp import handle_mcp_show_hosts
1724+
from hatch.mcp_host_config import MCPHostType, MCPServerConfig
1725+
from hatch.mcp_host_config.models import HostConfiguration
1726+
1727+
mock_env_manager = MagicMock()
1728+
mock_env_manager.list_environments.return_value = [{"name": "default"}]
1729+
mock_env_manager.get_environment_data.return_value = {"packages": []}
1730+
1731+
args = Namespace(
1732+
env_manager=mock_env_manager,
1733+
server="weather-server", # Only matches on claude-desktop
1734+
json=False,
1735+
)
1736+
1737+
claude_config = HostConfiguration(servers={
1738+
"weather-server": MCPServerConfig(name="weather-server", command="uvx", args=[]),
1739+
})
1740+
cursor_config = HostConfiguration(servers={
1741+
"fetch-server": MCPServerConfig(name="fetch-server", command="python", args=[]),
1742+
})
1743+
1744+
with patch('hatch.cli.cli_mcp.MCPHostRegistry') as mock_registry:
1745+
mock_registry.detect_available_hosts.return_value = [
1746+
MCPHostType.CLAUDE_DESKTOP,
1747+
MCPHostType.CURSOR,
1748+
]
1749+
1750+
def get_strategy_side_effect(host_type):
1751+
mock_strategy = MagicMock()
1752+
mock_strategy.get_config_path.return_value = MagicMock(exists=lambda: True)
1753+
if host_type == MCPHostType.CLAUDE_DESKTOP:
1754+
mock_strategy.read_configuration.return_value = claude_config
1755+
elif host_type == MCPHostType.CURSOR:
1756+
mock_strategy.read_configuration.return_value = cursor_config
1757+
else:
1758+
mock_strategy.read_configuration.return_value = HostConfiguration(servers={})
1759+
return mock_strategy
1760+
1761+
mock_registry.get_strategy.side_effect = get_strategy_side_effect
1762+
1763+
with patch('hatch.mcp_host_config.strategies'):
1764+
captured_output = io.StringIO()
1765+
with patch('sys.stdout', captured_output):
1766+
result = handle_mcp_show_hosts(args)
1767+
1768+
output = captured_output.getvalue()
1769+
1770+
# claude-desktop should appear (has matching server)
1771+
assert "claude-desktop" in output, "claude-desktop should appear"
1772+
1773+
# cursor should NOT appear (no matching servers)
1774+
assert "cursor" not in output, "cursor should NOT appear (no matching servers)"
1775+
1776+
def test_mcp_show_hosts_alphabetical_ordering(self):
1777+
"""Hosts should be sorted alphabetically.
1778+
1779+
Reference: R11 §1.4 - Alphabetical ordering
1780+
"""
1781+
from hatch.cli.cli_mcp import handle_mcp_show_hosts
1782+
from hatch.mcp_host_config import MCPHostType, MCPServerConfig
1783+
from hatch.mcp_host_config.models import HostConfiguration
1784+
1785+
mock_env_manager = MagicMock()
1786+
mock_env_manager.list_environments.return_value = [{"name": "default"}]
1787+
mock_env_manager.get_environment_data.return_value = {"packages": []}
1788+
1789+
args = Namespace(
1790+
env_manager=mock_env_manager,
1791+
server=None,
1792+
json=False,
1793+
)
1794+
1795+
mock_config = HostConfiguration(servers={
1796+
"server-a": MCPServerConfig(name="server-a", command="python", args=[]),
1797+
})
1798+
1799+
with patch('hatch.cli.cli_mcp.MCPHostRegistry') as mock_registry:
1800+
# Return hosts in non-alphabetical order
1801+
mock_registry.detect_available_hosts.return_value = [
1802+
MCPHostType.CURSOR,
1803+
MCPHostType.CLAUDE_DESKTOP,
1804+
]
1805+
1806+
mock_strategy = MagicMock()
1807+
mock_strategy.read_configuration.return_value = mock_config
1808+
mock_strategy.get_config_path.return_value = MagicMock(exists=lambda: True)
1809+
mock_registry.get_strategy.return_value = mock_strategy
1810+
1811+
with patch('hatch.mcp_host_config.strategies'):
1812+
captured_output = io.StringIO()
1813+
with patch('sys.stdout', captured_output):
1814+
result = handle_mcp_show_hosts(args)
1815+
1816+
output = captured_output.getvalue()
1817+
1818+
# Find positions of host names
1819+
claude_pos = output.find("claude-desktop")
1820+
cursor_pos = output.find("cursor")
1821+
1822+
# claude-desktop should appear before cursor (alphabetically)
1823+
assert claude_pos < cursor_pos, \
1824+
"Hosts should be sorted alphabetically (claude-desktop before cursor)"
1825+
1826+
def test_mcp_show_hosts_horizontal_separators(self):
1827+
"""Output should have horizontal separators between host sections.
1828+
1829+
Reference: R11 §3.1 - Horizontal separators
1830+
"""
1831+
from hatch.cli.cli_mcp import handle_mcp_show_hosts
1832+
from hatch.mcp_host_config import MCPHostType, MCPServerConfig
1833+
from hatch.mcp_host_config.models import HostConfiguration
1834+
1835+
mock_env_manager = MagicMock()
1836+
mock_env_manager.list_environments.return_value = [{"name": "default"}]
1837+
mock_env_manager.get_environment_data.return_value = {"packages": []}
1838+
1839+
args = Namespace(
1840+
env_manager=mock_env_manager,
1841+
server=None,
1842+
json=False,
1843+
)
1844+
1845+
mock_config = HostConfiguration(servers={
1846+
"server-a": MCPServerConfig(name="server-a", command="python", args=[]),
1847+
})
1848+
1849+
with patch('hatch.cli.cli_mcp.MCPHostRegistry') as mock_registry:
1850+
mock_registry.detect_available_hosts.return_value = [MCPHostType.CLAUDE_DESKTOP]
1851+
mock_strategy = MagicMock()
1852+
mock_strategy.read_configuration.return_value = mock_config
1853+
mock_strategy.get_config_path.return_value = MagicMock(exists=lambda: True)
1854+
mock_registry.get_strategy.return_value = mock_strategy
1855+
1856+
with patch('hatch.mcp_host_config.strategies'):
1857+
captured_output = io.StringIO()
1858+
with patch('sys.stdout', captured_output):
1859+
result = handle_mcp_show_hosts(args)
1860+
1861+
output = captured_output.getvalue()
1862+
1863+
# Should have horizontal separator (═ character)
1864+
assert "═" in output, "Output should have horizontal separators"
1865+
1866+
def test_mcp_show_hosts_json_output(self):
1867+
"""--json flag should output JSON format.
1868+
1869+
Reference: R11 §6.1 - JSON output format
1870+
"""
1871+
from hatch.cli.cli_mcp import handle_mcp_show_hosts
1872+
from hatch.mcp_host_config import MCPHostType, MCPServerConfig
1873+
from hatch.mcp_host_config.models import HostConfiguration
1874+
import json
1875+
1876+
mock_env_manager = MagicMock()
1877+
mock_env_manager.list_environments.return_value = [{"name": "default"}]
1878+
mock_env_manager.get_environment_data.return_value = {"packages": []}
1879+
1880+
args = Namespace(
1881+
env_manager=mock_env_manager,
1882+
server=None,
1883+
json=True, # JSON output
1884+
)
1885+
1886+
mock_host_config = HostConfiguration(servers={
1887+
"weather-server": MCPServerConfig(name="weather-server", command="uvx", args=["weather-mcp"]),
1888+
})
1889+
1890+
with patch('hatch.cli.cli_mcp.MCPHostRegistry') as mock_registry:
1891+
mock_registry.detect_available_hosts.return_value = [MCPHostType.CLAUDE_DESKTOP]
1892+
mock_strategy = MagicMock()
1893+
mock_strategy.read_configuration.return_value = mock_host_config
1894+
mock_strategy.get_config_path.return_value = MagicMock(exists=lambda: True)
1895+
mock_registry.get_strategy.return_value = mock_strategy
1896+
1897+
with patch('hatch.mcp_host_config.strategies'):
1898+
captured_output = io.StringIO()
1899+
with patch('sys.stdout', captured_output):
1900+
result = handle_mcp_show_hosts(args)
1901+
1902+
output = captured_output.getvalue()
1903+
1904+
# Should be valid JSON
1905+
try:
1906+
data = json.loads(output)
1907+
except json.JSONDecodeError:
1908+
pytest.fail(f"Output should be valid JSON: {output}")
1909+
1910+
# Should have hosts array
1911+
assert "hosts" in data, "JSON should have 'hosts' key"
1912+
assert len(data["hosts"]) > 0, "Should have at least one host"
1913+
1914+
# Host should have expected structure
1915+
host = data["hosts"][0]
1916+
assert "host" in host, "Host should have 'host' key"
1917+
assert "servers" in host, "Host should have 'servers' key"

0 commit comments

Comments
 (0)