from unittest.mock import patch, AsyncMock import pytest from httpx import AsyncClient, ASGITransport from main import app @pytest.fixture async def client(): transport = ASGITransport(app=app) async with AsyncClient(transport=transport, base_url="http://test") as c: yield c @pytest.mark.asyncio @patch("routes_sentiment.alphavantage_service.get_news_sentiment", new_callable=AsyncMock) @patch("routes_sentiment.finnhub_service.get_sentiment_summary", new_callable=AsyncMock) async def test_stock_sentiment(mock_sentiment, mock_av, client): mock_sentiment.return_value = { "symbol": "AAPL", "news_sentiment": {"bullish_percent": 0.7, "bearish_percent": 0.3}, "recent_news": [], "analyst_recommendations": [], "recent_upgrades_downgrades": [], } mock_av.return_value = { "configured": True, "symbol": "AAPL", "article_count": 1, "overall_sentiment": {"avg_score": 0.4, "label": "Bullish"}, "articles": [], } resp = await client.get("/api/v1/stock/AAPL/sentiment") assert resp.status_code == 200 data = resp.json()["data"] assert data["symbol"] == "AAPL" assert data["news_sentiment"]["bullish_percent"] == 0.7 assert data["alpha_vantage_sentiment"]["overall_sentiment"]["label"] == "Bullish" @pytest.mark.asyncio @patch("routes_sentiment.finnhub_service.get_insider_transactions", new_callable=AsyncMock) async def test_stock_insider_trades(mock_insider, client): mock_insider.return_value = [ {"name": "Tim Cook", "share": 100000, "change": -50000, "transactionCode": "S"} ] resp = await client.get("/api/v1/stock/AAPL/insider-trades") assert resp.status_code == 200 data = resp.json()["data"] assert len(data) == 1 assert data[0]["name"] == "Tim Cook" @pytest.mark.asyncio @patch("routes_sentiment.finnhub_service.get_recommendation_trends", new_callable=AsyncMock) async def test_stock_recommendations(mock_recs, client): mock_recs.return_value = [ {"period": "2026-01-01", "strongBuy": 10, "buy": 15, "hold": 5, "sell": 1, "strongSell": 0} ] resp = await client.get("/api/v1/stock/AAPL/recommendations") assert resp.status_code == 200 data = resp.json()["data"] assert len(data) == 1 assert data[0]["strong_buy"] == 10 @pytest.mark.asyncio @patch("routes_sentiment.finnhub_service.get_upgrade_downgrade", new_callable=AsyncMock) async def test_stock_upgrades(mock_upgrades, client): mock_upgrades.return_value = [ {"company": "Morgan Stanley", "action": "upgrade", "fromGrade": "Hold", "toGrade": "Buy"} ] resp = await client.get("/api/v1/stock/AAPL/upgrades") assert resp.status_code == 200 data = resp.json()["data"] assert len(data) == 1 assert data[0]["action"] == "upgrade" @pytest.mark.asyncio @patch("routes_sentiment.alphavantage_service.get_news_sentiment", new_callable=AsyncMock) async def test_stock_news_sentiment(mock_av, client): mock_av.return_value = { "configured": True, "symbol": "AAPL", "article_count": 2, "overall_sentiment": {"avg_score": 0.3, "label": "Somewhat-Bullish"}, "articles": [ {"title": "Apple up", "ticker_sentiment_score": 0.5}, {"title": "Apple flat", "ticker_sentiment_score": 0.1}, ], } resp = await client.get("/api/v1/stock/AAPL/news-sentiment") assert resp.status_code == 200 data = resp.json()["data"] assert data["article_count"] == 2 assert data["overall_sentiment"]["label"] == "Somewhat-Bullish" @pytest.mark.asyncio async def test_invalid_symbol_sentiment(client): resp = await client.get("/api/v1/stock/AAPL;DROP/sentiment") assert resp.status_code == 400