""" Tests for SessionID Pool Service (T-021, T-022) """ import pytest from unittest.mock import AsyncMock, patch, MagicMock import httpx from app.services.session_pool import ( SessionPool, session_pool, get_session_with_retry, ) class TestSessionPool: """Tests for SessionPool class.""" async def test_refresh_success(self): """Test successful session pool refresh.""" pool = SessionPool() mock_response = MagicMock() mock_response.status_code = 200 mock_response.json.return_value = { "data": [ {"sessionid": "session_001", "user": "test1"}, {"sessionid": "session_002", "user": "test2"}, {"sessionid": "session_003", "user": "test3"}, ] } mock_client = AsyncMock() mock_client.get.return_value = mock_response mock_client.__aenter__.return_value = mock_client mock_client.__aexit__.return_value = None with patch("httpx.AsyncClient", return_value=mock_client): result = await pool.refresh() assert result is True assert pool.size == 3 assert not pool.is_empty async def test_refresh_empty_data(self): """Test refresh with empty data array.""" pool = SessionPool() mock_response = MagicMock() mock_response.status_code = 200 mock_response.json.return_value = {"data": []} mock_client = AsyncMock() mock_client.get.return_value = mock_response mock_client.__aenter__.return_value = mock_client mock_client.__aexit__.return_value = None with patch("httpx.AsyncClient", return_value=mock_client): result = await pool.refresh() assert result is False assert pool.size == 0 async def test_refresh_api_error(self): """Test refresh with API error.""" pool = SessionPool() mock_response = MagicMock() mock_response.status_code = 500 mock_client = AsyncMock() mock_client.get.return_value = mock_response mock_client.__aenter__.return_value = mock_client mock_client.__aexit__.return_value = None with patch("httpx.AsyncClient", return_value=mock_client): result = await pool.refresh() assert result is False async def test_refresh_timeout(self): """Test refresh with timeout.""" pool = SessionPool() mock_client = AsyncMock() mock_client.get.side_effect = httpx.TimeoutException("Timeout") mock_client.__aenter__.return_value = mock_client mock_client.__aexit__.return_value = None with patch("httpx.AsyncClient", return_value=mock_client): result = await pool.refresh() assert result is False async def test_refresh_request_error(self): """Test refresh with request error.""" pool = SessionPool() mock_client = AsyncMock() mock_client.get.side_effect = httpx.RequestError("Connection failed") mock_client.__aenter__.return_value = mock_client mock_client.__aexit__.return_value = None with patch("httpx.AsyncClient", return_value=mock_client): result = await pool.refresh() assert result is False async def test_refresh_unexpected_error(self): """Test refresh with unexpected error.""" pool = SessionPool() mock_client = AsyncMock() mock_client.get.side_effect = ValueError("Unexpected") mock_client.__aenter__.return_value = mock_client mock_client.__aexit__.return_value = None with patch("httpx.AsyncClient", return_value=mock_client): result = await pool.refresh() assert result is False async def test_refresh_with_auth_header(self): """Test that refresh includes Authorization header.""" pool = SessionPool() mock_response = MagicMock() mock_response.status_code = 200 mock_response.json.return_value = {"data": [{"sessionid": "test"}]} mock_client = AsyncMock() mock_client.get.return_value = mock_response mock_client.__aenter__.return_value = mock_client mock_client.__aexit__.return_value = None with patch("httpx.AsyncClient", return_value=mock_client): with patch("app.services.session_pool.settings") as mock_settings: mock_settings.YUNTU_API_TOKEN = "test_token" mock_settings.YUNTU_API_TIMEOUT = 10.0 mock_settings.BRAND_API_BASE_URL = "https://api.test.com" await pool.refresh() mock_client.get.assert_called_once() call_args = mock_client.get.call_args assert "headers" in call_args.kwargs assert call_args.kwargs["headers"]["Authorization"] == "Bearer test_token" def test_get_random_from_pool(self): """Test getting random session from pool.""" pool = SessionPool() pool._sessions = ["session_1", "session_2", "session_3"] session = pool.get_random() assert session in pool._sessions def test_get_random_from_empty_pool(self): """Test getting random session from empty pool.""" pool = SessionPool() session = pool.get_random() assert session is None def test_remove_session(self): """Test removing a session from pool.""" pool = SessionPool() pool._sessions = ["session_1", "session_2", "session_3"] pool.remove("session_2") assert pool.size == 2 assert "session_2" not in pool._sessions def test_remove_nonexistent_session(self): """Test removing a session that doesn't exist.""" pool = SessionPool() pool._sessions = ["session_1"] # Should not raise pool.remove("nonexistent") assert pool.size == 1 def test_size_property(self): """Test size property.""" pool = SessionPool() assert pool.size == 0 pool._sessions = ["a", "b"] assert pool.size == 2 def test_is_empty_property(self): """Test is_empty property.""" pool = SessionPool() assert pool.is_empty is True pool._sessions = ["a"] assert pool.is_empty is False class TestGetSessionWithRetry: """Tests for get_session_with_retry function (T-022).""" async def test_get_session_success(self): """Test successful session retrieval.""" with patch.object(session_pool, "_sessions", ["session_1", "session_2"]): result = await get_session_with_retry() assert result in ["session_1", "session_2"] async def test_get_session_refresh_on_empty(self): """Test that pool is refreshed when empty.""" with patch.object(session_pool, "_sessions", []): with patch.object(session_pool, "refresh") as mock_refresh: mock_refresh.return_value = True # After refresh, pool should have sessions async def refresh_side_effect(): session_pool._sessions.append("new_session") return True mock_refresh.side_effect = refresh_side_effect result = await get_session_with_retry() assert mock_refresh.called assert result == "new_session" async def test_get_session_retry_on_refresh_failure(self): """Test retry behavior when refresh fails.""" original_sessions = session_pool._sessions.copy() try: session_pool._sessions = [] with patch.object(session_pool, "refresh") as mock_refresh: mock_refresh.return_value = False result = await get_session_with_retry(max_retries=3) assert result is None assert mock_refresh.call_count == 3 finally: session_pool._sessions = original_sessions async def test_get_session_max_retries(self): """Test max retries limit.""" original_sessions = session_pool._sessions.copy() try: session_pool._sessions = [] with patch.object(session_pool, "refresh") as mock_refresh: mock_refresh.return_value = False result = await get_session_with_retry(max_retries=5) assert result is None assert mock_refresh.call_count == 5 finally: session_pool._sessions = original_sessions class TestSessionPoolIntegration: """Integration tests for session pool.""" async def test_refresh_filters_invalid_items(self): """Test that refresh filters out invalid items.""" pool = SessionPool() mock_response = MagicMock() mock_response.status_code = 200 mock_response.json.return_value = { "data": [ {"sessionid": "valid_session"}, {"no_sessionid": "missing"}, None, {"sessionid": ""}, # Empty string should be filtered {"sessionid": "another_valid"}, ] } mock_client = AsyncMock() mock_client.get.return_value = mock_response mock_client.__aenter__.return_value = mock_client mock_client.__aexit__.return_value = None with patch("httpx.AsyncClient", return_value=mock_client): result = await pool.refresh() assert result is True assert pool.size == 2 assert "valid_session" in pool._sessions assert "another_valid" in pool._sessions async def test_refresh_handles_non_dict_data(self): """Test refresh with non-dict response.""" pool = SessionPool() mock_response = MagicMock() mock_response.status_code = 200 mock_response.json.return_value = ["not", "a", "dict"] mock_client = AsyncMock() mock_client.get.return_value = mock_response mock_client.__aenter__.return_value = mock_client mock_client.__aexit__.return_value = None with patch("httpx.AsyncClient", return_value=mock_client): result = await pool.refresh() assert result is False