Pin text I/O to UTF-8 and fail CI on locale-dependent reads/writes (#3296)

Co-authored-by: ShuQingDollarVoyager <57471784+ShuQingDollarVoyager@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Max 2026-08-16 12:41:53 +01:00 • committed by GitHub
commit 5285e936a9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 76 additions and 57 deletions

View file

@ -64,7 +64,7 @@ def test_mcp_requirement_falls_back_when_mcp_is_not_installed(monkeypatch: pytes
def _read_server(config_dir: Path, name: str) -> dict[str, Any]:
config = json.loads((config_dir / "claude_desktop_config.json").read_text())
config = json.loads((config_dir / "claude_desktop_config.json").read_text(encoding="utf-8"))
return config["mcpServers"][name]
@ -121,7 +121,7 @@ def test_env_vars_written(config_dir: Path):
def test_existing_env_vars_merged_new_wins(config_dir: Path):
"""Re-installing should merge env vars, with new values overriding existing ones."""
(config_dir / "claude_desktop_config.json").write_text(
json.dumps({"mcpServers": {"s": {"env": {"OLD": "keep", "KEY": "old"}}}})
json.dumps({"mcpServers": {"s": {"env": {"OLD": "keep", "KEY": "old"}}}}), encoding="utf-8"
)
assert update_claude_config(file_spec="s.py:app", server_name="s", env_vars={"KEY": "new"})
@ -131,7 +131,9 @@ def test_existing_env_vars_merged_new_wins(config_dir: Path):
def test_existing_env_vars_preserved_without_new(config_dir: Path):
"""Re-installing without env_vars should keep the existing env block intact."""
(config_dir / "claude_desktop_config.json").write_text(json.dumps({"mcpServers": {"s": {"env": {"KEEP": "me"}}}}))
(config_dir / "claude_desktop_config.json").write_text(
json.dumps({"mcpServers": {"s": {"env": {"KEEP": "me"}}}}), encoding="utf-8"
)
assert update_claude_config(file_spec="s.py:app", server_name="s")
@ -139,14 +141,27 @@ def test_existing_env_vars_preserved_without_new(config_dir: Path):
def test_other_servers_preserved(config_dir: Path):
"""Installing a new server should not clobber existing mcpServers entries."""
(config_dir / "claude_desktop_config.json").write_text(json.dumps({"mcpServers": {"other": {"command": "x"}}}))
"""Installing a new server must not clobber existing entries, non-ASCII text included (the file is UTF-8)."""
other = {"command": "C:\\Users\\张伟\\uv.exe", "env": {"CITY": "Zürich"}}
config_file = config_dir / "claude_desktop_config.json"
config_file.write_text(json.dumps({"mcpServers": {"文件": other}}, ensure_ascii=False), encoding="utf-8")
assert update_claude_config(file_spec="s.py:app", server_name="s")
config = json.loads((config_dir / "claude_desktop_config.json").read_text())
assert set(config["mcpServers"]) == {"other", "s"}
assert config["mcpServers"]["other"] == {"command": "x"}
config = json.loads(config_file.read_text(encoding="utf-8"))
assert set(config["mcpServers"]) == {"文件", "s"}
assert config["mcpServers"]["文件"] == other
@pytest.mark.parametrize("codec", ["utf-8-sig", "utf-16"])
def test_existing_config_with_a_bom_is_accepted(config_dir: Path, codec: str):
"""A config saved by Windows tooling (UTF-8 with BOM, or PowerShell 5's UTF-16 `>`) can still be installed into."""
config_file = config_dir / "claude_desktop_config.json"
config_file.write_bytes(json.dumps({"mcpServers": {"other": {"command": "x"}}}).encode(codec))
assert update_claude_config(file_spec="s.py:app", server_name="s")
assert set(json.loads(config_file.read_bytes())["mcpServers"]) == {"other", "s"}
def test_raises_when_config_dir_missing(monkeypatch: pytest.MonkeyPatch):

View file

@ -28,7 +28,7 @@ def _set_mcp_version(monkeypatch: pytest.MonkeyPatch, version: str) -> None:
def test_parse_file_path_accepts_valid_specs(tmp_path: Path, spec: str, expected_obj: str | None):
"""Should accept valid file specs."""
file = tmp_path / spec.split(":")[0]
file.write_text("x = 1")
file.write_text("x = 1", encoding="utf-8")
path, obj = _parse_file_path(f"{file}:{expected_obj}" if ":" in spec else str(file))
assert path == file.resolve()
assert obj == expected_obj