166 lines
5.5 KiB
Python
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""
|