mirror of
https://github.com/TauricResearch/TradingAgents.git
synced 2026-09-19 19:25:24 +03:00
- backtest takes --run-id, so an interrupted sweep continues - an inverted date range or an empty ticker list is an error, not a clean zero-cell run - a setup failure in a sweep reports one line instead of a traceback - the remembered endpoint URL is offered back - a round count set in the environment says it overrode the chosen research depth - the run directory validates the ticker, and the report saves under results_dir - the run says whether it resumed a saved run or started fresh
170 lines
6.6 KiB
Python
170 lines
6.6 KiB
Python
"""The CLI remembers what you chose last time and offers it back.
|
|
|
|
Prefill only: every prompt still appears, so a run never starts on a choice the
|
|
user did not see. Environment variables keep skipping their step outright and
|
|
win over anything remembered. Remembered values are validated against the
|
|
current choices each time, since models and providers come and go between
|
|
versions and a stale one must not be offered.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from unittest import mock
|
|
|
|
import pytest
|
|
|
|
from cli.models import AnalystType
|
|
from cli.prefs import load_last_run, sanitize, save_last_run
|
|
|
|
SAVED = {
|
|
"output_language": "English",
|
|
"analysts": ["market", "fundamentals"],
|
|
"research_depth": 3,
|
|
"llm_provider": "openai",
|
|
"quick_think_llm": "gpt-5.6-mini",
|
|
"deep_think_llm": "gpt-5.6",
|
|
}
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def _home(tmp_path, monkeypatch):
|
|
monkeypatch.setattr("cli.prefs._PREFS_PATH", tmp_path / "cli_prefs.json")
|
|
return tmp_path
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_round_trip():
|
|
save_last_run(SAVED)
|
|
assert load_last_run() == SAVED
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_missing_file_is_not_an_error():
|
|
assert load_last_run() == {}
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_a_corrupt_file_degrades_to_no_memory(_home):
|
|
(_home / "cli_prefs.json").write_text("{not json")
|
|
assert load_last_run() == {}
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_a_half_written_file_cannot_be_observed(_home):
|
|
"""Two runs finishing together must never leave a torn file behind."""
|
|
save_last_run(SAVED)
|
|
save_last_run({**SAVED, "research_depth": 5})
|
|
assert load_last_run()["research_depth"] == 5
|
|
assert list((_home).glob("*.tmp*")) == []
|
|
|
|
|
|
# --- validation against the current choices ---------------------------------
|
|
|
|
@pytest.mark.unit
|
|
def test_a_model_that_no_longer_exists_is_dropped():
|
|
# gpt-5.4 is still accepted by config, but is no longer in the picker's list.
|
|
kept = sanitize({**SAVED, "quick_think_llm": "gpt-5.4"}, "stock")
|
|
assert "quick_think_llm" not in kept
|
|
assert kept["deep_think_llm"] == "gpt-5.6" # the valid sibling survives
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_an_unknown_provider_drops_itself_and_its_models():
|
|
kept = sanitize({**SAVED, "llm_provider": "no-such-provider"}, "stock")
|
|
assert "llm_provider" not in kept
|
|
assert "quick_think_llm" not in kept and "deep_think_llm" not in kept
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_analysts_are_narrowed_to_the_asset_type():
|
|
kept = sanitize(SAVED, "crypto")
|
|
assert AnalystType.FUNDAMENTALS.value not in kept["analysts"]
|
|
assert AnalystType.MARKET.value in kept["analysts"]
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_junk_values_are_dropped_rather_than_offered():
|
|
kept = sanitize({"research_depth": 99, "analysts": ["astrology"], "output_language": 5}, "stock")
|
|
assert kept == {}
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_a_region_specific_provider_survives():
|
|
kept = sanitize({**SAVED, "llm_provider": "qwen-cn", "quick_think_llm": None}, "stock")
|
|
assert kept["llm_provider"] == "qwen-cn"
|
|
|
|
|
|
# --- wiring ------------------------------------------------------------------
|
|
|
|
def _answer_every_prompt(monkeypatch):
|
|
"""Drive the real selection flow, answering each prompt with a fixed value."""
|
|
import cli.main as m
|
|
|
|
monkeypatch.setattr(m, "fetch_announcements", lambda: [])
|
|
monkeypatch.setattr(m, "display_announcements", lambda *a: None)
|
|
monkeypatch.setattr(m, "get_ticker", lambda: "NVDA")
|
|
monkeypatch.setattr(m, "get_analysis_date", lambda: "2026-09-01")
|
|
monkeypatch.setattr(m, "ask_output_language", lambda default=None: "English")
|
|
monkeypatch.setattr(m, "select_analysts", lambda asset_type, default=None: [AnalystType.MARKET])
|
|
monkeypatch.setattr(m, "select_research_depth", lambda default=None: 3)
|
|
monkeypatch.setattr(m, "select_llm_provider", lambda default=None: ("openai", None))
|
|
monkeypatch.setattr(m, "select_shallow_thinking_agent", lambda p, default=None: "gpt-5.6-mini")
|
|
monkeypatch.setattr(m, "select_deep_thinking_agent", lambda p, default=None: "gpt-5.6")
|
|
monkeypatch.setattr(m, "ask_openai_reasoning_effort", lambda: "medium")
|
|
return m
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_selections_are_remembered_after_a_run(monkeypatch):
|
|
"""Drives the real flow: a stubbed selections dict would hide a key mismatch."""
|
|
m = _answer_every_prompt(monkeypatch)
|
|
|
|
m.get_user_selections()
|
|
|
|
remembered = load_last_run()
|
|
assert remembered["analysts"] == ["market"]
|
|
assert remembered["quick_think_llm"] == "gpt-5.6-mini"
|
|
assert remembered["deep_think_llm"] == "gpt-5.6"
|
|
assert remembered["llm_provider"] == "openai"
|
|
assert "ticker" not in remembered # changes every run; never remembered
|
|
assert "analysis_date" not in remembered # a stale date must not be offered
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_a_custom_language_is_remembered_without_breaking_the_next_run():
|
|
"""A free-text answer is not one of the menu's choices, and questionary
|
|
rejects a default it cannot find, so offering it back would crash startup."""
|
|
from cli.utils import ask_output_language
|
|
|
|
save_last_run({"output_language": "Turkish"})
|
|
with mock.patch("cli.utils.questionary.select") as select:
|
|
select.return_value.ask.return_value = "English"
|
|
ask_output_language(load_last_run()["output_language"])
|
|
assert select.call_args.kwargs["default"] is None
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_a_remembered_endpoint_is_offered_back(monkeypatch):
|
|
"""Users of a local or custom endpoint retyped the URL every run: it was
|
|
remembered and validated, then never read."""
|
|
import cli.main as m
|
|
|
|
save_last_run({"llm_provider": "openai_compatible", "backend_url": "http://localhost:1234/v1"})
|
|
offered = {}
|
|
monkeypatch.setattr(m, "select_llm_provider", lambda default=None: ("openai_compatible", None))
|
|
monkeypatch.setattr(m, "prompt_openai_compatible_url",
|
|
lambda default=None: offered.setdefault("default", default) or "http://x/v1")
|
|
monkeypatch.setattr(m, "fetch_announcements", lambda: [])
|
|
monkeypatch.setattr(m, "display_announcements", lambda *a: None)
|
|
monkeypatch.setattr(m, "get_ticker", lambda: "NVDA")
|
|
monkeypatch.setattr(m, "get_analysis_date", lambda: "2026-09-01")
|
|
monkeypatch.setattr(m, "ask_output_language", lambda default=None: "English")
|
|
monkeypatch.setattr(m, "select_analysts", lambda asset_type, default=None: [AnalystType.MARKET])
|
|
monkeypatch.setattr(m, "select_research_depth", lambda default=None: 1)
|
|
monkeypatch.setattr(m, "select_shallow_thinking_agent", lambda p, default=None: "local-model")
|
|
monkeypatch.setattr(m, "select_deep_thinking_agent", lambda p, default=None: "local-model")
|
|
|
|
m.get_user_selections()
|
|
|
|
assert offered["default"] == "http://localhost:1234/v1"
|