"""Unit tests for fundamental_service — the surviving read path. Writes to ``fundamental_data`` are covered by test_fundamental_data_refresh.py; this file only guards the lookup used by the router and scoring. """ from __future__ import annotations import json from datetime import datetime, timezone import pytest from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine from app.database import Base from app.exceptions import NotFoundError from app.models.fundamental import FundamentalData from app.models.ticker import Ticker from app.services import fundamental_service # Use a dedicated engine so commit/refresh work without conflicting # with the conftest transactional session. _engine = create_async_engine("sqlite+aiosqlite://", echo=False) _session_factory = async_sessionmaker(_engine, class_=AsyncSession, expire_on_commit=False) @pytest.fixture(autouse=True) async def _setup_tables(): async with _engine.begin() as conn: await conn.run_sync(Base.metadata.create_all) yield async with _engine.begin() as conn: await conn.run_sync(Base.metadata.drop_all) @pytest.fixture async def session() -> AsyncSession: async with _session_factory() as s: yield s @pytest.fixture async def ticker(session: AsyncSession) -> Ticker: """Create a test ticker.""" t = Ticker(symbol="AAPL") session.add(t) await session.commit() await session.refresh(t) return t @pytest.mark.asyncio async def test_get_fundamental_returns_the_cached_row( session: AsyncSession, ticker: Ticker ): fields = {"pe_ratio": "split guard applied"} session.add( FundamentalData( ticker_id=ticker.id, pe_ratio=None, market_cap=1_000_000.0, fetched_at=datetime.now(timezone.utc), unavailable_fields_json=json.dumps(fields), ) ) await session.commit() record = await fundamental_service.get_fundamental(session, symbol="aapl") assert record is not None assert record.market_cap == 1_000_000.0 assert json.loads(record.unavailable_fields_json) == fields @pytest.mark.asyncio async def test_get_fundamental_returns_none_without_a_cached_row( session: AsyncSession, ticker: Ticker ): assert await fundamental_service.get_fundamental(session, symbol="AAPL") is None @pytest.mark.asyncio async def test_get_fundamental_rejects_an_unknown_symbol(session: AsyncSession): with pytest.raises(NotFoundError): await fundamental_service.get_fundamental(session, symbol="NOPE")