Files
tradingagents/tests/test_vendor_routing.py
Yijia-Xiao e3bc872982 chore(lint): make the repository ruff-clean under the strict select
Clear the deferred full-repo lint backlog so the whole tree passes the strict
ruff select (E,W,F,I,B,UP,C4,SIM). Mechanical fixes dominate: import sorting,
pep585/604 annotations, dropped dead imports, and whitespace. The few semantic
changes are behavior-preserving: declare __all__ on the agent_utils and
alpha_vantage re-export hubs; expand 'from x import *' to explicit names; use
immutable tuple defaults instead of mutable list defaults; contextlib.suppress
for try/except/pass; and narrow an over-broad assertRaises.
2026-06-14 16:38:36 +00:00

102 lines
4.0 KiB
Python

"""Vendor router must respect the configured chain and never silently hide a
broken primary.
Regressions for #988 (explicit single-vendor config still fell back to others),
#289 (fallback ran for unchosen vendors), and #989 (serious primary failures
were swallowed without a trace).
"""
import copy
import unittest
from unittest import mock
import pytest
import tradingagents.dataflows.config as config_module
import tradingagents.default_config as default_config
from tradingagents.dataflows import interface
from tradingagents.dataflows.config import set_config
from tradingagents.dataflows.symbol_utils import NoMarketDataError
def _reset_config():
# Hard reset: set_config() merges, so empty DEFAULT dicts (e.g. tool_vendors)
# don't clear keys leaked by other tests. Replace the global outright.
config_module._config = copy.deepcopy(default_config.DEFAULT_CONFIG)
def _no_data(symbol, *a, **k):
raise NoMarketDataError(symbol, symbol, "no rows")
def _returns(value):
def impl(symbol, *a, **k):
return value
return impl
def _raises(exc):
def impl(symbol, *a, **k):
raise exc
return impl
@pytest.mark.unit
class VendorRoutingTests(unittest.TestCase):
def setUp(self):
_reset_config()
def tearDown(self):
_reset_config()
def _route(self, vendors_for_get_stock_data):
return mock.patch.dict(
interface.VENDOR_METHODS,
{"get_stock_data": vendors_for_get_stock_data},
clear=False,
)
def test_explicit_single_vendor_does_not_fall_back(self):
# #988: with yfinance pinned, a healthy alpha_vantage must NOT be used.
set_config({"data_vendors": {"core_stock_apis": "yfinance"}})
av = mock.Mock(side_effect=_returns("AV_DATA"))
with self._route({"yfinance": _no_data, "alpha_vantage": av}):
result = interface.route_to_vendor("get_stock_data", "FAKE", "2026-01-01", "2026-01-10")
self.assertIn("NO_DATA_AVAILABLE", result)
av.assert_not_called() # the unchosen vendor was never tried
def test_explicit_multi_vendor_falls_back_within_chain(self):
# Listing both vendors opts in to ordered fallback.
set_config({"data_vendors": {"core_stock_apis": "yfinance,alpha_vantage"}})
with self._route({"yfinance": _no_data, "alpha_vantage": _returns("AV_DATA")}):
result = interface.route_to_vendor("get_stock_data", "AAPL", "2026-01-01", "2026-01-10")
self.assertEqual(result, "AV_DATA")
def test_primary_error_is_logged_not_masked(self):
# #989: primary errors + fallback no-data -> NO_DATA, but the failure
# must be visible in logs (broken primary not hidden).
set_config({"data_vendors": {"core_stock_apis": "yfinance,alpha_vantage"}})
with self._route({"yfinance": _raises(ValueError("boom")), "alpha_vantage": _no_data}), \
self.assertLogs("tradingagents.dataflows.interface", level="WARNING") as cm:
result = interface.route_to_vendor("get_stock_data", "AAPL", "2026-01-01", "2026-01-10")
self.assertIn("NO_DATA_AVAILABLE", result)
joined = "\n".join(cm.output)
self.assertIn("boom", joined) # the real error surfaced in logs
self.assertIn("yfinance", joined)
def test_unknown_configured_vendor_raises(self):
set_config({"data_vendors": {"core_stock_apis": "bogus_vendor"}})
with self.assertRaises(ValueError) as ctx:
interface.route_to_vendor("get_stock_data", "AAPL", "2026-01-01", "2026-01-10")
self.assertIn("bogus_vendor", str(ctx.exception))
def test_default_sentinel_uses_all_vendors(self):
# No explicit choice ("default") keeps the resilient full-chain behavior.
set_config({"data_vendors": {"core_stock_apis": "default"}})
with self._route({"yfinance": _no_data, "alpha_vantage": _returns("AV_DATA")}):
result = interface.route_to_vendor("get_stock_data", "AAPL", "2026-01-01", "2026-01-10")
self.assertEqual(result, "AV_DATA")
if __name__ == "__main__":
unittest.main()