Files
signal-platform/tests/unit/test_finnhub_provider.py
T
dennisthiessen 5a531fd603
Deploy / lint (push) Successful in 9s
Deploy / test (push) Successful in 1m10s
Deploy / deploy (push) Successful in 38s
fix: convert Finnhub market cap from millions to absolute USD
Finnhub profile2 reports marketCapitalization in millions; storing it
as dollars made mega-caps like SPCX show as micro (e.g. 1.8M). Normalize
on ingest, add unit tests, and include a one-shot SQL backfill script.
2026-07-18 10:28:16 +02:00

93 lines
3.2 KiB
Python

"""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