import os import pytest from dataclasses import dataclass from typing import Optional from ra_aid.env import validate_environment @dataclass class MockArgs: provider: str expert_provider: str model: Optional[str] = None expert_model: Optional[str] = None @pytest.fixture def clean_env(monkeypatch): """Remove relevant environment variables before each test""" env_vars = [ 'ANTHROPIC_API_KEY', 'OPENAI_API_KEY', 'OPENROUTER_API_KEY', 'OPENAI_API_BASE', 'EXPERT_ANTHROPIC_API_KEY', 'EXPERT_OPENAI_API_KEY', 'EXPERT_OPENROUTER_API_KEY', 'EXPERT_OPENAI_API_BASE' ] for var in env_vars: monkeypatch.delenv(var, raising=False) def test_anthropic_validation(clean_env, monkeypatch): args = MockArgs(provider="anthropic", expert_provider="openai") # Should fail without API key with pytest.raises(SystemExit): validate_environment(args) # Should pass with API key monkeypatch.setenv('ANTHROPIC_API_KEY', 'test-key') expert_enabled, missing = validate_environment(args) assert not expert_enabled assert 'EXPERT_OPENAI_API_KEY environment variable is not set' in missing def test_openai_validation(clean_env, monkeypatch): args = MockArgs(provider="openai", expert_provider="openai") # Should fail without API key with pytest.raises(SystemExit): validate_environment(args) # Should pass with API key and enable expert mode with fallback monkeypatch.setenv('OPENAI_API_KEY', 'test-key') expert_enabled, missing = validate_environment(args) assert expert_enabled assert not missing assert os.environ.get('EXPERT_OPENAI_API_KEY') == 'test-key' def test_openai_compatible_validation(clean_env, monkeypatch): args = MockArgs(provider="openai-compatible", expert_provider="openai-compatible") # Should fail without API key and base URL with pytest.raises(SystemExit): validate_environment(args) # Should fail with only API key monkeypatch.setenv('OPENAI_API_KEY', 'test-key') with pytest.raises(SystemExit): validate_environment(args) # Should pass with both API key and base URL monkeypatch.setenv('OPENAI_API_BASE', 'http://test') expert_enabled, missing = validate_environment(args) assert expert_enabled assert not missing assert os.environ.get('EXPERT_OPENAI_API_KEY') == 'test-key' assert os.environ.get('EXPERT_OPENAI_API_BASE') == 'http://test' def test_expert_fallback(clean_env, monkeypatch): args = MockArgs(provider="openai", expert_provider="openai") # Set only base API key monkeypatch.setenv('OPENAI_API_KEY', 'test-key') # Should enable expert mode with fallback expert_enabled, missing = validate_environment(args) assert expert_enabled assert not missing assert os.environ.get('EXPERT_OPENAI_API_KEY') == 'test-key' # Should use explicit expert key if available monkeypatch.setenv('EXPERT_OPENAI_API_KEY', 'expert-key') expert_enabled, missing = validate_environment(args) assert expert_enabled assert not missing assert os.environ.get('EXPERT_OPENAI_API_KEY') == 'expert-key' def test_cross_provider_fallback(clean_env, monkeypatch): """Test that fallback works even when providers differ""" args = MockArgs(provider="openai", expert_provider="anthropic") # Set base API key for main provider and expert provider monkeypatch.setenv('OPENAI_API_KEY', 'openai-key') monkeypatch.setenv('ANTHROPIC_API_KEY', 'anthropic-key') # Should enable expert mode with fallback to ANTHROPIC base key expert_enabled, missing = validate_environment(args) assert expert_enabled assert not missing assert os.environ.get('EXPERT_ANTHROPIC_API_KEY') == 'anthropic-key' # Try with openai-compatible expert provider args = MockArgs(provider="anthropic", expert_provider="openai-compatible") monkeypatch.setenv('OPENAI_API_KEY', 'openai-key') monkeypatch.setenv('OPENAI_API_BASE', 'http://test') expert_enabled, missing = validate_environment(args) assert expert_enabled assert not missing assert os.environ.get('EXPERT_OPENAI_API_KEY') == 'openai-key' assert os.environ.get('EXPERT_OPENAI_API_BASE') == 'http://test' def test_no_warning_on_fallback(clean_env, monkeypatch): """Test that no warning is issued when fallback succeeds""" args = MockArgs(provider="openai", expert_provider="openai") # Set only base API key monkeypatch.setenv('OPENAI_API_KEY', 'test-key') # Should enable expert mode with fallback and no warnings expert_enabled, expert_missing = validate_environment(args) assert expert_enabled assert not expert_missing # List should be empty assert os.environ.get('EXPERT_OPENAI_API_KEY') == 'test-key' def test_different_providers_no_expert_key(clean_env, monkeypatch): """Test behavior when providers differ and only base keys are available""" args = MockArgs(provider="anthropic", expert_provider="openai") # Set only base keys monkeypatch.setenv('ANTHROPIC_API_KEY', 'anthropic-key') monkeypatch.setenv('OPENAI_API_KEY', 'openai-key') # Should enable expert mode and use base OPENAI key expert_enabled, missing = validate_environment(args) assert expert_enabled assert not missing assert os.environ.get('EXPERT_OPENAI_API_KEY') == 'openai-key' def test_mixed_provider_openai_compatible(clean_env, monkeypatch): """Test behavior with openai-compatible expert and different main provider""" args = MockArgs(provider="anthropic", expert_provider="openai-compatible") # Set all required keys and URLs monkeypatch.setenv('ANTHROPIC_API_KEY', 'anthropic-key') monkeypatch.setenv('OPENAI_API_KEY', 'openai-key') monkeypatch.setenv('OPENAI_API_BASE', 'http://test') # Should enable expert mode and use base openai key and URL expert_enabled, missing = validate_environment(args) assert expert_enabled assert not missing assert os.environ.get('EXPERT_OPENAI_API_KEY') == 'openai-key' assert os.environ.get('EXPERT_OPENAI_API_BASE') == 'http://test'