fix: scope MCP configuration to Codex and stabilize Windows launchers

This commit is contained in:
chenmingxuan committed 2026-10-08 18:01:15 +08:00
1 parent 18d17da966
commit c96935b47b
13 files changed
+290 -1125

No files matched your search

+74 -59
View File
@@ -120,33 +120,7 @@ command = "leave-alone"
assert path.with_name(path.name + ".artemis-update.bak").read_text(encoding="utf-8") == original
def test_json_migrates_existing_standard_mcp_to_current_checkout(tmp_path):
path = tmp_path / "mcp.json"
root = tmp_path / "project"
config = {
"cwd": str(root),
"command": "old",
"args": ["-m", "mcp_server"],
"disabled": True,
"env": {"CUSTOM": "preserved"},
"autoApprove": [],
}
path.write_text(json.dumps({"mcpServers": {"artemis": config, "other": {"command": "keep"}}}))
moved_root = tmp_path / "new release"
assert update.refresh_json(path, ("mcpServers",), moved_root, "new", ("mobile_batch",))
result = json.loads(path.read_text())["mcpServers"]
assert result["artemis"]["env"]["CUSTOM"] == "preserved"
assert result["artemis"]["env"]["PYTHONPATH"] == str(moved_root)
assert result["artemis"]["cwd"] == str(moved_root)
assert result["artemis"]["disabled"] is True
assert result["other"] == {"command": "keep"}
assert not update.refresh_json(path, ("mcpServers",), moved_root, "new", ("mobile_batch",))
missing = tmp_path / "not-installed.json"
assert not update.refresh_json(missing, ("mcpServers",), root, "new", ())
assert not missing.exists()
def test_mcp_sync_installs_detected_clients_and_migrates_codex(tmp_path, monkeypatch):
def test_mcp_sync_updates_only_codex_and_preserves_other_clients(tmp_path, monkeypatch):
from artemis.interfaces.cli.commands import mcp as mcp_command
home = tmp_path / "home"
@@ -170,8 +144,11 @@ def test_mcp_sync_installs_detected_clients_and_migrates_codex(tmp_path, monkeyp
cursor_home = home / ".cursor"
cursor_home.mkdir()
(cursor_home / "mcp.json").write_text('{"mcpServers":{"other":{"command":"keep"}}}')
vscode_home = home / "AppData/Code/User"
vscode_home.mkdir(parents=True)
vscode_config = vscode_home / "mcp.json"
vscode_config.write_bytes(b"{invalid VS Code JSONC}")
monkeypatch.setattr("pathlib.Path.home", lambda: home)
monkeypatch.setattr(mcp_command, "_get_vscode_user_dir", lambda: home / "vscode-user")
monkeypatch.setenv("CODEX_HOME", str(codex_home))
monkeypatch.setenv("APPDATA", str(home / "AppData"))
root = tmp_path / "new release"
@@ -187,26 +164,31 @@ def test_mcp_sync_installs_detected_clients_and_migrates_codex(tmp_path, monkeyp
assert entry["env"]["PYTHONPATH"] == str(root)
assert entry["enabled_tools"] == list(mcp_command.ARTEMIS_MCP_TOOLS)
cursor = json.loads((cursor_home / "mcp.json").read_text(encoding="utf-8"))
assert cursor["mcpServers"]["artemis"]["env"]["PYTHONPATH"] == str(root)
assert "artemis" not in cursor["mcpServers"]
assert cursor["mcpServers"]["other"] == {"command": "keep"}
assert vscode_config.read_bytes() == b"{invalid VS Code JSONC}"
assert not vscode_config.with_name("mcp.json.artemis-update.bak").exists()
first = codex_config.read_text(encoding="utf-8")
assert update.refresh_mcp(root) == 0
assert codex_config.read_text(encoding="utf-8") == first
def test_mcp_sync_fails_on_invalid_detected_config_without_overwriting(tmp_path, monkeypatch):
from artemis.interfaces.cli.commands import mcp as mcp_command
def test_mcp_sync_fails_on_invalid_codex_config_without_overwriting(tmp_path, monkeypatch, capsys):
home = tmp_path / "home"
cursor_home = home / ".cursor"
cursor_home.mkdir(parents=True)
config = cursor_home / "mcp.json"
config.write_text("{invalid json", encoding="utf-8")
codex_home = home / ".codex"
codex_home.mkdir(parents=True)
config = codex_home / "config.toml"
original = b'model = "keep-secret"\n[mcp_servers.artemis\n'
config.write_bytes(original)
monkeypatch.setattr("pathlib.Path.home", lambda: home)
monkeypatch.setattr(mcp_command, "_get_vscode_user_dir", lambda: home / "vscode-user")
monkeypatch.delenv("CODEX_HOME", raising=False)
monkeypatch.setenv("CODEX_HOME", str(codex_home))
assert update.refresh_mcp(tmp_path / "project") == 1
assert config.read_text(encoding="utf-8") == "{invalid json"
assert config.read_bytes() == original
assert not config.with_name(config.name + ".artemis-update.bak").exists()
output = capsys.readouterr().out
assert str(config) in output
assert "Invalid TOML" in output
assert "keep-secret" not in output
def test_mcp_sync_installs_codex_when_client_directory_exists(tmp_path, monkeypatch):
@@ -216,7 +198,6 @@ def test_mcp_sync_installs_codex_when_client_directory_exists(tmp_path, monkeypa
codex_home = home / ".codex"
codex_home.mkdir(parents=True)
monkeypatch.setattr("pathlib.Path.home", lambda: home)
monkeypatch.setattr(mcp_command, "_get_vscode_user_dir", lambda: home / "vscode-user")
monkeypatch.setenv("CODEX_HOME", str(codex_home))
root = tmp_path / "project"
assert update.refresh_mcp(root) == 0
@@ -225,6 +206,59 @@ def test_mcp_sync_installs_codex_when_client_directory_exists(tmp_path, monkeypa
assert config["mcp_servers"]["artemis"]["enabled_tools"] == list(mcp_command.ARTEMIS_MCP_TOOLS)
@pytest.mark.parametrize("codex_home_setting", [None, "", "custom"])
def test_codex_sync_reports_actual_path_and_ready_state(
tmp_path, monkeypatch, capsys, codex_home_setting
):
home = tmp_path / "home"
codex_home = home / ".codex" if codex_home_setting != "custom" else tmp_path / "custom Codex"
codex_home.mkdir(parents=True)
monkeypatch.setattr("pathlib.Path.home", lambda: home)
monkeypatch.setenv("ARTEMIS_LAUNCH_LANG", "zh-CN")
if codex_home_setting is None:
monkeypatch.delenv("CODEX_HOME", raising=False)
else:
monkeypatch.setenv("CODEX_HOME", str(codex_home) if codex_home_setting == "custom" else "")
root = tmp_path / "project"
assert update.refresh_mcp(root) == 0
first = capsys.readouterr().out
assert str(codex_home / "config.toml") in first
assert "已安装" in first
assert update.refresh_mcp(root) == 0
second = capsys.readouterr().out
assert str(codex_home / "config.toml") in second
assert "已是最新" in second
if codex_home_setting == "custom":
assert not (home / ".codex").exists()
def test_codex_sync_reports_missing_directory_without_touching_other_clients(
tmp_path, monkeypatch, capsys
):
monkeypatch.setattr("pathlib.Path.home", lambda: tmp_path)
monkeypatch.delenv("CODEX_HOME", raising=False)
monkeypatch.setenv("ARTEMIS_LAUNCH_LANG", "zh-CN")
assert update.refresh_mcp(tmp_path / "project") == 0
output = capsys.readouterr().out
assert str(tmp_path / ".codex/config.toml") in output
assert "未发现 Codex 配置目录" in output
assert "--install codex" in output
assert not (tmp_path / ".codex").exists()
def test_custom_codex_command_is_preserved_and_reported(tmp_path, monkeypatch, capsys):
codex_home = tmp_path / ".codex"
codex_home.mkdir()
config = codex_home / "config.toml"
original = '[mcp_servers.artemis]\ncommand = "custom"\nargs = ["wrapper"]\n'
config.write_text(original, encoding="utf-8")
monkeypatch.setenv("CODEX_HOME", str(codex_home))
monkeypatch.setenv("ARTEMIS_LAUNCH_LANG", "zh-CN")
assert update.refresh_mcp(tmp_path / "project") == 0
assert config.read_text(encoding="utf-8") == original
assert "自定义" in capsys.readouterr().out
def test_codex_install_preserves_existing_user_tables(tmp_path):
path = tmp_path / "config.toml"
path.write_text(
@@ -277,25 +311,6 @@ def test_codex_inline_environment_sync_is_idempotent(tmp_path):
)
def test_custom_mcp_wrapper_is_left_unchanged(tmp_path):
path = tmp_path / "mcp.json"
original = '{"mcpServers":{"artemis":{"command":"uv","args":["run","custom"]}}}'
path.write_text(original, encoding="utf-8")
assert (
update.refresh_json(
path,
("mcpServers",),
tmp_path,
"new-python",
("mobile_run_task",),
install_missing=True,
template={"command": "new-python", "args": ["-m", "mcp_server"], "env": {}},
)
is None
)
assert path.read_text(encoding="utf-8") == original
def test_frontend_fingerprint_detects_lock_source_and_deleted_assets(tmp_path):
ui = tmp_path / "apps/showcase_ui"
(ui / "src").mkdir(parents=True)