CognitiveKernel-Launchpad / ck_pro /tests /test_env_variable_fallback.py
charSLee013
feat: complete Hugging Face Spaces deployment with production-ready CognitiveKernel-Launchpad
1ea26af
"""
Test cases for environment variable fallback in LLM configuration.
Phase 1, Task 1.2: Design test cases for environment variable fallback scenarios
"""
import os
import pytest
from unittest.mock import patch
from ck_pro.config.settings import Settings, LLMConfig
class TestEnvironmentVariableFallback:
"""Test environment variable fallback behavior in _build_llm_config"""
def setup_method(self):
"""Clean up environment variables before each test"""
env_vars = ["OPENAI_API_BASE", "OPENAI_API_KEY", "OPENAI_API_MODEL"]
for var in env_vars:
os.environ.pop(var, None)
def teardown_method(self):
"""Clean up environment variables after each test"""
env_vars = ["OPENAI_API_BASE", "OPENAI_API_KEY", "OPENAI_API_MODEL"]
for var in env_vars:
os.environ.pop(var, None)
# Test Case 1.1: Environment variables used when no config provided
def test_env_vars_used_when_no_config(self):
"""Test that environment variables are used when no TOML config is provided"""
# Setup environment variables
os.environ["OPENAI_API_BASE"] = "https://test.openai.com/v1/chat/completions"
os.environ["OPENAI_API_KEY"] = "test-key-123"
os.environ["OPENAI_API_MODEL"] = "test-model-456"
# Call with empty config
result = Settings._build_llm_config({}, {"temperature": 0.5})
# Verify environment variables are used
assert result.call_target == "https://test.openai.com/v1/chat/completions"
assert result.api_key == "test-key-123"
assert result.model == "test-model-456"
assert result.extract_body == {"temperature": 0.5}
# Test Case 1.2: Environment variables not used when config provided
def test_env_vars_ignored_when_config_provided(self):
"""Test that environment variables are ignored when TOML config is provided"""
# Setup environment variables (should be ignored)
os.environ["OPENAI_API_BASE"] = "https://env.openai.com/v1/chat/completions"
os.environ["OPENAI_API_KEY"] = "env-key-123"
os.environ["OPENAI_API_MODEL"] = "env-model-456"
# Provide TOML config (should take precedence)
config = {
"call_target": "https://toml.openai.com/v1/chat/completions",
"api_key": "toml-key-789",
"model": "toml-model-999"
}
result = Settings._build_llm_config(config, {"temperature": 0.5})
# Verify TOML config is used, not environment variables
assert result.call_target == "https://toml.openai.com/v1/chat/completions"
assert result.api_key == "toml-key-789"
assert result.model == "toml-model-999"
# Test Case 1.3: Partial environment variable usage
def test_partial_env_var_usage(self):
"""Test mixing environment variables with some config values"""
# Setup only some environment variables
os.environ["OPENAI_API_KEY"] = "env-key-only"
# Don't set OPENAI_API_BASE or OPENAI_API_MODEL
# Provide partial TOML config
config = {
"call_target": "https://toml.openai.com/v1/chat/completions",
"model": "toml-model"
# api_key not provided in config
}
result = Settings._build_llm_config(config, {"temperature": 0.5})
# Verify mix of config and environment variables
assert result.call_target == "https://toml.openai.com/v1/chat/completions" # From config
assert result.api_key == "env-key-only" # From environment
assert result.model == "toml-model" # From config
# Test Case 1.4: No environment variables set (fallback to defaults)
def test_no_env_vars_fallback_to_defaults(self):
"""Test fallback to hardcoded defaults when no environment variables are set"""
# Don't set any environment variables
# Call with empty config
result = Settings._build_llm_config({}, {"temperature": 0.7})
# Verify hardcoded defaults are used
assert result.call_target == "https://api.openai.com/v1/chat/completions"
assert result.api_key == "your-api-key-here"
assert result.model == "gpt-4o-mini"
assert result.extract_body == {"temperature": 0.7}
# Test Case 1.5: Environment variables with extract_body merging
def test_env_vars_with_extract_body_merging(self):
"""Test environment variables work correctly with extract_body merging"""
os.environ["OPENAI_API_BASE"] = "https://test.openai.com/v1/chat/completions"
os.environ["OPENAI_API_KEY"] = "test-key"
os.environ["OPENAI_API_MODEL"] = "test-model"
# Provide config with extract_body
config = {
"extract_body": {"temperature": 0.8, "max_tokens": 2000}
}
result = Settings._build_llm_config(config, {"temperature": 0.5, "top_p": 0.9})
# Verify environment variables are used
assert result.call_target == "https://test.openai.com/v1/chat/completions"
assert result.api_key == "test-key"
assert result.model == "test-model"
# Verify extract_body merging: config overrides default
assert result.extract_body == {"temperature": 0.8, "max_tokens": 2000, "top_p": 0.9}
# Test Case 1.6: HTTP validation still works with environment variables
def test_http_validation_with_env_vars(self):
"""Test that HTTP validation still works when using environment variables"""
# Set invalid HTTP URL in environment
os.environ["OPENAI_API_BASE"] = "invalid-url-without-http"
config = {} # No config provided, should use env var
# Should raise ValueError for invalid HTTP URL
with pytest.raises(ValueError, match="call_target must be HTTP URL"):
Settings._build_llm_config(config, {"temperature": 0.5})
# Test Case 1.7: Priority order: TOML > env vars > defaults
def test_priority_order_comprehensive(self):
"""Comprehensive test of priority order: TOML > env vars > defaults"""
# Setup environment variables
os.environ["OPENAI_API_BASE"] = "https://env.openai.com/v1/chat/completions"
os.environ["OPENAI_API_KEY"] = "env-key"
os.environ["OPENAI_API_MODEL"] = "env-model"
# Test 1: All from TOML config (highest priority)
config1 = {
"call_target": "https://toml.openai.com/v1/chat/completions",
"api_key": "toml-key",
"model": "toml-model"
}
result1 = Settings._build_llm_config(config1, {"temperature": 0.5})
assert result1.call_target == "https://toml.openai.com/v1/chat/completions"
assert result1.api_key == "toml-key"
assert result1.model == "toml-model"
# Test 2: Mix of TOML and env vars
config2 = {
"call_target": "https://toml.openai.com/v1/chat/completions"
# api_key and model not provided, should use env vars
}
result2 = Settings._build_llm_config(config2, {"temperature": 0.5})
assert result2.call_target == "https://toml.openai.com/v1/chat/completions" # TOML
assert result2.api_key == "env-key" # Env var
assert result2.model == "env-model" # Env var
# Test 3: All from env vars
result3 = Settings._build_llm_config({}, {"temperature": 0.5})
assert result3.call_target == "https://env.openai.com/v1/chat/completions"
assert result3.api_key == "env-key"
assert result3.model == "env-model"
# Test 4: No env vars set, fallback to defaults
# Clean up env vars
os.environ.pop("OPENAI_API_BASE", None)
os.environ.pop("OPENAI_API_KEY", None)
os.environ.pop("OPENAI_API_MODEL", None)
result4 = Settings._build_llm_config({}, {"temperature": 0.5})
assert result4.call_target == "https://api.openai.com/v1/chat/completions" # Default
assert result4.api_key == "your-api-key-here" # Default
assert result4.model == "gpt-4o-mini" # Default
# Test Case 1.8: Backward compatibility with call_kwargs
def test_backward_compatibility_call_kwargs(self):
"""Test that legacy call_kwargs still works with environment variables"""
os.environ["OPENAI_API_KEY"] = "env-key"
config = {
"call_kwargs": {"temperature": 0.9, "max_tokens": 1500}
}
result = Settings._build_llm_config(config, {"temperature": 0.5})
# Verify environment variable is used
assert result.api_key == "env-key"
# Verify call_kwargs are merged with default extract_body
assert result.extract_body["temperature"] == 0.9 # From call_kwargs
assert result.extract_body["max_tokens"] == 1500 # From call_kwargs
class TestInheritanceWithEnvironmentVariables:
"""Test environment variables work correctly with inheritance"""
def setup_method(self):
"""Clean up environment variables"""
env_vars = ["OPENAI_API_BASE", "OPENAI_API_KEY", "OPENAI_API_MODEL"]
for var in env_vars:
os.environ.pop(var, None)
def teardown_method(self):
"""Clean up environment variables"""
env_vars = ["OPENAI_API_BASE", "OPENAI_API_KEY", "OPENAI_API_MODEL"]
for var in env_vars:
os.environ.pop(var, None)
def test_inheritance_priority_over_env_vars(self):
"""Test that inheritance has priority over environment variables"""
# This test verifies that the inheritance logic in to_ckagent_kwargs()
# works correctly with the new environment variable fallback
# Setup environment variables
os.environ["OPENAI_API_KEY"] = "env-key"
# Create settings with CK model having api_key, web model inheriting
settings = Settings()
settings.ck.model = LLMConfig(
call_target="https://ck.openai.com/v1/chat/completions",
api_key="ck-key", # This should be inherited by web model
model="ck-model"
)
# Web model should inherit from CK model, not use env var
web_model_dict = {
"call_target": "https://web.openai.com/v1/chat/completions",
"model": "web-model"
# api_key not specified, should inherit from ck.model
}
web_config = Settings._build_llm_config(web_model_dict, {"temperature": 0.0})
# The inheritance happens in to_ckagent_kwargs(), so this test
# verifies that env vars don't interfere with inheritance logic
assert web_config.call_target == "https://web.openai.com/v1/chat/completions"
assert web_config.model == "web-model"
# api_key should be inherited from ck.model, not from env var
# (This test assumes inheritance logic is working correctly)
def test_inheritance_with_model_field(self):
"""Test that model field is properly inherited from parent to child configs"""
# Create settings with parent model
settings = Settings()
settings.ck.model = LLMConfig(
call_target="https://parent.openai.com/v1/chat/completions",
api_key="parent-key",
model="parent-model"
)
# Create child web model without model specified (should inherit)
settings.web.model = LLMConfig(
call_target="https://web.openai.com/v1/chat/completions",
api_key="web-key",
model="" # Empty model should trigger inheritance
)
# Get kwargs and check inheritance
kwargs = settings.to_ckagent_kwargs()
web_agent_config = kwargs.get("web_agent", {})
web_model_config = web_agent_config.get("model", {})
# Verify that model was inherited from parent
assert web_model_config.get("model") == "parent-model", f"Expected 'parent-model', got {web_model_config.get('model')}"
# Verify other fields are preserved
assert web_model_config.get("call_target") == "https://web.openai.com/v1/chat/completions"
assert web_model_config.get("api_key") == "web-key"
if __name__ == "__main__":
pytest.main([__file__, "-v"])