TradingAgents/tradingagents/domains/marketdata/clients/finnhub_client_test.py

321 lines
11 KiB
Python

#!/usr/bin/env python3
"""
Comprehensive tests for FinnhubClient using pytest-vcr.
This test suite records real API interactions with Finnhub and replays them
in subsequent runs, providing realistic testing without network dependencies.
"""
import os
from datetime import date
import pytest
# NOTE: FinnhubClient was removed - this test needs to be updated
# from tradingagents.clients.finnhub_client import FinnhubClient
pytest.skip("FinnhubClient implementation removed", allow_module_level=True)
@pytest.fixture
def finnhub_client():
"""Create FinnhubClient with test API key."""
# Use environment variable or test key for VCR recording
api_key = os.getenv("FINNHUB_API_KEY", "test_api_key")
return FinnhubClient(api_key=api_key)
@pytest.fixture
def vcr_config():
"""Configure VCR with proper settings."""
return {
"filter_headers": ["X-Finnhub-Token"], # Filter out API key from recordings
"record_mode": "once", # Record once, then replay
"match_on": ["uri", "method"],
"decode_compressed_response": True,
}
class TestFinnhubClientConnection:
"""Test client connection and initialization."""
@pytest.mark.vcr
def test_client_initialization(self, finnhub_client):
"""Test that client initializes correctly."""
assert finnhub_client.api_key is not None
assert finnhub_client.client is not None
@pytest.mark.vcr
def test_connection_success(self, finnhub_client):
"""Test successful API connection."""
assert finnhub_client.test_connection() is True
def test_client_info(self, finnhub_client):
"""Test client metadata."""
info = finnhub_client.get_client_info()
assert info["client_type"] == "FinnhubClient"
assert info["api_key_set"] is True
class TestFundamentalDataMethods:
"""Test the fundamental data methods needed by FundamentalDataService."""
@pytest.mark.vcr
def test_get_balance_sheet_quarterly(self, finnhub_client):
"""Test balance sheet retrieval with date object."""
test_date = date(2024, 1, 1)
data = finnhub_client.get_balance_sheet("AAPL", "quarterly", test_date)
assert isinstance(data, dict)
# Finnhub financials_reported returns data structure with 'data' key
assert "data" in data or len(data) > 0
@pytest.mark.vcr
def test_get_balance_sheet_annual(self, finnhub_client):
"""Test annual balance sheet retrieval."""
test_date = date(2024, 1, 1)
data = finnhub_client.get_balance_sheet("AAPL", "annual", test_date)
assert isinstance(data, dict)
@pytest.mark.vcr
def test_get_income_statement_quarterly(self, finnhub_client):
"""Test income statement retrieval."""
test_date = date(2024, 1, 1)
data = finnhub_client.get_income_statement("AAPL", "quarterly", test_date)
assert isinstance(data, dict)
@pytest.mark.vcr
def test_get_income_statement_annual(self, finnhub_client):
"""Test annual income statement retrieval."""
test_date = date(2024, 1, 1)
data = finnhub_client.get_income_statement("AAPL", "annual", test_date)
assert isinstance(data, dict)
@pytest.mark.vcr
def test_get_cash_flow_with_date_object(self, finnhub_client):
"""Test cash flow retrieval with date object."""
test_date = date(2024, 3, 31)
data = finnhub_client.get_cash_flow("AAPL", "quarterly", test_date)
assert isinstance(data, dict)
@pytest.mark.vcr
def test_fundamental_data_different_symbols(self, finnhub_client):
"""Test fundamental data for different symbols."""
symbols = ["AAPL", "MSFT", "GOOGL"]
test_date = date(2024, 1, 1)
for symbol in symbols:
data = finnhub_client.get_balance_sheet(symbol, "quarterly", test_date)
assert isinstance(data, dict)
class TestExistingMethods:
"""Test existing methods with enhanced date support."""
@pytest.mark.vcr
def test_get_company_news_january(self, finnhub_client):
"""Test company news for January 2024."""
start_date = date(2024, 1, 1)
end_date = date(2024, 1, 31)
news = finnhub_client.get_company_news("AAPL", start_date, end_date)
assert isinstance(news, list)
@pytest.mark.vcr
def test_get_company_news_date_objects(self, finnhub_client):
"""Test company news with date objects."""
start_date = date(2024, 1, 1)
end_date = date(2024, 1, 31)
news = finnhub_client.get_company_news("AAPL", start_date, end_date)
assert isinstance(news, list)
@pytest.mark.vcr
def test_get_insider_transactions_date_objects(self, finnhub_client):
"""Test insider transactions with date objects."""
start_date = date(2024, 1, 1)
end_date = date(2024, 1, 31)
data = finnhub_client.get_insider_transactions("AAPL", start_date, end_date)
assert isinstance(data, dict)
assert "data" in data
@pytest.mark.vcr
def test_get_insider_sentiment(self, finnhub_client):
"""Test insider sentiment with date objects."""
start_date = date(2024, 1, 1)
end_date = date(2024, 1, 31)
data = finnhub_client.get_insider_sentiment("AAPL", start_date, end_date)
assert isinstance(data, dict)
@pytest.mark.vcr
def test_get_quote(self, finnhub_client):
"""Test stock quote retrieval."""
quote = finnhub_client.get_quote("AAPL")
assert isinstance(quote, dict)
# Quote should contain current price 'c' field
if quote: # Only check if we got data
assert "c" in quote
@pytest.mark.vcr
def test_get_company_profile(self, finnhub_client):
"""Test company profile retrieval."""
profile = finnhub_client.get_company_profile("AAPL")
assert isinstance(profile, dict)
class TestErrorHandling:
"""Test error handling and edge cases."""
@pytest.mark.vcr
def test_invalid_symbol_balance_sheet(self, finnhub_client):
"""Test balance sheet with invalid symbol."""
test_date = date(2024, 1, 1)
data = finnhub_client.get_balance_sheet(
"INVALID_SYMBOL_XYZ", "quarterly", test_date
)
# Should return dict with empty data structure, not raise exception
assert isinstance(data, dict)
assert "data" in data
@pytest.mark.vcr
def test_invalid_symbol_news(self, finnhub_client):
"""Test news with invalid symbol."""
start_date = date(2024, 1, 1)
end_date = date(2024, 1, 31)
news = finnhub_client.get_company_news(
"INVALID_SYMBOL_XYZ", start_date, end_date
)
# Should return empty list, not raise exception
assert isinstance(news, list)
def test_connection_with_invalid_api_key(self):
"""Test connection failure with invalid API key."""
client = FinnhubClient("invalid_api_key")
# This should return False, not raise an exception
assert client.test_connection() is False
@pytest.mark.vcr
def test_frequency_normalization(self, finnhub_client):
"""Test that frequency parameters are normalized correctly."""
# Test different frequency formats
frequencies = ["quarterly", "QUARTERLY", "q", "Q", "annual", "ANNUAL", "a", "A"]
test_date = date(2024, 1, 1)
for freq in frequencies:
data = finnhub_client.get_balance_sheet("AAPL", freq, test_date)
assert isinstance(data, dict)
def test_date_edge_cases(self, finnhub_client):
"""Test date edge cases."""
# Test with year-end date
test_date = date(2024, 12, 31)
# This shouldn't raise exceptions
# (actual API calls are mocked/recorded)
data = finnhub_client.get_balance_sheet("AAPL", "quarterly", test_date)
assert isinstance(data, dict)
class TestMultipleSymbolsAndTimeframes:
"""Test with multiple symbols and different timeframes."""
@pytest.mark.vcr
def test_multiple_symbols_balance_sheet(self, finnhub_client):
"""Test balance sheet for multiple major symbols."""
symbols = ["AAPL", "MSFT", "GOOGL", "TSLA"]
test_date = date(2024, 1, 1)
for symbol in symbols:
data = finnhub_client.get_balance_sheet(symbol, "quarterly", test_date)
assert isinstance(data, dict)
@pytest.mark.vcr
def test_quarterly_vs_annual_frequency(self, finnhub_client):
"""Test quarterly vs annual frequency."""
symbol = "AAPL"
test_date = date(2024, 1, 1)
quarterly_data = finnhub_client.get_balance_sheet(
symbol, "quarterly", test_date
)
annual_data = finnhub_client.get_balance_sheet(symbol, "annual", test_date)
assert isinstance(quarterly_data, dict)
assert isinstance(annual_data, dict)
@pytest.mark.vcr
def test_all_fundamental_methods_same_symbol(self, finnhub_client):
"""Test all fundamental methods for the same symbol."""
symbol = "AAPL"
frequency = "quarterly"
report_date = date(2024, 1, 1)
balance_sheet = finnhub_client.get_balance_sheet(symbol, frequency, report_date)
income_statement = finnhub_client.get_income_statement(
symbol, frequency, report_date
)
cash_flow = finnhub_client.get_cash_flow(symbol, frequency, report_date)
assert isinstance(balance_sheet, dict)
assert isinstance(income_statement, dict)
assert isinstance(cash_flow, dict)
# Integration test to verify the client works with service expectations
class TestServiceIntegration:
"""Test that client works as expected by FundamentalDataService."""
def test_service_expected_methods_exist(self, finnhub_client):
"""Test that all methods expected by FundamentalDataService exist."""
# These are the methods the service calls
assert hasattr(finnhub_client, "get_balance_sheet")
assert hasattr(finnhub_client, "get_income_statement")
assert hasattr(finnhub_client, "get_cash_flow")
# Verify method signatures accept the expected parameters
import inspect
for method_name in [
"get_balance_sheet",
"get_income_statement",
"get_cash_flow",
]:
method = getattr(finnhub_client, method_name)
sig = inspect.signature(method)
params = list(sig.parameters.keys())
# Service calls: method(symbol, frequency, date)
assert "symbol" in params
assert "frequency" in params
assert "report_date" in params
@pytest.mark.vcr
def test_data_format_for_service_conversion(self, finnhub_client):
"""Test that returned data format can be processed by service conversion method."""
test_date = date(2024, 1, 1)
data = finnhub_client.get_balance_sheet("AAPL", "quarterly", test_date)
# Service expects either empty dict or dict with data
assert isinstance(data, dict)
# The service's _convert_to_financial_statement expects either:
# 1. Empty/falsy data -> returns None
# 2. Dict with "data" key containing the actual financial data
if data: # If we got data
# Should be able to access data["data"] or similar structure
# This validates the format is compatible with service expectations
assert isinstance(data, dict)