"""Unit tests for FinnhubFundamentalProvider unit conversions.""" from __future__ import annotations from unittest.mock import AsyncMock, patch import httpx import pytest from app.providers.fundamentals_chain import FinnhubFundamentalProvider def _mock_response(status_code: int, json_data: object = None) -> httpx.Response: return httpx.Response( status_code=status_code, json=json_data if json_data is not None else {}, request=httpx.Request("GET", "https://example.com"), ) @pytest.fixture def provider() -> FinnhubFundamentalProvider: return FinnhubFundamentalProvider(api_key="test-key") @pytest.mark.asyncio async def test_finnhub_market_cap_converted_from_millions_to_dollars(provider): """Finnhub marketCapitalization is in millions — store absolute USD. SPCX-scale example: ~$1.8T → Finnhub reports 1_800_000 (millions). Without conversion the UI showed 1.8M / micro cap. """ profile = {"marketCapitalization": 1_800_000} # millions → $1.8T metrics = {"metric": {"peTTM": 40.0, "revenueGrowthTTMYoy": 25.0}} earnings = [{"surprisePercent": 2.5}] calendar = {"earningsCalendar": []} async def mock_get(url, params=None): if "profile2" in url: return _mock_response(200, profile) if "stock/metric" in url: return _mock_response(200, metrics) if "stock/earnings" in url: return _mock_response(200, earnings) if "calendar/earnings" in url: return _mock_response(200, calendar) return _mock_response(200, {}) with patch("app.providers.fundamentals_chain.httpx.AsyncClient") as MockClient: instance = AsyncMock() instance.get.side_effect = mock_get instance.__aenter__ = AsyncMock(return_value=instance) instance.__aexit__ = AsyncMock(return_value=False) MockClient.return_value = instance result = await provider.fetch_fundamentals("SPCX") assert result.market_cap == 1_800_000 * 1_000_000 # $1.8T assert result.pe_ratio == 40.0 assert result.revenue_growth == 25.0 assert result.earnings_surprise == 2.5 @pytest.mark.asyncio async def test_finnhub_market_cap_none_when_missing(provider): profile: dict = {} metrics = {"metric": {}} earnings: list = [] calendar = {"earningsCalendar": []} async def mock_get(url, params=None): if "profile2" in url: return _mock_response(200, profile) if "stock/metric" in url: return _mock_response(200, metrics) if "stock/earnings" in url: return _mock_response(200, earnings) if "calendar/earnings" in url: return _mock_response(200, calendar) return _mock_response(200, {}) with patch("app.providers.fundamentals_chain.httpx.AsyncClient") as MockClient: instance = AsyncMock() instance.get.side_effect = mock_get instance.__aenter__ = AsyncMock(return_value=instance) instance.__aexit__ = AsyncMock(return_value=False) MockClient.return_value = instance result = await provider.fetch_fundamentals("XYZ") assert result.market_cap is None assert "market_cap" in result.unavailable_fields