stock-data-api / tests /test_source_runner.py
fromozuzhouzzz
fix(api): encoding, financial history, fund-flow & report resilience
c20d7c0
Raw
History Blame Contribute Delete
2.86 kB
"""Tests for source runner resilience."""
import time
from unittest.mock import MagicMock, patch
import pytest
from app.services.source_runner import (
AllSourcesFailed,
SourceDataInsufficient,
SourceResult,
SourceUnavailable,
_backoff_seconds,
_count_rows,
_is_transient_error,
run_sources,
)
class TestCountRows:
def test_list(self):
assert _count_rows([1, 2, 3]) == 3
def test_dict_with_daily_key(self):
assert _count_rows({"daily": [1, 2], "other": "value"}) == 2
def test_dict_no_list_keys(self):
assert _count_rows({"key": "value"}) is None
def test_none(self):
assert _count_rows(None) is None
class TestIsTransientError:
def test_timeout_is_transient(self):
assert _is_transient_error(TimeoutError("timeout")) is True
def test_source_unavailable_is_transient(self):
assert _is_transient_error(SourceUnavailable("unavailable")) is True
def test_source_data_insufficient_not_transient(self):
assert _is_transient_error(SourceDataInsufficient("insufficient")) is False
def test_generic_exception_not_transient(self):
assert _is_transient_error(ValueError("bad")) is False
class TestBackoffSeconds:
def test_exponential_growth(self):
b1 = _backoff_seconds(1.0, 0.0, 10.0, 1)
b2 = _backoff_seconds(1.0, 0.0, 10.0, 2)
b3 = _backoff_seconds(1.0, 0.0, 10.0, 3)
assert b1 < b2 < b3
def test_capped_at_max(self):
delay = _backoff_seconds(1.0, 0.0, 5.0, 10)
assert delay <= 5.0
class TestRunSources:
def test_success_returns_data(self):
sources = [("test", lambda: {"daily": [1, 2, 3]})]
result, attempts = run_sources("test", sources, timeout_seconds=5, retry_attempts=1)
assert result.source == "test"
assert len(attempts) == 1
assert attempts[0]["ok"] is True
def test_min_rows_rejects_insufficient(self):
sources = [("test", lambda: {"daily": [1]})]
with pytest.raises(AllSourcesFailed):
run_sources("test", sources, timeout_seconds=5, retry_attempts=1, min_rows=5)
def test_fallback_on_failure(self):
def good():
return {"daily": [1, 2, 3]}
def bad():
raise ValueError("fail")
sources = [("bad", bad), ("good", good)]
result, attempts = run_sources("test", sources, timeout_seconds=5, retry_attempts=1)
assert result.source == "good"
assert len(attempts) == 2
assert attempts[0]["ok"] is False
assert attempts[1]["ok"] is True
def test_all_fail_raises(self):
def fail():
raise ValueError("fail")
sources = [("fail", fail)]
with pytest.raises(AllSourcesFailed):
run_sources("test", sources, timeout_seconds=5, retry_attempts=1)