Spaces:
Paused
Paused
| import os | |
| from unittest.mock import AsyncMock, patch | |
| import pytest | |
| from api_utils.auth_manager import AuthManager, auth_manager | |
| # --- Fixtures --- | |
| def manager(): | |
| return AuthManager() | |
| def mock_saved_auth_dir(tmp_path): | |
| """Mock the SAVED_AUTH_DIR constant.""" | |
| with patch("api_utils.auth_manager.SAVED_AUTH_DIR", str(tmp_path)): | |
| yield tmp_path | |
| # --- Tests --- | |
| def test_init_default(): | |
| """Test initialization without environment variable.""" | |
| with patch.dict(os.environ, {}, clear=True): | |
| am = AuthManager() | |
| assert am.current_profile is None | |
| assert am.failed_profiles == set() | |
| def test_init_with_env_var(): | |
| """Test initialization with ACTIVE_AUTH_JSON_PATH.""" | |
| with patch.dict(os.environ, {"ACTIVE_AUTH_JSON_PATH": "/path/to/auth.json"}): | |
| am = AuthManager() | |
| assert am.current_profile == "/path/to/auth.json" | |
| async def test_get_available_profiles_no_dir(manager): | |
| """Test get_available_profiles when directory doesn't exist.""" | |
| with patch("os.path.exists", return_value=False): | |
| profiles = await manager.get_available_profiles() | |
| assert profiles == [] | |
| async def test_get_available_profiles_success(manager, mock_saved_auth_dir): | |
| """Test get_available_profiles with existing files.""" | |
| # Create dummy auth files | |
| (mock_saved_auth_dir / "auth1.json").touch() | |
| (mock_saved_auth_dir / "auth2.json").touch() | |
| with patch("os.path.exists", return_value=True): | |
| profiles = await manager.get_available_profiles() | |
| assert len(profiles) == 2 | |
| assert any("auth1.json" in p for p in profiles) | |
| assert any("auth2.json" in p for p in profiles) | |
| # Check sorting | |
| assert profiles == sorted(profiles) | |
| async def test_get_next_profile_success(manager): | |
| """Test getting next profile successfully.""" | |
| mock_profiles = ["/dir/auth1.json", "/dir/auth2.json"] | |
| with patch.object( | |
| manager, "get_available_profiles", new_callable=AsyncMock | |
| ) as mock_get: | |
| mock_get.return_value = mock_profiles | |
| # First call | |
| next_p = await manager.get_next_profile() | |
| assert next_p == "/dir/auth1.json" | |
| assert manager.current_profile == "/dir/auth1.json" | |
| async def test_get_next_profile_skips_failed(manager): | |
| """Test that failed profiles are skipped.""" | |
| mock_profiles = ["/dir/auth1.json", "/dir/auth2.json", "/dir/auth3.json"] | |
| manager.failed_profiles.add("/dir/auth1.json") | |
| with patch.object( | |
| manager, "get_available_profiles", new_callable=AsyncMock | |
| ) as mock_get: | |
| mock_get.return_value = mock_profiles | |
| next_p = await manager.get_next_profile() | |
| assert next_p == "/dir/auth2.json" | |
| # Mark auth2 as failed and try again | |
| manager.mark_profile_failed() # marks current (auth2) | |
| next_p_2 = await manager.get_next_profile() | |
| assert next_p_2 == "/dir/auth3.json" | |
| async def test_get_next_profile_skips_current(manager): | |
| """Test that current profile is skipped even if not failed (to ensure rotation if needed, or just behavior check).""" | |
| # Actually logic says: os.path.basename(p) != current_basename | |
| # So if we call get_next_profile, it should give us a *different* one if available. | |
| mock_profiles = ["/dir/auth1.json", "/dir/auth2.json"] | |
| manager.current_profile = "/dir/auth1.json" | |
| with patch.object( | |
| manager, "get_available_profiles", new_callable=AsyncMock | |
| ) as mock_get: | |
| mock_get.return_value = mock_profiles | |
| next_p = await manager.get_next_profile() | |
| assert next_p == "/dir/auth2.json" | |
| async def test_get_next_profile_exhausted(manager): | |
| """Test raising RuntimeError when no profiles available.""" | |
| with patch.object( | |
| manager, "get_available_profiles", new_callable=AsyncMock | |
| ) as mock_get: | |
| mock_get.return_value = [] | |
| with pytest.raises(RuntimeError, match="All authentication profiles exhausted"): | |
| await manager.get_next_profile() | |
| async def test_get_next_profile_all_failed(manager): | |
| """Test raising RuntimeError when all profiles failed.""" | |
| mock_profiles = ["/dir/auth1.json"] | |
| manager.failed_profiles.add("/dir/auth1.json") | |
| with patch.object( | |
| manager, "get_available_profiles", new_callable=AsyncMock | |
| ) as mock_get: | |
| mock_get.return_value = mock_profiles | |
| with pytest.raises(RuntimeError, match="All authentication profiles exhausted"): | |
| await manager.get_next_profile() | |
| def test_mark_profile_failed(manager): | |
| """Test marking profile as failed.""" | |
| # 1. With explicit path | |
| manager.mark_profile_failed("/dir/auth1.json") | |
| assert "/dir/auth1.json" in manager.failed_profiles | |
| # 2. With current profile | |
| manager.current_profile = "/dir/auth2.json" | |
| manager.mark_profile_failed() | |
| assert "/dir/auth2.json" in manager.failed_profiles | |
| # 3. No profile active and no arg | |
| manager.current_profile = None | |
| manager.mark_profile_failed() # Should log warning but not crash | |
| # (Assert log if needed, but no crash is enough for basic coverage) | |
| def test_global_instance(): | |
| """Ensure global instance exists.""" | |
| assert isinstance(auth_manager, AuthManager) | |