"""Unit tests for Oracle client using pytest-httpx.""" import json from pathlib import Path import pytest from pytest_httpx import HTTPXMock FIXTURES_DIR = Path(__file__).parent.parent / "fixtures" def load_fixture(name: str) -> dict: return json.loads((FIXTURES_DIR / name).read_text()) @pytest.mark.asyncio async def test_search_filings(httpx_mock: HTTPXMock): from libs.oracle_client.client import OracleClient from libs.oracle_client.filings import FilingsService data = load_fixture("filing_search.json") httpx_mock.add_response(json=data, url="http://oracle:18001/api/v1/filings/search/AAPL") async with OracleClient("http://oracle:18001") as client: svc = FilingsService(client) result = await svc.search_filings("AAPL") assert result.ticker == "AAPL" assert len(result.filings) == 2 assert result.filings[0].form_type == "8-K" @pytest.mark.asyncio async def test_get_exhibit(httpx_mock: HTTPXMock): from libs.oracle_client.client import OracleClient from libs.oracle_client.filings import FilingsService data = load_fixture("exhibit_content.json") httpx_mock.add_response(json=data) async with OracleClient("http://oracle:18001") as client: svc = FilingsService(client) result = await svc.get_exhibit("0000320193-26-000001") assert result.accession_no == "0000320193-26-000001" assert len(result.content) > 0 @pytest.mark.asyncio async def test_get_daily_bars(httpx_mock: HTTPXMock): from libs.oracle_client.client import OracleClient from libs.oracle_client.price import PriceService data = load_fixture("price_data.json") httpx_mock.add_response(json=data) async with OracleClient("http://oracle:18001") as client: svc = PriceService(client) result = await svc.get_daily_bars("AAPL") assert result.ticker == "AAPL" assert len(result.bars) > 0 assert result.bars[0].close > 0 @pytest.mark.asyncio async def test_not_found_raises_oracle_not_found(httpx_mock: HTTPXMock): from libs.oracle_client.client import OracleClient from libs.oracle_client.exceptions import OracleNotFoundError from libs.oracle_client.filings import FilingsService httpx_mock.add_response(status_code=404) async with OracleClient("http://oracle:18001") as client: svc = FilingsService(client) with pytest.raises(OracleNotFoundError): await svc.get_exhibit("NONEXISTENT") @pytest.mark.asyncio async def test_server_error_raises_oracle_server_error(httpx_mock: HTTPXMock): from libs.oracle_client.client import OracleClient from libs.oracle_client.exceptions import OracleServerError from libs.oracle_client.filings import FilingsService # Must provide one response per retry attempt (3 total) httpx_mock.add_response(status_code=500) httpx_mock.add_response(status_code=500) httpx_mock.add_response(status_code=500) async with OracleClient("http://oracle:18001") as client: svc = FilingsService(client) with pytest.raises(OracleServerError): await svc.get_exhibit("ACC123") @pytest.mark.asyncio async def test_get_retries_on_transient_error_then_succeeds(httpx_mock: HTTPXMock): from libs.oracle_client.client import OracleClient from libs.oracle_client.filings import FilingsService data = load_fixture("exhibit_content.json") httpx_mock.add_response(status_code=500) # attempt 1 fails httpx_mock.add_response(status_code=500) # attempt 2 fails httpx_mock.add_response(json=data) # attempt 3 succeeds async with OracleClient("http://oracle:18001") as client: svc = FilingsService(client) result = await svc.get_exhibit("0000320193-26-000001") assert result.accession_no == "0000320193-26-000001" @pytest.mark.asyncio async def test_get_short_volume(httpx_mock: HTTPXMock): from libs.oracle_client.client import OracleClient from libs.oracle_client.finra import FinraService data = load_fixture("short_volume.json") httpx_mock.add_response(json=data) async with OracleClient("http://oracle:18001") as client: svc = FinraService(client) result = await svc.get_short_volume("AAPL") assert result.symbol == "AAPL" assert len(result.data) == 3 @pytest.mark.asyncio async def test_get_dividend_history(httpx_mock: HTTPXMock): from libs.oracle_client.client import OracleClient from libs.oracle_client.dividends import DividendService httpx_mock.add_response( json={ "symbol": "AAPL", "dividends": [ { "ticker": "AAPL", "ex_dividend_date": "2025-02-10", "amount": 0.25, "declaration_date": None, "record_date": None, "payment_date": None, "currency": "USD", "dividend_type": "regular", "frequency": "quarterly", "as_of_date": "2025-01-11", "source": "yfinance", } ], "total_count": 1, "annual_yield_estimate": 1.0, "metadata": {"note": "ok"}, } ) async with OracleClient("http://oracle:18001") as client: svc = DividendService(client) result = await svc.get_history("AAPL") assert result.symbol == "AAPL" assert result.total_count == 1 assert result.dividends[0].ticker == "AAPL" assert result.dividends[0].ex_dividend_date == "2025-02-10" @pytest.mark.asyncio async def test_get_upcoming_dividends(httpx_mock: HTTPXMock): from libs.oracle_client.client import OracleClient from libs.oracle_client.dividends import DividendService httpx_mock.add_response( json={ "dividends": [ { "ticker": "AAPL", "ex_dividend_date": "2025-02-10", "amount": 0.25, "declaration_date": None, "record_date": None, "payment_date": None, "currency": "USD", "dividend_type": "regular", "frequency": "quarterly", "as_of_date": "2025-01-11", "source": "yfinance", } ], "total_count": 1, "metadata": {"as_of_date": "2025-02-07"}, } ) async with OracleClient("http://oracle:18001") as client: svc = DividendService(client) result = await svc.get_upcoming( as_of_date="2025-02-07", from_ex_date="2025-02-10", to_ex_date="2025-02-10", symbols=["AAPL", "MSFT"], ) assert result.total_count == 1 assert result.dividends[0].ticker == "AAPL" assert result.metadata["as_of_date"] == "2025-02-07" @pytest.mark.asyncio async def test_get_financial_data(httpx_mock: HTTPXMock): from libs.oracle_client.client import OracleClient from libs.oracle_client.financial import FinancialService data = load_fixture("financial_data.json") httpx_mock.add_response(json=data) async with OracleClient("http://oracle:18001") as client: svc = FinancialService(client) result = await svc.get_financial_data("AAPL") assert result.ticker == "AAPL" assert len(result.periods) == 2 @pytest.mark.asyncio async def test_get_company_info(httpx_mock: HTTPXMock): from libs.oracle_client.client import OracleClient from libs.oracle_client.financial import FinancialService data = load_fixture("financial_data.json") httpx_mock.add_response(json=data) async with OracleClient("http://oracle:18001") as client: svc = FinancialService(client) info = await svc.get_company_info("AAPL") assert info.ticker == "AAPL" assert info.name == "Apple Inc." assert info.cik == "0000320193" assert info.sector == "Technology" assert info.exchange == "NASDAQ" assert info.country == "US" assert info.market_cap == 3500000000000.0 @pytest.mark.asyncio async def test_connection_error_raises_oracle_connection_error(httpx_mock: HTTPXMock): """ConnectError 시 OracleConnectionError가 발생한다 (3회 재시도 후).""" import httpx from libs.oracle_client.client import OracleClient from libs.oracle_client.exceptions import OracleConnectionError # 3번 모두 ConnectError (max_attempts=3) httpx_mock.add_exception(httpx.ConnectError("connection refused")) httpx_mock.add_exception(httpx.ConnectError("connection refused")) httpx_mock.add_exception(httpx.ConnectError("connection refused")) async with OracleClient("http://oracle:18001") as client: with pytest.raises(OracleConnectionError): await client.get("/health") @pytest.mark.asyncio async def test_timeout_raises_oracle_timeout_error(httpx_mock: HTTPXMock): """ReadTimeout 시 OracleTimeoutError가 발생한다 (3회 재시도 후).""" import httpx from libs.oracle_client.client import OracleClient from libs.oracle_client.exceptions import OracleTimeoutError httpx_mock.add_exception(httpx.ReadTimeout("read timeout")) httpx_mock.add_exception(httpx.ReadTimeout("read timeout")) httpx_mock.add_exception(httpx.ReadTimeout("read timeout")) async with OracleClient("http://oracle:18001") as client: with pytest.raises(OracleTimeoutError): await client.get("/health") @pytest.mark.asyncio async def test_client_without_context_manager_raises(): """context manager 없이 get() 호출 시 RuntimeError가 발생한다.""" from libs.oracle_client.client import OracleClient client = OracleClient("http://oracle:18001") with pytest.raises(RuntimeError, match="async context manager"): await client.get("/health") @pytest.mark.asyncio async def test_get_attention_entity(httpx_mock: HTTPXMock): from libs.oracle_client.attention import AttentionService from libs.oracle_client.client import OracleClient data = load_fixture("attention_entity.json") httpx_mock.add_response( json=data, url="http://oracle:18001/api/v1/attention/entity/AAPL", ) async with OracleClient("http://oracle:18001") as client: svc = AttentionService(client) result = await svc.get_entity("AAPL") assert result.ticker == "AAPL" assert result.entity.canonical_name == "Apple" assert result.entity.wiki_title == "Apple Inc." assert result.status == "exists" @pytest.mark.asyncio async def test_get_event_attention(httpx_mock: HTTPXMock): import datetime as dt from libs.oracle_client.attention import AttentionService from libs.oracle_client.client import OracleClient data = load_fixture("attention_event.json") httpx_mock.add_response( json=data, url="http://oracle:18001/api/v1/attention/event/AAPL?event_date=2024-02-01", ) async with OracleClient("http://oracle:18001") as client: svc = AttentionService(client) result = await svc.get_event_attention("AAPL", dt.date(2024, 2, 1)) assert result.ticker == "AAPL" assert result.event_date == "2024-02-01" assert result.wiki.views == 28837 assert result.news.gdelt_status == "not_collected" @pytest.mark.asyncio async def test_resolve_attention_entity(httpx_mock: HTTPXMock): from libs.oracle_client.attention import AttentionService from libs.oracle_client.client import OracleClient data = load_fixture("attention_entity.json") data["status"] = "resolved" data["message"] = "Entity resolved: wiki_title='Apple Inc.' confidence=0.95" httpx_mock.add_response( json=data, method="POST", url="http://oracle:18001/api/v1/attention/admin/resolve/AAPL", ) async with OracleClient("http://oracle:18001") as client: svc = AttentionService(client) result = await svc.resolve_entity("AAPL") assert result.status == "resolved" assert result.entity.resolver_confidence == 0.95 @pytest.mark.asyncio async def test_collect_attention_wiki(httpx_mock: HTTPXMock): from libs.oracle_client.attention import AttentionService from libs.oracle_client.client import OracleClient data = load_fixture("attention_collect.json") httpx_mock.add_response( json=data, method="POST", url="http://oracle:18001/api/v1/attention/admin/collect/wiki/AAPL?event_date=2024-02-01", ) async with OracleClient("http://oracle:18001") as client: svc = AttentionService(client) result = await svc.collect_wiki("AAPL", "2024-02-01") assert result.ticker == "AAPL" assert result.source == "wiki" assert result.records_collected == 0 @pytest.mark.asyncio async def test_collect_attention_gdelt(httpx_mock: HTTPXMock): from libs.oracle_client.attention import AttentionService from libs.oracle_client.client import OracleClient data = load_fixture("attention_collect.json") data["source"] = "gdelt" data["records_collected"] = 12 httpx_mock.add_response( json=data, method="POST", url="http://oracle:18001/api/v1/attention/admin/collect/gdelt/AAPL?event_date=2024-02-01", ) async with OracleClient("http://oracle:18001") as client: svc = AttentionService(client) result = await svc.collect_gdelt("AAPL", "2024-02-01") assert result.source == "gdelt" assert result.records_collected == 12