Files
stonks-oracle/tests/test_market_adapter.py
T

166 lines
5.5 KiB
Python

"""Tests for the Polygon.io market data adapter.
Validates request building, response parsing, and error handling.
"""
from services.adapters.market_adapter import MarketDataAdapter, PolygonMarketAdapter
# --- Fake Polygon responses ---
PREV_BARS_RESPONSE = {
"ticker": "AAPL",
"queryCount": 1,
"resultsCount": 1,
"adjusted": True,
"results": [
{
"T": "AAPL",
"v": 58_350_544,
"vw": 171.5322,
"o": 171.0,
"c": 172.28,
"h": 173.1,
"l": 170.5,
"t": 1712793600000,
"n": 620_123,
}
],
"status": "OK",
"request_id": "req-abc-123",
}
TICKER_DETAILS_RESPONSE = {
"results": {
"ticker": "AAPL",
"name": "Apple Inc.",
"market": "stocks",
"locale": "us",
"primary_exchange": "XNAS",
"type": "CS",
"currency_name": "usd",
"market_cap": 2_700_000_000_000,
},
"status": "OK",
"request_id": "req-def-456",
}
RANGE_BARS_RESPONSE = {
"ticker": "AAPL",
"queryCount": 3,
"resultsCount": 3,
"adjusted": True,
"results": [
{"T": "AAPL", "o": 170.0, "c": 171.0, "h": 172.0, "l": 169.5, "v": 50_000_000, "t": 1712620800000},
{"T": "AAPL", "o": 171.0, "c": 172.0, "h": 173.0, "l": 170.0, "v": 55_000_000, "t": 1712707200000},
{"T": "AAPL", "o": 172.0, "c": 172.5, "h": 174.0, "l": 171.0, "v": 48_000_000, "t": 1712793600000},
],
"status": "OK",
"request_id": "req-ghi-789",
}
class TestPolygonSourceType:
def test_source_type(self):
adapter = PolygonMarketAdapter(api_key="k")
assert adapter.source_type() == "market_api"
def test_inherits_market_data_adapter(self):
assert issubclass(PolygonMarketAdapter, MarketDataAdapter)
def test_bucket_name(self):
adapter = PolygonMarketAdapter(api_key="k")
assert adapter.bucket_name() == "stonks-raw-market"
class TestPolygonBuildRequest:
def setup_method(self):
self.adapter = PolygonMarketAdapter(api_key="test-key", base_url="https://api.polygon.io")
def test_prev_bars_default(self):
url, params = self.adapter._build_request("AAPL", "prev_bars", {})
assert url == "https://api.polygon.io/v2/aggs/ticker/AAPL/prev"
assert params["apiKey"] == "test-key"
def test_prev_bars_with_adjusted(self):
url, params = self.adapter._build_request("AAPL", "prev_bars", {"adjusted": False})
assert params["adjusted"] == "false"
def test_range_bars(self):
config = {
"multiplier": 1,
"timespan": "day",
"from_date": "2026-04-01",
"to_date": "2026-04-10",
"adjusted": True,
"limit": 50,
"sort": "asc",
}
url, params = self.adapter._build_request("AAPL", "range_bars", config)
assert "/v2/aggs/ticker/AAPL/range/1/day/2026-04-01/2026-04-10" in url
assert params["adjusted"] == "true"
assert params["limit"] == "50"
assert params["sort"] == "asc"
def test_ticker_details(self):
url, params = self.adapter._build_request("MSFT", "ticker_details", {})
assert url == "https://api.polygon.io/v3/reference/tickers/MSFT"
assert params["apiKey"] == "test-key"
def test_unknown_endpoint_defaults_to_prev(self):
url, _ = self.adapter._build_request("AAPL", "unknown_thing", {})
assert "/v2/aggs/ticker/AAPL/prev" in url
def test_trailing_slash_stripped(self):
adapter = PolygonMarketAdapter(api_key="k", base_url="https://api.polygon.io/")
url, _ = adapter._build_request("AAPL", "prev_bars", {})
assert "//v2" not in url
class TestPolygonExtractItems:
def setup_method(self):
self.adapter = PolygonMarketAdapter(api_key="k")
def test_extract_prev_bars(self):
items = self.adapter._extract_items(PREV_BARS_RESPONSE, "prev_bars")
assert len(items) == 1
assert items[0]["T"] == "AAPL"
def test_extract_range_bars(self):
items = self.adapter._extract_items(RANGE_BARS_RESPONSE, "range_bars")
assert len(items) == 3
def test_extract_ticker_details(self):
items = self.adapter._extract_items(TICKER_DETAILS_RESPONSE, "ticker_details")
assert len(items) == 1
assert items[0]["ticker"] == "AAPL"
def test_extract_empty_results_list(self):
items = self.adapter._extract_items({"results": [], "status": "OK"}, "prev_bars")
assert items == []
def test_extract_missing_results_key(self):
items = self.adapter._extract_items({"status": "OK"}, "prev_bars")
assert items == []
def test_extract_ticker_details_empty(self):
items = self.adapter._extract_items({"results": {}, "status": "OK"}, "ticker_details")
assert items == []
class TestPolygonErrorResult:
def test_error_result_fields(self):
adapter = PolygonMarketAdapter(api_key="k")
result = adapter._error_result("AAPL", "something broke", 42.5, http_status=500, raw=b"err")
assert not result.ok
assert result.error == "something broke"
assert result.http_status == 500
assert result.response_time_ms == 42.5
assert result.raw_payload == b"err"
assert result.metadata["provider"] == "polygon"
def test_error_result_defaults(self):
adapter = PolygonMarketAdapter(api_key="k")
result = adapter._error_result("AAPL", "timeout", 100.0)
assert result.http_status is None
assert result.raw_payload == b""