"""Tests for the Polygon.io news adapter. Validates request building, response parsing, and error handling. """ from services.adapters.news_adapter import NewsDataAdapter, PolygonNewsAdapter # --- Fake Polygon news responses --- NEWS_RESPONSE = { "results": [ { "id": "abc123", "publisher": {"name": "Reuters", "homepage_url": "https://reuters.com"}, "title": "Apple Reports Record Revenue", "article_url": "https://reuters.com/apple-record", "tickers": ["AAPL"], "published_utc": "2026-04-10T14:30:00Z", "description": "Apple Inc. reported record quarterly revenue.", "keywords": ["earnings", "apple", "revenue"], }, { "id": "def456", "publisher": {"name": "Bloomberg", "homepage_url": "https://bloomberg.com"}, "title": "Apple Supply Chain Update", "article_url": "https://bloomberg.com/apple-supply", "tickers": ["AAPL", "TSM"], "published_utc": "2026-04-10T12:00:00Z", "description": "Supply chain adjustments for upcoming product cycle.", "keywords": ["supply_chain", "apple"], }, ], "status": "OK", "request_id": "req-news-001", "count": 2, "next_url": "https://api.polygon.io/v2/reference/news?cursor=abc", } EMPTY_NEWS_RESPONSE = { "results": [], "status": "OK", "request_id": "req-news-002", "count": 0, } class TestPolygonNewsSourceType: def test_source_type(self): adapter = PolygonNewsAdapter(api_key="k") assert adapter.source_type() == "news_api" def test_inherits_news_data_adapter(self): assert issubclass(PolygonNewsAdapter, NewsDataAdapter) def test_bucket_name(self): adapter = PolygonNewsAdapter(api_key="k") assert adapter.bucket_name() == "stonks-raw-news" class TestPolygonNewsBuildRequest: def setup_method(self): self.adapter = PolygonNewsAdapter(api_key="test-key", base_url="https://api.polygon.io") def test_default_request(self): url, params = self.adapter._build_request("AAPL", {}) assert url == "https://api.polygon.io/v2/reference/news" assert params["apiKey"] == "test-key" assert params["ticker"] == "AAPL" assert params["limit"] == "20" def test_custom_limit(self): _, params = self.adapter._build_request("AAPL", {"limit": 50}) assert params["limit"] == "50" def test_limit_capped_at_1000(self): _, params = self.adapter._build_request("AAPL", {"limit": 5000}) assert params["limit"] == "1000" def test_order_param(self): _, params = self.adapter._build_request("AAPL", {"order": "asc"}) assert params["order"] == "asc" def test_date_filters(self): config = { "published_utc_gte": "2026-04-01", "published_utc_lte": "2026-04-10", } _, params = self.adapter._build_request("AAPL", config) assert params["published_utc.gte"] == "2026-04-01" assert params["published_utc.lte"] == "2026-04-10" def test_no_date_filters_when_absent(self): _, params = self.adapter._build_request("AAPL", {}) assert "published_utc.gte" not in params assert "published_utc.lte" not in params def test_trailing_slash_stripped(self): adapter = PolygonNewsAdapter(api_key="k", base_url="https://api.polygon.io/") url, _ = adapter._build_request("AAPL", {}) assert "//v2" not in url class TestPolygonNewsExtractItems: def setup_method(self): self.adapter = PolygonNewsAdapter(api_key="k") def test_extract_articles(self): items = self.adapter._extract_items(NEWS_RESPONSE) assert len(items) == 2 assert items[0]["title"] == "Apple Reports Record Revenue" assert items[1]["tickers"] == ["AAPL", "TSM"] def test_extract_empty_results(self): items = self.adapter._extract_items(EMPTY_NEWS_RESPONSE) assert items == [] def test_extract_missing_results_key(self): items = self.adapter._extract_items({"status": "OK"}) assert items == [] def test_extract_non_list_results(self): items = self.adapter._extract_items({"results": "unexpected"}) assert items == [] class TestPolygonNewsErrorResult: def test_error_result_fields(self): adapter = PolygonNewsAdapter(api_key="k") result = adapter._error_result("AAPL", "rate limited", 150.0, http_status=429, raw=b"slow down") assert not result.ok assert result.error == "rate limited" assert result.http_status == 429 assert result.response_time_ms == 150.0 assert result.raw_payload == b"slow down" assert result.metadata["provider"] == "polygon" assert result.source_type == "news_api" def test_error_result_defaults(self): adapter = PolygonNewsAdapter(api_key="k") result = adapter._error_result("MSFT", "timeout", 200.0) assert result.http_status is None assert result.raw_payload == b"" assert result.ticker == "MSFT"