"""Agent registry merge/override semantics + creds fallback — no network, temp-dir isolated.""" from __future__ import annotations import json import pytest from config import agent_registry as ar from config.personas import PERSONAS @pytest.fixture def reg(tmp_path, monkeypatch): """Point the registry at a temp file so tests never touch state/agents.json.""" path = tmp_path / "agents.json" monkeypatch.setattr(ar, "registry_path", lambda: path) return path def test_defaults_when_no_file(reg): personas = ar.load_personas() assert [p.id for p in personas] == [p.id for p in PERSONAS] def test_builtin_override_merges(reg): ar.upsert_agent("nova", {"time_budget_min": 90, "target_pick_count": 5}) nova = next(p for p in ar.load_personas() if p.id == "nova") assert nova.time_budget_min == 90 assert nova.target_pick_count == 5 # untouched fields keep code defaults assert nova.display_name.startswith("Nova") def test_only_diff_is_stored_for_builtins(reg): nova_defaults = next(p for p in PERSONAS if p.id == "nova") ar.upsert_agent("nova", {"time_budget_min": 90, "max_position_pct": nova_defaults.max_position_pct}) stored = ar.load_overrides()["nova"] assert stored == {"time_budget_min": 90} # same-as-default values not persisted def test_disabled_agents_excluded(reg): ar.upsert_agent("atlas", {"enabled": False}) assert "atlas" not in [p.id for p in ar.load_personas()] assert "atlas" in [p.id for p in ar.load_personas(include_disabled=True)] def test_custom_agent_created_and_deleted(reg): ar.upsert_agent("scalper", {"display_name": "Scalper", "system_prompt": "scalp fast"}) scalper = next(p for p in ar.load_personas() if p.id == "scalper") assert scalper.options_quota == 1 # custom base defaults applied ar.delete_agent("scalper") assert "scalper" not in [p.id for p in ar.load_personas()] def test_custom_agent_requires_prompt_fields(reg): with pytest.raises(Exception): ar.upsert_agent("broken", {"display_name": "no prompt"}) def test_corrupt_file_falls_back_to_builtins(reg): reg.write_text("{not json") personas = ar.load_personas() assert [p.id for p in personas] == [p.id for p in PERSONAS] def test_invalid_override_falls_back_for_that_agent(reg): reg.write_text(json.dumps({"agents": {"nova": {"max_position_pct": "not-a-number"}}})) nova = next(p for p in ar.load_personas() if p.id == "nova") assert nova.max_position_pct == next(p for p in PERSONAS if p.id == "nova").max_position_pct def test_reset_builtin(reg): ar.upsert_agent("nova", {"time_budget_min": 45}) ar.delete_agent("nova") nova = next(p for p in ar.load_personas() if p.id == "nova") assert nova.time_budget_min is None def test_env_creds_fallback_for_custom_ids(monkeypatch): from config.settings import get_settings monkeypatch.setenv("ALPACA_SCALPER_KEY", "k123") monkeypatch.setenv("ALPACA_SCALPER_SECRET", "s456") key, secret = get_settings().alpaca_creds("scalper") assert (key, secret) == ("k123", "s456")