TradingAgents/tradingagents/clients/test_finnhub_client.py

318 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
from tradingagents.clients.finnhub_client import FinnhubClient
@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)