"""Tests for /api/ticker/validate and /api/ticker/historical.""" from __future__ import annotations from datetime import datetime, timezone from io import BytesIO from types import SimpleNamespace from unittest.mock import AsyncMock import pytest def _build_session_factory(tmp_path): """Spin up a fresh in-memory schema and return (engine, factory, setup). Mirrors tests/test_llm_csv_parser.py / tests/test_referral_conversion.py.""" from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine from app import db as db_mod from app.db import Base import app.models # noqa: F401 engine = create_async_engine(f"sqlite+aiosqlite:///{tmp_path}/tv.db") factory = async_sessionmaker(engine, expire_on_commit=False) db_mod._engine = engine db_mod._session_factory = factory async def _setup(): async with engine.begin() as conn: await conn.run_sync(Base.metadata.create_all) return engine, factory, _setup @pytest.mark.asyncio async def test_validate_happy_path(tmp_path, monkeypatch): from app.routers.ticker_validate import validate_ticker from app.services.market import Quote import app.routers.ticker_validate as mod _, factory, setup = _build_session_factory(tmp_path) await setup() # Mock fetch_yahoo to return a successful quote. async def _fake_yahoo(client, symbol, label, note, anchor=None): return Quote( symbol=symbol, source="yahoo", label=label, note=note, price=172.40, currency="USD", as_of="2026-05-27", changes={}, ) monkeypatch.setattr(mod, "fetch_yahoo", _fake_yahoo) # Avoid the MySQL-only upsert on SQLite. async def _fake_upsert(session, tickers): return len(list(tickers)) monkeypatch.setattr(mod, "upsert_tickers", _fake_upsert) async with factory() as session: result = await validate_ticker(symbol="aapl", session=session) assert result["ok"] is True assert result["symbol"] == "AAPL" assert result["price"] == 172.40 assert result["currency"] == "USD" assert result["as_of"] == "2026-05-27"