"""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""