import os from dataclasses import dataclass from typing import Optional import pytest from ra_aid.env import validate_environment @dataclass class MockArgs: provider: str expert_provider: str 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(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", "TAVILY_API_KEY", "ANTHROPIC_MODEL", ] for var in env_vars: monkeypatch.delenv(var, raising=False) def test_anthropic_validation(clean_env, monkeypatch): args = MockArgs( provider="anthropic", expert_provider="openai", model="claude-3-haiku-20240307" ) # Should fail without API key with pytest.raises(SystemExit): validate_environment(args) # Should pass with API key and model monkeypatch.setenv("ANTHROPIC_API_KEY", "test-key") expert_enabled, expert_missing, web_research_enabled, web_research_missing = ( validate_environment(args) ) assert not expert_enabled assert not web_research_enabled assert "TAVILY_API_KEY environment variable is not set" in web_research_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, expert_missing, web_research_enabled, web_research_missing = ( validate_environment(args) ) assert expert_enabled assert not expert_missing assert not web_research_enabled assert "TAVILY_API_KEY environment variable is not set" in web_research_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, expert_missing, web_research_enabled, web_research_missing = ( validate_environment(args) ) assert expert_enabled assert not expert_missing assert not web_research_enabled assert "TAVILY_API_KEY environment variable is not set" in web_research_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, expert_missing, web_research_enabled, web_research_missing = ( validate_environment(args) ) assert expert_enabled assert not expert_missing assert not web_research_enabled assert "TAVILY_API_KEY environment variable is not set" in web_research_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, expert_missing, web_research_enabled, web_research_missing = ( validate_environment(args) ) assert expert_enabled assert not expert_missing assert not web_research_enabled assert "TAVILY_API_KEY environment variable is not set" in web_research_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", expert_model="claude-3-haiku-20240307", ) # Set base API key for main provider and expert provider monkeypatch.setenv("OPENAI_API_KEY", "openai-key") monkeypatch.setenv("ANTHROPIC_API_KEY", "anthropic-key") monkeypatch.setenv("ANTHROPIC_MODEL", "claude-3-haiku-20240307") # Should enable expert mode with fallback to ANTHROPIC base key expert_enabled, expert_missing, web_research_enabled, web_research_missing = ( validate_environment(args) ) assert expert_enabled assert not expert_missing assert not web_research_enabled assert "TAVILY_API_KEY environment variable is not set" in web_research_missing # 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, expert_missing, web_research_enabled, web_research_missing = ( validate_environment(args) ) assert expert_enabled assert not expert_missing assert not web_research_enabled assert "TAVILY_API_KEY environment variable is not set" in web_research_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, web_research_enabled, web_research_missing = ( validate_environment(args) ) assert expert_enabled assert not expert_missing assert not web_research_enabled assert "TAVILY_API_KEY environment variable is not set" in web_research_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, expert_missing, web_research_enabled, web_research_missing = ( validate_environment(args) ) assert expert_enabled assert not expert_missing assert not web_research_enabled assert "TAVILY_API_KEY environment variable is not set" in web_research_missing assert os.environ.get("EXPERT_OPENAI_API_KEY") == "expert-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", model="claude-3-haiku-20240307" ) # 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, expert_missing, web_research_enabled, web_research_missing = ( validate_environment(args) ) assert expert_enabled assert not expert_missing assert not web_research_enabled assert "TAVILY_API_KEY environment variable is not set" in web_research_missing 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", model="claude-3-haiku-20240307", ) # 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, expert_missing, web_research_enabled, web_research_missing = ( validate_environment(args) ) assert expert_enabled assert not expert_missing assert not web_research_enabled assert "TAVILY_API_KEY environment variable is not set" in web_research_missing assert os.environ.get("EXPERT_OPENAI_API_KEY") == "openai-key" assert os.environ.get("EXPERT_OPENAI_API_BASE") == "http://test"