1+ """Test API key detection in different environments."""
2+
3+ import os
4+ import pytest
5+ from pathlib import Path
6+ from unittest .mock import patch , MagicMock
7+
8+ from orchestrator .utils .api_keys_flexible import (
9+ load_api_keys_optional ,
10+ get_missing_providers ,
11+ ensure_api_key
12+ )
13+
14+
15+ class TestAPIKeyDetection :
16+ """Test API key detection functionality."""
17+
18+ def test_load_api_keys_from_environment (self ):
19+ """Test loading API keys from environment variables."""
20+ # Set test environment variables
21+ test_keys = {
22+ "OPENAI_API_KEY" : "test-openai-key" ,
23+ "ANTHROPIC_API_KEY" : "test-anthropic-key" ,
24+ "GOOGLE_AI_API_KEY" : "test-google-key" ,
25+ "HF_TOKEN" : "test-hf-token"
26+ }
27+
28+ with patch .dict (os .environ , test_keys , clear = False ):
29+ # Should load from environment
30+ loaded_keys = load_api_keys_optional ()
31+
32+ assert len (loaded_keys ) >= 4 # At least our test keys
33+ assert loaded_keys .get ("openai" ) == "test-openai-key"
34+ assert loaded_keys .get ("anthropic" ) == "test-anthropic-key"
35+ assert loaded_keys .get ("google" ) == "test-google-key"
36+ assert loaded_keys .get ("huggingface" ) == "test-hf-token"
37+
38+ def test_load_api_keys_github_actions (self ):
39+ """Test loading API keys in GitHub Actions environment."""
40+ test_keys = {
41+ "GITHUB_ACTIONS" : "true" ,
42+ "CI" : "true" ,
43+ "OPENAI_API_KEY" : "gh-secret-openai" ,
44+ "ANTHROPIC_API_KEY" : "gh-secret-anthropic"
45+ }
46+
47+ with patch .dict (os .environ , test_keys , clear = True ):
48+ # Should detect GitHub Actions and use environment variables
49+ loaded_keys = load_api_keys_optional ()
50+
51+ assert loaded_keys .get ("openai" ) == "gh-secret-openai"
52+ assert loaded_keys .get ("anthropic" ) == "gh-secret-anthropic"
53+ # Should not have keys that weren't set
54+ assert "google" not in loaded_keys
55+ assert "huggingface" not in loaded_keys
56+
57+ def test_load_api_keys_from_dotenv_file (self ):
58+ """Test loading API keys from .env file."""
59+ # Create a temporary .env file
60+ import tempfile
61+ with tempfile .NamedTemporaryFile (mode = 'w' , suffix = '.env' , delete = False ) as f :
62+ f .write ("OPENAI_API_KEY=file-openai-key\n " )
63+ f .write ("ANTHROPIC_API_KEY=file-anthropic-key\n " )
64+ temp_env_path = Path (f .name )
65+
66+ try :
67+ # Mock the home directory .env path
68+ with patch ('orchestrator.utils.api_keys_flexible.Path.home' ) as mock_home :
69+ mock_home .return_value = temp_env_path .parent
70+ with patch .object (Path , 'exists' ) as mock_exists :
71+ def exists_side_effect (self ):
72+ # Only our temp file exists
73+ return str (self ) == str (temp_env_path .parent / ".orchestrator" / ".env" )
74+
75+ mock_exists .side_effect = exists_side_effect
76+
77+ # Clear environment to ensure we load from file
78+ with patch .dict (os .environ , {}, clear = True ):
79+ # This should load from our temp .env file
80+ loaded_keys = load_api_keys_optional ()
81+
82+ # Note: dotenv loading in tests can be tricky
83+ # The test mainly verifies the code path works
84+ finally :
85+ # Clean up
86+ temp_env_path .unlink ()
87+
88+ def test_get_missing_providers (self ):
89+ """Test identifying missing API key providers."""
90+ test_keys = {
91+ "OPENAI_API_KEY" : "test-key" ,
92+ "ANTHROPIC_API_KEY" : "test-key"
93+ }
94+
95+ # Mock the load_api_keys_optional to return only our test keys
96+ with patch ('orchestrator.utils.api_keys_flexible.load_api_keys_optional' ) as mock_load :
97+ mock_load .return_value = {"openai" : "test-key" , "anthropic" : "test-key" }
98+
99+ # Check all providers
100+ missing = get_missing_providers ()
101+ assert "google" in missing
102+ assert "huggingface" in missing
103+ assert "openai" not in missing
104+ assert "anthropic" not in missing
105+
106+ # Check specific providers
107+ missing_specific = get_missing_providers ({"openai" , "google" })
108+ assert "google" in missing_specific
109+ assert "openai" not in missing_specific
110+ assert len (missing_specific ) == 1
111+
112+ def test_ensure_api_key_success (self ):
113+ """Test ensuring API key when it exists."""
114+ with patch .dict (os .environ , {"OPENAI_API_KEY" : "test-key" }):
115+ key = ensure_api_key ("openai" )
116+ assert key == "test-key"
117+
118+ def test_ensure_api_key_missing (self ):
119+ """Test ensuring API key when it's missing."""
120+ # Mock load_api_keys_optional to return empty dict
121+ with patch ('orchestrator.utils.api_keys_flexible.load_api_keys_optional' ) as mock_load :
122+ mock_load .return_value = {}
123+
124+ with pytest .raises (EnvironmentError ) as exc_info :
125+ ensure_api_key ("openai" )
126+
127+ assert "Missing API key for openai" in str (exc_info .value )
128+ assert "OPENAI_API_KEY" in str (exc_info .value )
129+
130+ def test_debug_logging_output (self , capsys ):
131+ """Test that debug logging works correctly."""
132+ test_keys = {
133+ "GITHUB_ACTIONS" : "true" ,
134+ "OPENAI_API_KEY" : "test-key-12345" ,
135+ "ANTHROPIC_API_KEY" : "test-key-67890"
136+ }
137+
138+ with patch .dict (os .environ , test_keys , clear = True ):
139+ load_api_keys_optional ()
140+
141+ # Check debug output
142+ captured = capsys .readouterr ()
143+ assert "Running in GitHub Actions" in captured .out
144+ assert "Using environment variables from GitHub secrets" in captured .out
145+ assert "Found API key for openai (length: 14)" in captured .out
146+ assert "Found API key for anthropic (length: 14)" in captured .out
147+ assert "No API key found for google" in captured .out
148+ assert "Total API keys found: 2" in captured .out
149+ # Should not log actual key values
150+ assert "test-key-12345" not in captured .out
151+ assert "test-key-67890" not in captured .out
152+
153+
154+ class TestModelInitialization :
155+ """Test model initialization with API keys."""
156+
157+ @pytest .mark .skipif (
158+ not any (os .environ .get (key ) for key in ["OPENAI_API_KEY" , "ANTHROPIC_API_KEY" ]),
159+ reason = "No API keys available for model testing"
160+ )
161+ def test_init_models_with_api_keys (self ):
162+ """Test that init_models works when API keys are available."""
163+ from orchestrator import init_models
164+
165+ # Should initialize successfully
166+ registry = init_models ()
167+
168+ # Should have at least one model
169+ models = registry .list_models ()
170+ assert len (models ) > 0
171+
172+ # Check that API key providers have models
173+ available_keys = load_api_keys_optional ()
174+ for provider in available_keys :
175+ # Should have at least one model from each provider with keys
176+ provider_models = [m for m in models if m .startswith (f"{ provider } :" )]
177+ assert len (provider_models ) > 0 , f"No models found for provider { provider } "
178+
179+ def test_init_models_without_api_keys (self ):
180+ """Test that init_models handles missing API keys gracefully."""
181+ # Clear all API keys
182+ api_key_env_vars = ["OPENAI_API_KEY" , "ANTHROPIC_API_KEY" , "GOOGLE_AI_API_KEY" , "HF_TOKEN" ]
183+
184+ # Save current values
185+ saved_env = {key : os .environ .get (key ) for key in api_key_env_vars }
186+
187+ try :
188+ # Clear API keys
189+ for key in api_key_env_vars :
190+ if key in os .environ :
191+ del os .environ [key ]
192+
193+ from orchestrator import init_models
194+
195+ # Should still initialize (might only have local models)
196+ registry = init_models ()
197+
198+ # Should return a registry even with no models
199+ assert registry is not None
200+
201+ finally :
202+ # Restore environment
203+ for key , value in saved_env .items ():
204+ if value is not None :
205+ os .environ [key ] = value
0 commit comments