"""Integration tests for provider validation and environment handling.""" import os import pytest from dataclasses import dataclass from typing import Optional from ra_aid.env import validate_environment from ra_aid.provider_strategy import ( ProviderFactory, ValidationResult, AnthropicStrategy, OpenAIStrategy, OpenAICompatibleStrategy, OpenRouterStrategy, GeminiStrategy, ) @dataclass class MockArgs: """Mock arguments for testing.""" provider: str expert_provider: Optional[str] = None model: Optional[str] = None expert_model: Optional[str] = None research_provider: Optional[str] = None research_model: Optional[str] = None planner_provider: Optional[str] = None planner_model: Optional[str] = None @pytest.fixture def clean_env(): """Remove all provider-related environment variables.""" env_vars = [ "ANTHROPIC_API_KEY", "OPENAI_API_KEY", "OPENROUTER_API_KEY", "OPENAI_API_BASE", "EXPERT_ANTHROPIC_API_KEY", "EXPERT_OPENAI_API_KEY", "EXPERT_OPENAI_API_BASE", "TAVILY_API_KEY", "ANTHROPIC_MODEL", "GEMINI_API_KEY", "EXPERT_GEMINI_API_KEY", "GEMINI_MODEL", ] # Store original values original_values = {} for var in env_vars: original_values[var] = os.environ.get(var) if var in os.environ: del os.environ[var] yield # Restore original values for var, value in original_values.items(): if value is not None: os.environ[var] = value elif var in os.environ: del os.environ[var] def test_provider_validation_respects_cli_args(clean_env): """Test that provider validation respects CLI args over defaults.""" # Set up environment with only OpenAI credentials os.environ["OPENAI_API_KEY"] = "test-key" # Should succeed with OpenAI provider args = MockArgs(provider="openai") expert_enabled, expert_missing, web_enabled, web_missing = validate_environment( args ) assert not expert_missing # Should fail with Anthropic provider even though it's first alphabetically args = MockArgs(provider="anthropic", model="claude-3-haiku-20240307") with pytest.raises(SystemExit): validate_environment(args) def test_expert_provider_fallback(clean_env): """Test expert provider falls back to main provider keys.""" os.environ["OPENAI_API_KEY"] = "test-key" args = MockArgs(provider="openai", expert_provider="openai") expert_enabled, expert_missing, _, _ = validate_environment(args) assert expert_enabled assert not expert_missing def test_openai_compatible_base_url(clean_env): """Test OpenAI-compatible provider requires base URL.""" os.environ["OPENAI_API_KEY"] = "test-key" args = MockArgs(provider="openai-compatible") with pytest.raises(SystemExit): validate_environment(args) os.environ["OPENAI_API_BASE"] = "http://test" expert_enabled, expert_missing, _, _ = validate_environment(args) assert not expert_missing def test_expert_provider_separate_keys(clean_env): """Test expert provider can use separate keys.""" os.environ["OPENAI_API_KEY"] = "main-key" os.environ["EXPERT_OPENAI_API_KEY"] = "expert-key" args = MockArgs(provider="openai", expert_provider="openai") expert_enabled, expert_missing, _, _ = validate_environment(args) assert expert_enabled assert not expert_missing def test_web_research_independent(clean_env): """Test web research validation is independent of provider.""" os.environ["OPENAI_API_KEY"] = "test-key" args = MockArgs(provider="openai") # Without Tavily key expert_enabled, expert_missing, web_enabled, web_missing = validate_environment( args ) assert not web_enabled assert web_missing # With Tavily key os.environ["TAVILY_API_KEY"] = "test-key" expert_enabled, expert_missing, web_enabled, web_missing = validate_environment( args ) assert web_enabled assert not web_missing def test_provider_factory_unknown_provider(clean_env): """Test provider factory handles unknown providers.""" strategy = ProviderFactory.create("unknown") assert strategy is None args = MockArgs(provider="unknown") with pytest.raises(SystemExit): validate_environment(args) def test_provider_strategy_validation(clean_env): """Test individual provider strategies.""" # Test Anthropic strategy strategy = AnthropicStrategy() result = strategy.validate() assert not result.valid assert "ANTHROPIC_API_KEY environment variable is not set" in result.missing_vars os.environ["ANTHROPIC_API_KEY"] = "test-key" os.environ["ANTHROPIC_MODEL"] = "claude-3-haiku-20240307" result = strategy.validate() assert result.valid assert not result.missing_vars # Test OpenAI strategy strategy = OpenAIStrategy() result = strategy.validate() assert not result.valid assert "OPENAI_API_KEY environment variable is not set" in result.missing_vars os.environ["OPENAI_API_KEY"] = "test-key" result = strategy.validate() assert result.valid assert not result.missing_vars def test_missing_provider_arg(): """Test handling of missing provider argument.""" with pytest.raises(SystemExit): validate_environment(None) with pytest.raises(SystemExit): validate_environment(MockArgs(provider=None)) def test_empty_provider_arg(): """Test handling of empty provider argument.""" with pytest.raises(SystemExit): validate_environment(MockArgs(provider="")) def test_incomplete_openai_compatible_config(clean_env): """Test OpenAI-compatible provider with incomplete configuration.""" strategy = OpenAICompatibleStrategy() # No configuration result = strategy.validate() assert not result.valid assert "OPENAI_API_KEY environment variable is not set" in result.missing_vars assert "OPENAI_API_BASE environment variable is not set" in result.missing_vars # Only API key os.environ["OPENAI_API_KEY"] = "test-key" result = strategy.validate() assert not result.valid assert "OPENAI_API_BASE environment variable is not set" in result.missing_vars # Only base URL os.environ.pop("OPENAI_API_KEY") os.environ["OPENAI_API_BASE"] = "http://test" result = strategy.validate() assert not result.valid assert "OPENAI_API_KEY environment variable is not set" in result.missing_vars def test_incomplete_gemini_config(clean_env): """Test Gemini provider with incomplete configuration.""" strategy = GeminiStrategy() # No configuration result = strategy.validate() assert not result.valid assert "GEMINI_API_KEY environment variable is not set" in result.missing_vars # Valid API key os.environ["GEMINI_API_KEY"] = "test-key" result = strategy.validate() assert result.valid assert not result.missing_vars def test_incomplete_expert_config(clean_env): """Test expert provider with incomplete configuration.""" # Set main provider but not expert os.environ["OPENAI_API_KEY"] = "test-key" args = MockArgs(provider="openai", expert_provider="openai-compatible") expert_enabled, expert_missing, _, _ = validate_environment(args) assert not expert_enabled assert len(expert_missing) == 1 assert "EXPERT_OPENAI_API_BASE" in expert_missing[0] # Set expert key but not base URL os.environ["EXPERT_OPENAI_API_KEY"] = "test-key" expert_enabled, expert_missing, _, _ = validate_environment(args) assert not expert_enabled assert len(expert_missing) == 1 assert "EXPERT_OPENAI_API_BASE" in expert_missing[0] def test_empty_environment_variables(clean_env): """Test handling of empty environment variables.""" # Empty API key os.environ["OPENAI_API_KEY"] = "" args = MockArgs(provider="openai") with pytest.raises(SystemExit): validate_environment(args) # Empty base URL os.environ["OPENAI_API_KEY"] = "test-key" os.environ["OPENAI_API_BASE"] = "" args = MockArgs(provider="openai-compatible") with pytest.raises(SystemExit): validate_environment(args) def test_openrouter_validation(clean_env): """Test OpenRouter provider validation.""" strategy = OpenRouterStrategy() # No API key result = strategy.validate() assert not result.valid assert "OPENROUTER_API_KEY environment variable is not set" in result.missing_vars # Empty API key os.environ["OPENROUTER_API_KEY"] = "" result = strategy.validate() assert not result.valid assert "OPENROUTER_API_KEY environment variable is not set" in result.missing_vars # Valid API key os.environ["OPENROUTER_API_KEY"] = "test-key" result = strategy.validate() assert result.valid assert not result.missing_vars def test_multiple_expert_providers(clean_env): """Test validation with multiple expert providers.""" os.environ["OPENAI_API_KEY"] = "test-key" os.environ["ANTHROPIC_API_KEY"] = "test-key" os.environ["ANTHROPIC_MODEL"] = "claude-3-haiku-20240307" # First expert provider valid, second invalid args = MockArgs( provider="openai", expert_provider="anthropic", expert_model="claude-3-haiku-20240307", ) expert_enabled, expert_missing, _, _ = validate_environment(args) assert expert_enabled assert not expert_missing # Switch to invalid provider args = MockArgs(provider="openai", expert_provider="openai-compatible") expert_enabled, expert_missing, _, _ = validate_environment(args) assert not expert_enabled assert expert_missing