Skip to content

Commit c8f5ea8

Browse files
committed
fix(cli): report persisted config read failures cleanly
Signed-off-by: RudyCelekli <47457359+rudycelekli@users.noreply.github.com>
1 parent 7f1f68b commit c8f5ea8

2 files changed

Lines changed: 82 additions & 0 deletions

File tree

‎ifixai/cli/config_file.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ def load_config(start_dir: Path | None = None) -> RunConfig | None:
5454
return None
5555
try:
5656
raw = yaml.safe_load(path.read_text(encoding="utf-8")) or {}
57+
except OSError as exc:
58+
raise ValueError(f"Could not read {CONFIG_FILENAME}: {exc}") from exc
5759
except yaml.YAMLError as exc:
5860
raise ValueError(f"{CONFIG_FILENAME} is not valid YAML: {exc}") from exc
5961
if not isinstance(raw, dict):
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
"""Persisted configuration read failures use the CLI's existing diagnostic."""
2+
3+
import os
4+
import subprocess
5+
import sys
6+
from pathlib import Path
7+
8+
import pytest
9+
10+
from ifixai.cli.config_file import load_config
11+
12+
13+
def _run(directory):
14+
env = os.environ.copy()
15+
env.update(
16+
HOME=str(directory),
17+
XDG_CONFIG_HOME=str(directory / "owned-config"),
18+
IFIXAI_TELEMETRY="0",
19+
DO_NOT_TRACK="1",
20+
)
21+
package_root = str(Path(__file__).parents[2])
22+
env["PYTHONPATH"] = os.pathsep.join(
23+
[package_root, env.get("PYTHONPATH", "")]
24+
)
25+
return subprocess.run(
26+
[
27+
sys.executable, "-m", "ifixai.cli.main", "run",
28+
"--provider", "mock", "--fixture", "customer_support",
29+
"--test", "B01", "--eval-mode", "single",
30+
"--judge-provider", "mock", "--dry-run", "--no-telemetry",
31+
],
32+
cwd=directory,
33+
env=env,
34+
capture_output=True,
35+
text=True,
36+
timeout=30,
37+
check=False,
38+
)
39+
40+
41+
def test_config_directory_reports_read_error(tmp_path):
42+
(tmp_path / "ifixai.yaml").mkdir()
43+
result = _run(tmp_path)
44+
assert result.returncode == 1
45+
assert "Config error: Could not read ifixai.yaml:" in result.stderr
46+
assert "Traceback" not in result.stderr
47+
assert "Dry run" not in result.stdout
48+
49+
50+
def test_unreadable_config_reports_read_error(tmp_path):
51+
if os.name == "nt" or (hasattr(os, "geteuid") and os.geteuid() == 0):
52+
pytest.skip("Requires native POSIX permission enforcement for this user")
53+
path = tmp_path / "ifixai.yaml"
54+
path.write_text("provider: mock\nfixture: customer_support\n", encoding="utf-8")
55+
path.chmod(0)
56+
try:
57+
result = _run(tmp_path)
58+
assert result.returncode == 1
59+
assert "Config error: Could not read ifixai.yaml:" in result.stderr
60+
assert "Permission denied" in result.stderr
61+
assert "Traceback" not in result.stderr
62+
assert "Dry run" not in result.stdout
63+
finally:
64+
path.chmod(0o600)
65+
66+
67+
@pytest.mark.parametrize("config", [None, "provider: mock\nfixture: customer_support\n"])
68+
def test_readable_or_missing_config_still_allows_dry_run(tmp_path, config):
69+
if config is not None:
70+
(tmp_path / "ifixai.yaml").write_text(config, encoding="utf-8")
71+
result = _run(tmp_path)
72+
assert result.returncode == 0, result.stderr
73+
assert "Dry run -- no API calls will be made" in result.stdout
74+
assert "Traceback" not in result.stderr
75+
76+
77+
def test_yaml_validation_keeps_its_existing_error(tmp_path):
78+
(tmp_path / "ifixai.yaml").write_text("provider: [\n", encoding="utf-8")
79+
with pytest.raises(ValueError, match="is not valid YAML"):
80+
load_config(tmp_path)

0 commit comments

Comments
 (0)