"""Tests for the new social sentiment functions in finnhub_service.""" from unittest.mock import patch, AsyncMock, MagicMock import pytest import finnhub_service # --- get_social_sentiment --- @pytest.mark.asyncio @patch("finnhub_service.settings") async def test_social_sentiment_not_configured(mock_settings): mock_settings.finnhub_api_key = "" result = await finnhub_service.get_social_sentiment("AAPL") assert result["configured"] is False assert "INVEST_API_FINNHUB_API_KEY" in result["message"] @pytest.mark.asyncio @patch("finnhub_service.settings") @patch("finnhub_service.httpx.AsyncClient") async def test_social_sentiment_premium_required_403(mock_client_cls, mock_settings): mock_settings.finnhub_api_key = "test_key" mock_resp = MagicMock() mock_resp.status_code = 403 mock_client = AsyncMock() mock_client.get.return_value = mock_resp mock_client.__aenter__ = AsyncMock(return_value=mock_client) mock_client.__aexit__ = AsyncMock(return_value=False) mock_client_cls.return_value = mock_client result = await finnhub_service.get_social_sentiment("AAPL") assert result["configured"] is True assert result["premium_required"] is True assert result["reddit"] == [] assert result["twitter"] == [] @pytest.mark.asyncio @patch("finnhub_service.settings") @patch("finnhub_service.httpx.AsyncClient") async def test_social_sentiment_premium_required_401(mock_client_cls, mock_settings): mock_settings.finnhub_api_key = "test_key" mock_resp = MagicMock() mock_resp.status_code = 401 mock_client = AsyncMock() mock_client.get.return_value = mock_resp mock_client.__aenter__ = AsyncMock(return_value=mock_client) mock_client.__aexit__ = AsyncMock(return_value=False) mock_client_cls.return_value = mock_client result = await finnhub_service.get_social_sentiment("AAPL") assert result["premium_required"] is True @pytest.mark.asyncio @patch("finnhub_service.settings") @patch("finnhub_service.httpx.AsyncClient") async def test_social_sentiment_success_with_data(mock_client_cls, mock_settings): mock_settings.finnhub_api_key = "test_key" mock_resp = MagicMock() mock_resp.status_code = 200 mock_resp.raise_for_status = MagicMock() mock_resp.json.return_value = { "reddit": [ {"mention": 50, "positiveScore": 30, "negativeScore": 10, "score": 0.5}, {"mention": 30, "positiveScore": 20, "negativeScore": 5, "score": 0.6}, ], "twitter": [ {"mention": 100, "positiveScore": 60, "negativeScore": 20, "score": 0.4}, ], } mock_client = AsyncMock() mock_client.get.return_value = mock_resp mock_client.__aenter__ = AsyncMock(return_value=mock_client) mock_client.__aexit__ = AsyncMock(return_value=False) mock_client_cls.return_value = mock_client result = await finnhub_service.get_social_sentiment("AAPL") assert result["configured"] is True assert result["symbol"] == "AAPL" assert result["reddit_summary"]["total_mentions"] == 80 assert result["reddit_summary"]["data_points"] == 2 assert result["twitter_summary"]["total_mentions"] == 100 assert len(result["reddit"]) == 2 assert len(result["twitter"]) == 1 @pytest.mark.asyncio @patch("finnhub_service.settings") @patch("finnhub_service.httpx.AsyncClient") async def test_social_sentiment_empty_lists(mock_client_cls, mock_settings): mock_settings.finnhub_api_key = "test_key" mock_resp = MagicMock() mock_resp.status_code = 200 mock_resp.raise_for_status = MagicMock() mock_resp.json.return_value = {"reddit": [], "twitter": []} mock_client = AsyncMock() mock_client.get.return_value = mock_resp mock_client.__aenter__ = AsyncMock(return_value=mock_client) mock_client.__aexit__ = AsyncMock(return_value=False) mock_client_cls.return_value = mock_client result = await finnhub_service.get_social_sentiment("AAPL") assert result["configured"] is True assert result["reddit_summary"] is None assert result["twitter_summary"] is None @pytest.mark.asyncio @patch("finnhub_service.settings") @patch("finnhub_service.httpx.AsyncClient") async def test_social_sentiment_non_dict_response(mock_client_cls, mock_settings): mock_settings.finnhub_api_key = "test_key" mock_resp = MagicMock() mock_resp.status_code = 200 mock_resp.raise_for_status = MagicMock() mock_resp.json.return_value = "unexpected string response" mock_client = AsyncMock() mock_client.get.return_value = mock_resp mock_client.__aenter__ = AsyncMock(return_value=mock_client) mock_client.__aexit__ = AsyncMock(return_value=False) mock_client_cls.return_value = mock_client result = await finnhub_service.get_social_sentiment("AAPL") assert result["reddit"] == [] assert result["twitter"] == [] # --- _summarize_social --- def test_summarize_social_empty(): result = finnhub_service._summarize_social([]) assert result == {} def test_summarize_social_single_entry(): entries = [{"mention": 10, "positiveScore": 7, "negativeScore": 2, "score": 0.5}] result = finnhub_service._summarize_social(entries) assert result["total_mentions"] == 10 assert result["total_positive"] == 7 assert result["total_negative"] == 2 assert result["avg_score"] == 0.5 assert result["data_points"] == 1 def test_summarize_social_multiple_entries(): entries = [ {"mention": 100, "positiveScore": 60, "negativeScore": 20, "score": 0.4}, {"mention": 50, "positiveScore": 30, "negativeScore": 10, "score": 0.6}, ] result = finnhub_service._summarize_social(entries) assert result["total_mentions"] == 150 assert result["total_positive"] == 90 assert result["total_negative"] == 30 assert result["avg_score"] == 0.5 assert result["data_points"] == 2 def test_summarize_social_missing_fields(): entries = [{"mention": 5}] result = finnhub_service._summarize_social(entries) assert result["total_mentions"] == 5 assert result["total_positive"] == 0 assert result["total_negative"] == 0 # --- get_reddit_sentiment --- @pytest.mark.asyncio @patch("finnhub_service.httpx.AsyncClient") async def test_reddit_sentiment_symbol_found(mock_client_cls): mock_resp = MagicMock() mock_resp.raise_for_status = MagicMock() mock_resp.json.return_value = { "results": [ {"ticker": "AAPL", "name": "Apple Inc", "rank": 3, "mentions": 150, "mentions_24h_ago": 100, "upvotes": 500, "rank_24h_ago": 5}, {"ticker": "TSLA", "name": "Tesla", "rank": 1, "mentions": 300, "mentions_24h_ago": 280, "upvotes": 900, "rank_24h_ago": 2}, ] } mock_client = AsyncMock() mock_client.get.return_value = mock_resp mock_client.__aenter__ = AsyncMock(return_value=mock_client) mock_client.__aexit__ = AsyncMock(return_value=False) mock_client_cls.return_value = mock_client result = await finnhub_service.get_reddit_sentiment("AAPL") assert result["found"] is True assert result["symbol"] == "AAPL" assert result["rank"] == 3 assert result["mentions_24h"] == 150 assert result["mentions_24h_ago"] == 100 assert result["mentions_change_pct"] == 50.0 @pytest.mark.asyncio @patch("finnhub_service.httpx.AsyncClient") async def test_reddit_sentiment_symbol_not_found(mock_client_cls): mock_resp = MagicMock() mock_resp.raise_for_status = MagicMock() mock_resp.json.return_value = { "results": [ {"ticker": "TSLA", "rank": 1, "mentions": 300, "mentions_24h_ago": 280} ] } mock_client = AsyncMock() mock_client.get.return_value = mock_resp mock_client.__aenter__ = AsyncMock(return_value=mock_client) mock_client.__aexit__ = AsyncMock(return_value=False) mock_client_cls.return_value = mock_client result = await finnhub_service.get_reddit_sentiment("AAPL") assert result["found"] is False assert result["symbol"] == "AAPL" assert "not in Reddit" in result["message"] @pytest.mark.asyncio @patch("finnhub_service.httpx.AsyncClient") async def test_reddit_sentiment_zero_mentions_prev(mock_client_cls): mock_resp = MagicMock() mock_resp.raise_for_status = MagicMock() mock_resp.json.return_value = { "results": [ {"ticker": "AAPL", "rank": 1, "mentions": 50, "mentions_24h_ago": 0, "upvotes": 200, "rank_24h_ago": None} ] } mock_client = AsyncMock() mock_client.get.return_value = mock_resp mock_client.__aenter__ = AsyncMock(return_value=mock_client) mock_client.__aexit__ = AsyncMock(return_value=False) mock_client_cls.return_value = mock_client result = await finnhub_service.get_reddit_sentiment("AAPL") assert result["found"] is True assert result["mentions_change_pct"] is None # division by zero handled @pytest.mark.asyncio @patch("finnhub_service.httpx.AsyncClient") async def test_reddit_sentiment_api_failure(mock_client_cls): mock_client = AsyncMock() mock_client.get.side_effect = Exception("Connection error") mock_client.__aenter__ = AsyncMock(return_value=mock_client) mock_client.__aexit__ = AsyncMock(return_value=False) mock_client_cls.return_value = mock_client result = await finnhub_service.get_reddit_sentiment("AAPL") assert result["symbol"] == "AAPL" assert "error" in result @pytest.mark.asyncio @patch("finnhub_service.httpx.AsyncClient") async def test_reddit_sentiment_case_insensitive(mock_client_cls): mock_resp = MagicMock() mock_resp.raise_for_status = MagicMock() mock_resp.json.return_value = { "results": [ {"ticker": "aapl", "rank": 1, "mentions": 100, "mentions_24h_ago": 80, "upvotes": 400, "rank_24h_ago": 2} ] } mock_client = AsyncMock() mock_client.get.return_value = mock_resp mock_client.__aenter__ = AsyncMock(return_value=mock_client) mock_client.__aexit__ = AsyncMock(return_value=False) mock_client_cls.return_value = mock_client result = await finnhub_service.get_reddit_sentiment("AAPL") assert result["found"] is True # --- get_reddit_trending --- @pytest.mark.asyncio @patch("finnhub_service.httpx.AsyncClient") async def test_reddit_trending_happy_path(mock_client_cls): mock_resp = MagicMock() mock_resp.raise_for_status = MagicMock() mock_resp.json.return_value = { "results": [ {"ticker": "TSLA", "name": "Tesla", "rank": 1, "mentions": 500, "upvotes": 1000, "rank_24h_ago": 2, "mentions_24h_ago": 400}, {"ticker": "AAPL", "name": "Apple", "rank": 2, "mentions": 300, "upvotes": 700, "rank_24h_ago": 1, "mentions_24h_ago": 350}, {"ticker": "GME", "name": "GameStop", "rank": 3, "mentions": 200, "upvotes": 500, "rank_24h_ago": 3, "mentions_24h_ago": 180}, ] } mock_client = AsyncMock() mock_client.get.return_value = mock_resp mock_client.__aenter__ = AsyncMock(return_value=mock_client) mock_client.__aexit__ = AsyncMock(return_value=False) mock_client_cls.return_value = mock_client result = await finnhub_service.get_reddit_trending() assert len(result) == 3 assert result[0]["symbol"] == "TSLA" assert result[0]["rank"] == 1 assert result[1]["symbol"] == "AAPL" assert "mentions_24h" in result[0] assert "upvotes" in result[0] @pytest.mark.asyncio @patch("finnhub_service.httpx.AsyncClient") async def test_reddit_trending_limits_to_25(mock_client_cls): mock_resp = MagicMock() mock_resp.raise_for_status = MagicMock() mock_resp.json.return_value = { "results": [ {"ticker": f"SYM{i}", "rank": i + 1, "mentions": 100 - i, "upvotes": 50, "rank_24h_ago": i, "mentions_24h_ago": 80} for i in range(30) ] } mock_client = AsyncMock() mock_client.get.return_value = mock_resp mock_client.__aenter__ = AsyncMock(return_value=mock_client) mock_client.__aexit__ = AsyncMock(return_value=False) mock_client_cls.return_value = mock_client result = await finnhub_service.get_reddit_trending() assert len(result) == 25 @pytest.mark.asyncio @patch("finnhub_service.httpx.AsyncClient") async def test_reddit_trending_empty_results(mock_client_cls): mock_resp = MagicMock() mock_resp.raise_for_status = MagicMock() mock_resp.json.return_value = {"results": []} mock_client = AsyncMock() mock_client.get.return_value = mock_resp mock_client.__aenter__ = AsyncMock(return_value=mock_client) mock_client.__aexit__ = AsyncMock(return_value=False) mock_client_cls.return_value = mock_client result = await finnhub_service.get_reddit_trending() assert result == [] @pytest.mark.asyncio @patch("finnhub_service.httpx.AsyncClient") async def test_reddit_trending_api_failure(mock_client_cls): mock_client = AsyncMock() mock_client.get.side_effect = Exception("ApeWisdom down") mock_client.__aenter__ = AsyncMock(return_value=mock_client) mock_client.__aexit__ = AsyncMock(return_value=False) mock_client_cls.return_value = mock_client result = await finnhub_service.get_reddit_trending() assert result == []