Files
Aetheel/tests/test_mcp_config.py
tanmay11k 6d73f74e0b feat: config-driven architecture, install wizard, live runtime switching, usage tracking, auto-failover
Major changes:
- Config-driven adapters: all channels (Slack, Discord, Telegram, WebChat, Webhooks) controlled via config.json with enabled flags and token auto-detection, no CLI flags required
- Runtime engine field: runtime.engine selects opencode/claude from config
- Interactive install script: 8-phase setup wizard with AI runtime detection/installation, token setup, identity file personalization (personality presets), aetheel CLI command, background service (launchd/systemd)
- Live runtime switching: /engine, /model, /provider commands hot-swap the AI runtime from chat without restart, changes persisted to config.json
- Usage tracking: per-request cost extraction from Claude Code JSON output, cumulative stats via /usage command
- Auto-failover: rate limit detection on both runtimes, automatic switch to other engine on quota errors with user notification
- Chat commands work without / prefix (Slack intercepts / in channels), commands: engine, model, provider, config, usage, reload, cron, subagents, status, help
- /config set for editing config.json from chat with dotted key notation
- Security audit saved to docs/security-audit.md
- Full command reference in docs/commands.md
- Future changes doc with NanoClaw agent teams analysis
- Logo added to README and WebChat UI
- README fully rewritten with all features documented
2026-02-18 01:07:12 -05:00

135 lines
4.9 KiB
Python

"""
Tests for the MCP config writer utility (write_mcp_config).
"""
import json
import os
import pytest
from config import MCPConfig, MCPServerConfig, write_mcp_config
# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------
@pytest.fixture
def workspace(tmp_path):
"""Return a temporary workspace directory path."""
return str(tmp_path)
@pytest.fixture
def sample_mcp_config():
"""Return an MCPConfig with one server entry."""
return MCPConfig(
servers={
"my-tool": MCPServerConfig(
command="npx",
args=["-y", "@my/tool"],
env={"API_KEY": "test123"},
)
}
)
# ---------------------------------------------------------------------------
# Tests: write_mcp_config
# ---------------------------------------------------------------------------
class TestWriteMcpConfig:
def test_skips_when_no_servers(self, workspace):
"""Empty servers dict should not create any file."""
write_mcp_config(MCPConfig(), workspace, use_claude=True)
assert not os.path.exists(os.path.join(workspace, ".mcp.json"))
assert not os.path.exists(os.path.join(workspace, "opencode.json"))
def test_writes_mcp_json_for_claude(self, workspace, sample_mcp_config):
"""Claude runtime should produce .mcp.json with 'mcpServers' key."""
write_mcp_config(sample_mcp_config, workspace, use_claude=True)
path = os.path.join(workspace, ".mcp.json")
assert os.path.isfile(path)
with open(path, encoding="utf-8") as f:
data = json.load(f)
assert "mcpServers" in data
assert "my-tool" in data["mcpServers"]
srv = data["mcpServers"]["my-tool"]
assert srv["command"] == "npx"
assert srv["args"] == ["-y", "@my/tool"]
assert srv["env"] == {"API_KEY": "test123"}
def test_writes_opencode_json_for_opencode(self, workspace, sample_mcp_config):
"""OpenCode runtime should produce opencode.json with 'mcp' key."""
write_mcp_config(sample_mcp_config, workspace, use_claude=False)
path = os.path.join(workspace, "opencode.json")
assert os.path.isfile(path)
with open(path, encoding="utf-8") as f:
data = json.load(f)
assert "mcp" in data
assert "my-tool" in data["mcp"]
def test_multiple_servers(self, workspace):
"""Multiple server entries should all appear in the output."""
cfg = MCPConfig(
servers={
"server-a": MCPServerConfig(command="cmd-a", args=["--flag"]),
"server-b": MCPServerConfig(command="cmd-b", env={"X": "1"}),
}
)
write_mcp_config(cfg, workspace, use_claude=True)
with open(os.path.join(workspace, ".mcp.json"), encoding="utf-8") as f:
data = json.load(f)
assert len(data["mcpServers"]) == 2
assert "server-a" in data["mcpServers"]
assert "server-b" in data["mcpServers"]
def test_creates_parent_directories(self, tmp_path):
"""Should create intermediate directories if workspace doesn't exist."""
nested = str(tmp_path / "deep" / "nested" / "workspace")
cfg = MCPConfig(
servers={"s": MCPServerConfig(command="echo")}
)
write_mcp_config(cfg, nested, use_claude=True)
assert os.path.isfile(os.path.join(nested, ".mcp.json"))
def test_produces_valid_json(self, workspace, sample_mcp_config):
"""Output file must be valid, parseable JSON."""
write_mcp_config(sample_mcp_config, workspace, use_claude=True)
path = os.path.join(workspace, ".mcp.json")
with open(path, encoding="utf-8") as f:
data = json.load(f) # Would raise on invalid JSON
assert isinstance(data, dict)
def test_overwrites_existing_file(self, workspace):
"""Writing twice should overwrite the previous config."""
cfg1 = MCPConfig(servers={"old": MCPServerConfig(command="old-cmd")})
cfg2 = MCPConfig(servers={"new": MCPServerConfig(command="new-cmd")})
write_mcp_config(cfg1, workspace, use_claude=True)
write_mcp_config(cfg2, workspace, use_claude=True)
with open(os.path.join(workspace, ".mcp.json"), encoding="utf-8") as f:
data = json.load(f)
assert "new" in data["mcpServers"]
assert "old" not in data["mcpServers"]
def test_tilde_expansion(self, monkeypatch, tmp_path):
"""Workspace path with ~ should be expanded."""
monkeypatch.setenv("HOME", str(tmp_path))
# Also handle Windows
monkeypatch.setattr(os.path, "expanduser", lambda p: p.replace("~", str(tmp_path)))
cfg = MCPConfig(servers={"s": MCPServerConfig(command="echo")})
write_mcp_config(cfg, "~/my-workspace", use_claude=False)
assert os.path.isfile(os.path.join(str(tmp_path), "my-workspace", "opencode.json"))