stock-analysis-api / tests /test_settings.py
vjeai's picture
Deploy: all fixes — yfinance candles, ml_signal 2y history, no handoff schemas, sequential report phase
3be03dd
Raw
History Blame Contribute Delete
3.82 kB
"""
Tests for config/settings.py — model client routing logic.
"""
import pytest
from unittest.mock import patch, MagicMock
def _get_routing_fn(llm_mode: str):
import importlib
import sys
with patch.dict("os.environ", {"LLM_MODE": llm_mode}):
for key in list(sys.modules.keys()):
if "config.settings" in key or key == "config.settings":
del sys.modules[key]
import config.settings as s
importlib.reload(s)
return s.get_model_client, s
class TestModelClientRouting:
def _mock_clients(self, settings_mod):
cheap = MagicMock(name="cheap")
reasoning = MagicMock(name="reasoning")
analysis = MagicMock(name="analysis")
local = MagicMock(name="local")
settings_mod._cheap_client = lambda: cheap
settings_mod._reasoning_client = lambda: reasoning
settings_mod._analysis_client = lambda: analysis
settings_mod._local_client = lambda: local
return cheap, reasoning, analysis, local
def test_cloud_data_uses_cheap(self):
fn, mod = _get_routing_fn("cloud")
cheap, reasoning, analysis, local = self._mock_clients(mod)
result = fn("data")
assert result is cheap
def test_cloud_reasoning_uses_reasoning(self):
fn, mod = _get_routing_fn("cloud")
cheap, reasoning, analysis, local = self._mock_clients(mod)
result = fn("reasoning")
assert result is reasoning
def test_cloud_analysis_uses_analysis(self):
fn, mod = _get_routing_fn("cloud")
cheap, reasoning, analysis, local = self._mock_clients(mod)
result = fn("analysis")
assert result is analysis
def test_local_mode_all_tasks_use_local(self):
fn, mod = _get_routing_fn("local")
cheap, reasoning, analysis, local = self._mock_clients(mod)
for task in ["data", "reasoning", "analysis"]:
assert fn(task) is local, f"task={task} should use local in local mode"
def test_hybrid_data_uses_local(self):
fn, mod = _get_routing_fn("hybrid")
cheap, reasoning, analysis, local = self._mock_clients(mod)
assert fn("data") is local
def test_hybrid_reasoning_uses_reasoning(self):
fn, mod = _get_routing_fn("hybrid")
cheap, reasoning, analysis, local = self._mock_clients(mod)
assert fn("reasoning") is reasoning
def test_hybrid_analysis_uses_analysis(self):
fn, mod = _get_routing_fn("hybrid")
cheap, reasoning, analysis, local = self._mock_clients(mod)
assert fn("analysis") is analysis
def test_unknown_task_defaults_to_analysis(self):
fn, mod = _get_routing_fn("cloud")
cheap, reasoning, analysis, local = self._mock_clients(mod)
assert fn("unknown_task") is analysis
class TestGroqClientFallback:
"""Ensure cheap/reasoning clients gracefully fall back when GROQ key absent."""
def test_cheap_client_falls_back_without_groq_key(self):
with patch.dict("os.environ", {"GROQ_API_KEY": "", "LLM_MODE": "cloud"}):
import importlib, sys
for key in list(sys.modules.keys()):
if "config.settings" in key:
del sys.modules[key]
import config.settings as s
importlib.reload(s)
# Should not raise — falls back to next provider
# We just assert the function exists and returns something
try:
client = s._cheap_client()
assert client is not None
except Exception as e:
# If no fallback keys set either, it's expected to fail downstream
# but should not be a silent swallowed error
assert "api_key" in str(e).lower() or "key" in str(e).lower()