ticker-validate: add /api/ticker/historical with weekend-walkback
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
ca953e5ea2
commit
b7d6235fcb
2 changed files with 185 additions and 0 deletions
|
|
@ -135,3 +135,110 @@ async def test_validate_seeds_universe_and_quote(tmp_path, monkeypatch):
|
|||
assert len(rows) == 1
|
||||
assert rows[0].price == 100.0
|
||||
assert rows[0].currency == "USD"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_historical_happy_path(monkeypatch):
|
||||
from app.routers.ticker_validate import get_historical
|
||||
import app.routers.ticker_validate as mod
|
||||
|
||||
async def _fake_hist(client, symbol, target_iso):
|
||||
# close, currency, actual_iso
|
||||
return 185.92, "USD", "2024-01-12"
|
||||
monkeypatch.setattr(mod, "fetch_yahoo_historical", _fake_hist)
|
||||
|
||||
result = await get_historical(symbol="aapl", date="2024-01-15")
|
||||
|
||||
assert result["ok"] is True
|
||||
assert result["close"] == 185.92
|
||||
assert result["currency"] == "USD"
|
||||
# 2024-01-15 was a Monday — but our fake says it walked back to Jan 12 (Fri).
|
||||
assert result["actual_date"] == "2024-01-12"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_historical_future_date_rejected():
|
||||
from fastapi import HTTPException
|
||||
from app.routers.ticker_validate import get_historical
|
||||
|
||||
future = "2099-01-01"
|
||||
with pytest.raises(HTTPException) as exc:
|
||||
await get_historical(symbol="AAPL", date=future)
|
||||
assert exc.value.status_code == 400
|
||||
assert "future" in str(exc.value.detail).lower()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_historical_bad_date_format_rejected():
|
||||
from fastapi import HTTPException
|
||||
from app.routers.ticker_validate import get_historical
|
||||
|
||||
with pytest.raises(HTTPException) as exc:
|
||||
await get_historical(symbol="AAPL", date="not-a-date")
|
||||
assert exc.value.status_code == 400
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_historical_no_data(monkeypatch):
|
||||
from app.routers.ticker_validate import get_historical
|
||||
import app.routers.ticker_validate as mod
|
||||
|
||||
async def _fake_hist(client, symbol, target_iso):
|
||||
return None, None, None
|
||||
monkeypatch.setattr(mod, "fetch_yahoo_historical", _fake_hist)
|
||||
|
||||
result = await get_historical(symbol="ZZZNEW", date="2020-01-15")
|
||||
assert result["ok"] is False
|
||||
assert "no data" in result["error"].lower()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_historical_provider_failure(monkeypatch):
|
||||
import httpx
|
||||
|
||||
from app.routers.ticker_validate import get_historical
|
||||
import app.routers.ticker_validate as mod
|
||||
|
||||
async def _fake_hist(client, symbol, target_iso):
|
||||
raise httpx.RequestError("connection failed")
|
||||
monkeypatch.setattr(mod, "fetch_yahoo_historical", _fake_hist)
|
||||
|
||||
result = await get_historical(symbol="AAPL", date="2024-01-15")
|
||||
assert result["ok"] is False
|
||||
assert "couldn" in result["error"].lower() or "fetch" in result["error"].lower()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_fetch_yahoo_historical_walks_back_to_preceding_trading_day(monkeypatch):
|
||||
"""Unit test for the helper itself: feed a hand-crafted series with a
|
||||
weekend gap, ask for the Saturday close, expect Friday's close."""
|
||||
from app.routers.ticker_validate import fetch_yahoo_historical
|
||||
|
||||
# Build a fake httpx client that returns a chart payload with a
|
||||
# Thu-Fri-Mon-Tue series; we ask for Saturday and expect Friday.
|
||||
thu_ts = int(datetime(2024, 1, 11, tzinfo=timezone.utc).timestamp())
|
||||
fri_ts = int(datetime(2024, 1, 12, tzinfo=timezone.utc).timestamp())
|
||||
mon_ts = int(datetime(2024, 1, 15, tzinfo=timezone.utc).timestamp())
|
||||
payload = {
|
||||
"chart": {"result": [{
|
||||
"meta": {"currency": "USD"},
|
||||
"timestamp": [thu_ts, fri_ts, mon_ts],
|
||||
"indicators": {"quote": [{"close": [184.0, 185.92, 186.10]}]},
|
||||
}]}
|
||||
}
|
||||
|
||||
class _FakeResponse:
|
||||
def __init__(self, data): self._data = data
|
||||
def json(self): return self._data
|
||||
def raise_for_status(self): pass
|
||||
|
||||
class _FakeClient:
|
||||
async def get(self, *args, **kwargs):
|
||||
return _FakeResponse(payload)
|
||||
|
||||
close, currency, actual = await fetch_yahoo_historical(
|
||||
_FakeClient(), "AAPL", "2024-01-13", # a Saturday
|
||||
)
|
||||
assert close == 185.92
|
||||
assert currency == "USD"
|
||||
assert actual == "2024-01-12"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue