fix(cli): use UTF-8 for config files

This commit is contained in:
Haotian Zheng
2026-08-10 16:44:17 -07:00
committed by Elliot Slusky
parent 7feda3dad3
commit 4efdb07dae
5 changed files with 99 additions and 12 deletions
+4 -4
View File
@@ -158,7 +158,7 @@ def _show_toml_config(console: Console, config_path: Path) -> None:
console.print(f"[dim]Loading config from: {config_path}[/dim]")
if config_path.exists():
config_content = config_path.read_text()
config_content = config_path.read_text(encoding="utf-8")
syntax = Syntax(config_content, "toml", theme="monokai", line_numbers=True)
console.print(Panel(syntax, title="Config File", border_style="cyan"))
else:
@@ -170,7 +170,7 @@ def _show_json_config(console: Console, config_path: Path) -> None:
console.print(f"[dim]Loading config from: {config_path}[/dim]")
if config_path.exists():
config_content = config_path.read_text()
config_content = config_path.read_text(encoding="utf-8")
try:
import tomllib # Python 3.11+
@@ -375,7 +375,7 @@ def set_config(key: str, value: str) -> None:
os.environ.get("OPENJARVIS_CONFIG", DEFAULT_CONFIG_DIR / "config.toml")
)
if config_path.exists():
doc = tomlkit.parse(config_path.read_text())
doc = tomlkit.parse(config_path.read_text(encoding="utf-8"))
else:
doc = tomlkit.document()
config_path.parent.mkdir(parents=True, exist_ok=True)
@@ -390,7 +390,7 @@ def set_config(key: str, value: str) -> None:
current[parts[-1]] = typed_value
# Write back
config_path.write_text(tomlkit.dumps(doc))
config_path.write_text(tomlkit.dumps(doc), encoding="utf-8")
console.print(f"[green]Set[/green] {key} = {value!r}")
+3 -1
View File
@@ -344,7 +344,9 @@ def init(
console.print(f" Looked in: {examples_dir}")
raise SystemExit(1)
DEFAULT_CONFIG_DIR.mkdir(parents=True, exist_ok=True)
DEFAULT_CONFIG_PATH.write_text(preset_path.read_text())
DEFAULT_CONFIG_PATH.write_text(
preset_path.read_text(encoding="utf-8"), encoding="utf-8"
)
console.print(
f"[green]Preset '{preset}' installed to {DEFAULT_CONFIG_PATH}[/green]"
)
+32
View File
@@ -138,6 +138,38 @@ class TestCLI:
content = config_path.read_text()
assert "[engine]" in content
def test_init_preset_uses_utf8_for_config_copy(self, tmp_path: Path) -> None:
"""Preset installation reads and writes shipped TOML as UTF-8."""
config_dir = tmp_path / ".openjarvis"
config_path = config_dir / "config.toml"
original_read_text = Path.read_text
original_write_text = Path.write_text
def read_text(path: Path, *args: object, **kwargs: object) -> str:
if path.name == "chat-simple.toml":
assert kwargs.get("encoding") == "utf-8"
return original_read_text(path, *args, **kwargs)
def write_text(path: Path, data: str, *args: object, **kwargs: object) -> int:
if path == config_path:
assert kwargs.get("encoding") == "utf-8"
return original_write_text(path, data, *args, **kwargs)
with (
mock.patch("openjarvis.cli.init_cmd.DEFAULT_CONFIG_DIR", config_dir),
mock.patch("openjarvis.cli.init_cmd.DEFAULT_CONFIG_PATH", config_path),
mock.patch.object(Path, "read_text", autospec=True, side_effect=read_text),
mock.patch.object(
Path, "write_text", autospec=True, side_effect=write_text
),
):
result = CliRunner().invoke(cli, ["init", "--preset", "chat-simple"])
assert result.exit_code == 0
assert "lightweight conversational AI" in config_path.read_text(
encoding="utf-8"
)
class TestStartupResilience:
"""Importing the CLI must not force heavy/native deps (#404, #309).
+24 -7
View File
@@ -4,6 +4,7 @@ from __future__ import annotations
import json
from pathlib import Path
from unittest import mock
import pytest
from click.testing import CliRunner
@@ -109,18 +110,34 @@ temperature = 0.7
except json.JSONDecodeError:
pytest.fail(f"Output is not valid JSON: {result.output}")
def test_config_show_toml_displays_raw_content(self, tmp_path: Path) -> None:
"""Test that config show toml displays the raw TOML content."""
@pytest.mark.parametrize("output_format", ["toml", "json"])
def test_config_show_uses_utf8_for_config_file(
self, tmp_path: Path, output_format: str
) -> None:
"""Test that config show reads UTF-8 config files explicitly."""
# Create a temporary config file
config_file = tmp_path / "test_config.toml"
config_file.write_text('[engine]\ndefault = "ollama"\n')
result = CliRunner().invoke(
cli, ["config", "show", "toml", "--path", str(config_file)]
config_file.write_text(
'# Preset comment — stored as UTF-8\n[engine]\ndefault = "ollama"\n',
encoding="utf-8",
)
original_read_text = Path.read_text
def read_text(path: Path, *args: object, **kwargs: object) -> str:
if path == config_file:
assert kwargs.get("encoding") == "utf-8"
return original_read_text(path, *args, **kwargs)
with mock.patch.object(Path, "read_text", autospec=True, side_effect=read_text):
result = CliRunner().invoke(
cli, ["config", "show", output_format, "--path", str(config_file)]
)
assert result.exit_code == 0
assert "[engine]" in result.output
if output_format == "toml":
assert "[engine]" in result.output
else:
assert '"engine"' in result.output
assert "ollama" in result.output
def test_config_show_json_displays_parsed_content(self, tmp_path: Path) -> None:
+36
View File
@@ -60,6 +60,42 @@ class TestConfigSet:
assert "vllm" in content
assert "qwen2.5:3b" in content
def test_set_uses_utf8_for_existing_config(self, tmp_path: Path) -> None:
"""config set preserves a UTF-8 config regardless of the system locale."""
config_file = tmp_path / "config.toml"
config_file.write_text(
'# Preset comment — stored as UTF-8\n[engine]\ndefault = "ollama"\n',
encoding="utf-8",
)
original_read_text = Path.read_text
original_write_text = Path.write_text
def read_text(path: Path, *args: object, **kwargs: object) -> str:
if path == config_file:
assert kwargs.get("encoding") == "utf-8"
return original_read_text(path, *args, **kwargs)
def write_text(path: Path, data: str, *args: object, **kwargs: object) -> int:
if path == config_file:
assert kwargs.get("encoding") == "utf-8"
return original_write_text(path, data, *args, **kwargs)
with (
mock.patch.dict(os.environ, {"OPENJARVIS_CONFIG": str(config_file)}),
mock.patch.object(Path, "read_text", autospec=True, side_effect=read_text),
mock.patch.object(
Path, "write_text", autospec=True, side_effect=write_text
),
):
result = CliRunner().invoke(
cli, ["config", "set", "engine.default", "vllm"]
)
assert result.exit_code == 0
content = config_file.read_text(encoding="utf-8")
assert "Preset comment — stored as UTF-8" in content
assert "vllm" in content
def test_set_invalid_key_rejected(self, tmp_path: Path) -> None:
"""config set rejects unknown keys."""
config_file = tmp_path / "config.toml"