"""Unit tests for the Antigravity provider.""" import json import os import tempfile from pathlib import Path from unittest import mock import pytest from providers.antigravity import AntigravityProvider from providers.antigravity_auth import AntigravityAccount, AntigravityTokenManager from providers.registries.antigravity import AntigravityModelRegistry from providers.shared import ProviderType class TestAntigravityModelRegistry: """Tests for the Antigravity model registry.""" def test_registry_loads_models(self): """Verify registry loads models from config file.""" registry = AntigravityModelRegistry() models = registry.list_models() assert len(models) > 0 assert "claude-opus-4-5-thinking" in models assert "claude-sonnet-4-5" in models assert "gemini-3-pro-high" in models def test_registry_aliases(self): """Verify alias resolution works correctly.""" registry = AntigravityModelRegistry() # Test alias resolution capabilities = registry.resolve("antigravity-claude-opus-4-5-thinking") assert capabilities is not None assert capabilities.model_name == "claude-opus-4-5-thinking" # Test short alias capabilities = registry.resolve("ag-opus-thinking") assert capabilities is not None assert capabilities.model_name == "claude-opus-4-5-thinking" def test_model_capabilities(self): """Verify model capabilities are correct.""" registry = AntigravityModelRegistry() opus = registry.get_capabilities("claude-opus-4-5-thinking") assert opus is not None assert opus.supports_extended_thinking is True assert opus.max_thinking_tokens == 32768 assert opus.supports_images is True sonnet = registry.get_capabilities("claude-sonnet-4-5") assert sonnet is not None assert sonnet.supports_extended_thinking is False def test_quota_pool(self): """Verify quota pool assignment.""" registry = AntigravityModelRegistry() # Antigravity quota models assert registry.get_quota_pool("claude-opus-4-5-thinking") == "antigravity" assert registry.get_quota_pool("gemini-3-pro-high") == "antigravity" # Gemini CLI quota models assert registry.get_quota_pool("gemini-2.5-pro") == "gemini-cli" assert registry.get_quota_pool("gemini-3-flash-preview") == "gemini-cli" class TestAntigravityTokenManager: """Tests for the OAuth2 token manager.""" def test_empty_initialization(self): """Verify manager handles no accounts gracefully.""" # Create temp dir without accounts file with tempfile.TemporaryDirectory() as tmpdir: fake_path = Path(tmpdir) / "nonexistent.json" manager = AntigravityTokenManager(accounts_file=fake_path) assert not manager.has_accounts() assert manager.get_account_count() == 0 def test_env_var_override(self, monkeypatch): """Verify environment variable takes precedence.""" monkeypatch.setenv("ANTIGRAVITY_REFRESH_TOKEN", "test_token_123") monkeypatch.setenv("ANTIGRAVITY_PROJECT_ID", "test-project") manager = AntigravityTokenManager() assert manager.has_accounts() assert manager.get_account_count() == 1 def test_load_accounts_from_file(self): """Verify loading accounts from JSON file.""" accounts_data = { "version": 3, "accounts": [ { "email": "test@example.com", "refreshToken": "token_1", "projectId": "project-1", "enabled": True, }, { "email": "test2@example.com", "refreshToken": "token_2", "projectId": "project-2", "enabled": True, }, { "email": "disabled@example.com", "refreshToken": "token_3", "projectId": "project-3", "enabled": False, # Should be skipped }, ], } with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f: json.dump(accounts_data, f) temp_path = f.name try: # Clear env to ensure file is used with mock.patch.dict(os.environ, {}, clear=True): manager = AntigravityTokenManager(accounts_file=temp_path) assert manager.has_accounts() assert manager.get_account_count() == 2 # Disabled account skipped finally: os.unlink(temp_path) class TestAntigravityAccount: """Tests for the AntigravityAccount dataclass.""" def test_token_validity(self): """Test token validity checking.""" import time account = AntigravityAccount( email="test@example.com", refresh_token="token", project_id="project", ) # No access token assert not account.is_token_valid() # Set valid token account.access_token = "access_token" account.token_expiry = time.time() + 3600 assert account.is_token_valid() # Expired token account.token_expiry = time.time() - 100 assert not account.is_token_valid() def test_rate_limit_tracking(self): """Test rate limit tracking.""" import time account = AntigravityAccount( email="test@example.com", refresh_token="token", project_id="project", ) # No rate limits initially assert not account.is_rate_limited() assert not account.is_rate_limited("claude-opus-4-5-thinking") # Set rate limit account.set_rate_limited("claude-opus-4-5-thinking", 60.0) assert account.is_rate_limited("claude-opus-4-5-thinking") assert account.is_rate_limited() # Any rate limit active class TestAntigravityProvider: """Tests for the Antigravity provider.""" def test_provider_type(self): """Verify provider type is correct.""" # Mock token manager to avoid needing real credentials with mock.patch.object(AntigravityTokenManager, "__init__", return_value=None): with mock.patch.object(AntigravityTokenManager, "has_accounts", return_value=True): provider = AntigravityProvider() assert provider.get_provider_type() == ProviderType.ANTIGRAVITY def test_model_capabilities(self): """Verify model capabilities are accessible.""" with mock.patch.object(AntigravityTokenManager, "__init__", return_value=None): with mock.patch.object(AntigravityTokenManager, "has_accounts", return_value=True): provider = AntigravityProvider() caps = provider.get_all_model_capabilities() assert len(caps) > 0 assert "claude-opus-4-5-thinking" in caps def test_model_resolution(self): """Verify alias resolution works through provider.""" with mock.patch.object(AntigravityTokenManager, "__init__", return_value=None): with mock.patch.object(AntigravityTokenManager, "has_accounts", return_value=True): provider = AntigravityProvider() # Test alias resolution resolved = provider._resolve_model_name("ag-opus-thinking") assert resolved == "claude-opus-4-5-thinking" # Test canonical name resolved = provider._resolve_model_name("claude-sonnet-4-5") assert resolved == "claude-sonnet-4-5" def test_request_building(self): """Verify request body is built correctly.""" with mock.patch.object(AntigravityTokenManager, "__init__", return_value=None): with mock.patch.object(AntigravityTokenManager, "has_accounts", return_value=True): provider = AntigravityProvider() capabilities = provider.get_capabilities("claude-opus-4-5-thinking") request = provider._build_request( prompt="Hello, world!", model_name="claude-opus-4-5-thinking", project_id="test-project", system_prompt="You are a helpful assistant.", temperature=0.7, max_output_tokens=1000, thinking_mode="medium", capabilities=capabilities, ) assert request["project"] == "test-project" assert request["model"] == "claude-opus-4-5-thinking" assert "contents" in request["request"] assert "systemInstruction" in request["request"] assert "generationConfig" in request["request"] # Check thinking config for thinking models config = request["request"]["generationConfig"] assert "thinkingConfig" in config assert config["thinkingConfig"]["thinkingBudget"] > 0 def test_list_models(self): """Verify list_models returns expected models.""" with mock.patch.object(AntigravityTokenManager, "__init__", return_value=None): with mock.patch.object(AntigravityTokenManager, "has_accounts", return_value=True): provider = AntigravityProvider() models = provider.list_models(respect_restrictions=False) assert len(models) > 0 # Check that both canonical names and aliases are present assert any("claude" in m.lower() for m in models) assert any("gemini" in m.lower() for m in models) @pytest.mark.integration class TestAntigravityIntegration: """Integration tests (require actual credentials and valid tokens). These tests require: - Valid Antigravity credentials (env var or accounts file) - Working OAuth tokens that can be refreshed Run with: pytest -m integration tests/test_antigravity_provider.py Skip with: pytest -m "not integration" tests/test_antigravity_provider.py """ @pytest.mark.skipif( not os.getenv("ANTIGRAVITY_REFRESH_TOKEN") and not Path.home().joinpath(".config/opencode/antigravity-accounts.json").exists(), reason="Antigravity credentials not available", ) def test_token_refresh(self): """Test actual token refresh with real credentials.""" manager = AntigravityTokenManager() assert manager.has_accounts() # This will trigger a token refresh - may fail if tokens are expired try: token, project_id, headers = manager.get_access_token() assert token is not None assert len(token) > 0 assert project_id is not None except RuntimeError as e: if "rate limited or have invalid tokens" in str(e): pytest.skip("Antigravity tokens are expired or invalid") raise @pytest.mark.skipif( not os.getenv("ANTIGRAVITY_REFRESH_TOKEN") and not Path.home().joinpath(".config/opencode/antigravity-accounts.json").exists(), reason="Antigravity credentials not available", ) def test_simple_generation(self): """Test actual content generation with Antigravity API.""" provider = AntigravityProvider() try: response = provider.generate_content( prompt="Say 'hello' and nothing else.", model_name="claude-sonnet-4-5", temperature=0.0, max_output_tokens=100, ) except RuntimeError as e: if "rate limited or have invalid tokens" in str(e): pytest.skip("Antigravity tokens are expired or invalid") raise assert response is not None assert response.content is not None assert "hello" in response.content.lower() assert response.provider == ProviderType.ANTIGRAVITY