"""load_profiles_from_dir — and the tally that keeps a partial set from being silent.
A profile whose config.json is unreadable is skipped by design (it must not take
the server down), but the UI then just shows a shorter list. On prod, 2026-10-07
15:20, four of them (modeler_3d, navi_code, secretary, server_admin) were dropped
with `Extra data: line 125 column 1` — a second JSON document appended to the
file — and the only trace was a per-profile error line: nothing said that four
profiles had gone missing.
"""
import json
from structlog.testing import capture_logs
from navi.profiles.loader import load_profiles_from_dir
GOOD_CONFIG = {"id": "good", "name": "Good", "description": "a valid profile"}
def write_profile(base, dirname, config=GOOD_CONFIG, prompt="You are helpful.\n"):
"""Create a profile directory; `config` is written raw as JSON text."""
d = base / dirname
d.mkdir()
(d / "config.json").write_text(
config if isinstance(config, str) else json.dumps(config), encoding="utf-8"
)
(d / "system_prompt.txt").write_text(prompt, encoding="utf-8")
return d
class TestLoading:
def test_valid_profiles_load(self, tmp_path):
write_profile(tmp_path, "alpha", {**GOOD_CONFIG, "id": "alpha"})
write_profile(tmp_path, "beta", {**GOOD_CONFIG, "id": "beta"})
profiles = load_profiles_from_dir(tmp_path)
assert [p.id for p in profiles] == ["alpha", "beta"]
def test_a_broken_config_does_not_take_the_others_with_it(self, tmp_path, capfd):
write_profile(tmp_path, "alpha", {**GOOD_CONFIG, "id": "alpha"})
write_profile(tmp_path, "corrupt", "{ not json at all")
profiles = load_profiles_from_dir(tmp_path)
assert [p.id for p in profiles] == ["alpha"]
out = capfd.readouterr().out
assert "profile.loader.error" in out
assert "corrupt" in out
def test_a_config_missing_required_keys_is_skipped(self, tmp_path):
write_profile(tmp_path, "alpha", {**GOOD_CONFIG, "id": "alpha"})
write_profile(tmp_path, "nameless", {"description": "no id, no name"})
profiles = load_profiles_from_dir(tmp_path)
assert [p.id for p in profiles] == ["alpha"]
def test_subdirectories_without_the_files_are_not_dropped_profiles(self, tmp_path):
"""Stray directories are not corruption — counting them would make the
tally cry wolf on every ordinary tree (e.g. a __pycache__ dir)."""
write_profile(tmp_path, "alpha", {**GOOD_CONFIG, "id": "alpha"})
(tmp_path / "__pycache__").mkdir()
(tmp_path / "notes.txt").write_text("not a profile", encoding="utf-8")
profiles = load_profiles_from_dir(tmp_path)
assert [p.id for p in profiles] == ["alpha"]
class TestSummaryLog:
def test_the_tally_names_what_was_skipped(self, tmp_path):
write_profile(tmp_path, "alpha", {**GOOD_CONFIG, "id": "alpha"})
write_profile(tmp_path, "corrupt", "{ definitely not json")
with capture_logs() as captured:
load_profiles_from_dir(tmp_path)
errors = [e for e in captured if e["event"] == "profile.loader.error"]
assert [e["profile_dir"] for e in errors] == ["corrupt"]
summary = [e for e in captured if e["event"] == "profile.loader.summary"]
assert len(summary) == 1
assert summary[0]["log_level"] == "warning"
assert summary[0]["loaded"] == 1
assert summary[0]["skipped"] == ["corrupt"]
def test_a_clean_load_warns_about_nothing(self, tmp_path):
write_profile(tmp_path, "alpha", {**GOOD_CONFIG, "id": "alpha"})
with capture_logs() as captured:
load_profiles_from_dir(tmp_path)
assert [e for e in captured if e["log_level"] in ("warning", "error")] == []