Files
wehub-resource-sync 7a0da7932b
OSV-Scanner (Scheduled) / scan-scheduled (push) Failing after 0s
Create Release / test-gate (push) Has been cancelled
Create Release / release-gate (push) Has been cancelled
Create Release / ci-gate (push) Has been cancelled
Create Release / version-check (push) Has been cancelled
Create Release / e2e-test-gate (push) Has been cancelled
Create Release / responsive-test-gate (push) Has been cancelled
Create Release / compat-test-gate (push) Has been cancelled
Create Release / compose-integration-gate (push) Has been cancelled
Create Release / vulture-gate (push) Has been cancelled
Create Release / build (push) Has been cancelled
Create Release / provenance (push) Has been cancelled
Create Release / prerelease-docker (push) Has been cancelled
Create Release / publish-docker (push) Has been cancelled
Create Release / create-release (push) Has been cancelled
Create Release / cleanup-changelog (push) Has been cancelled
Create Release / trigger-pypi (push) Has been cancelled
Create Release / monitor-pypi (push) Has been cancelled
Create Release / Clean up orphan prerelease tags and signatures (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Has been cancelled
Docker Tests (Consolidated) / Accessibility Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Unit Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Example Tests (push) Has been cancelled
Docker Tests (Consolidated) / Production Image Smoke Test (push) Has been cancelled
Docker Tests (Consolidated) / Infrastructure Tests (push) Has been cancelled
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Has been cancelled
Backwards Compatibility / Verify Encryption Constants (push) Has been cancelled
Backwards Compatibility / PyPI Version Compatibility (push) Has been cancelled
Backwards Compatibility / Database Migration Tests (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Docker Tests (Consolidated) / detect-changes (push) Has been cancelled
Docker Tests (Consolidated) / Build Test Image (push) Has been cancelled
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:08:55 +08:00

287 lines
11 KiB
Python

"""
Comprehensive test for OpenAI API key configuration and usage.
This test specifically verifies that OpenAI API keys can be:
1. Configured through settings
2. Properly passed to the OpenAI client
3. Used for actual research operations
"""
import pytest
from unittest.mock import Mock, patch
import os
from local_deep_research.config.llm_config import get_llm
@pytest.mark.requires_llm
class TestOpenAIAPIKeyUsage:
"""Test OpenAI API key configuration and usage throughout the system."""
@pytest.fixture
def openai_settings_snapshot(self):
"""Create settings snapshot with OpenAI configuration.
Uses simplified format (raw values) to bypass get_typed_setting_value
type coercion in get_setting_from_snapshot().
"""
return {
"search.tool": "searxng",
"llm.provider": "openai",
"llm.model": "gpt-3.5-turbo",
"llm.temperature": 0.7,
"llm.openai.api_key": "sk-test-1234567890abcdef",
"llm.openai.api_base": None,
"llm.openai.organization": None,
"llm.streaming": None,
"llm.max_retries": None,
"llm.request_timeout": None,
"llm.context_window_unrestricted": False,
"llm.context_window_size": 128000,
"llm.supports_max_tokens": True,
"llm.max_tokens": 100000,
"llm.provider.openai.context_window": 4096,
"research.iterations": 2,
"research.questions_per_iteration": 3,
"research.search_engines": ["wikipedia"],
"research.local_context": 2000,
"research.web_context": 2000,
"rate_limiting.llm_enabled": False,
}
def test_openai_api_key_in_llm_config(self, openai_settings_snapshot):
"""Test that OpenAI API key is properly passed to ChatOpenAI."""
with patch(
"local_deep_research.llm.providers.implementations.openai.ChatOpenAI"
) as mock_openai:
# Mock the LLM instance
mock_llm_instance = Mock()
mock_openai.return_value = mock_llm_instance
# Get LLM with settings
get_llm(settings_snapshot=openai_settings_snapshot)
# Verify ChatOpenAI was called with correct API key
mock_openai.assert_called_once()
call_args = mock_openai.call_args
# Check that API key was passed
assert call_args is not None
assert len(call_args) > 1
assert call_args[1]["api_key"] == "sk-test-1234567890abcdef"
assert call_args[1]["model"] == "gpt-3.5-turbo"
assert call_args[1]["temperature"] == 0.7
@pytest.mark.skipif(
os.environ.get("CI") == "true"
or os.environ.get("GITHUB_ACTIONS") == "true",
reason="Skipped in CI - requires environment variable configuration",
)
def test_openai_api_key_from_environment(self, openai_settings_snapshot):
"""Test fallback to environment variable if API key not in settings."""
# Modify settings to have no API key
settings_no_key = openai_settings_snapshot.copy()
settings_no_key["llm.openai.api_key"] = None
# Set LDR-prefixed environment variable
with patch.dict(
os.environ, {"LDR_LLM_OPENAI_API_KEY": "sk-env-test-key"}
):
with patch(
"local_deep_research.llm.providers.implementations.openai.ChatOpenAI"
) as mock_openai:
mock_llm_instance = Mock()
mock_openai.return_value = mock_llm_instance
get_llm(settings_snapshot=settings_no_key)
# Should use environment variable
call_args = mock_openai.call_args
assert call_args is not None
assert len(call_args) > 1
assert call_args[1]["api_key"] == "sk-env-test-key"
def test_openai_api_key_in_research_flow(self, openai_settings_snapshot):
"""Test that API key is properly passed through research flow to OpenAI."""
# Mock the ChatOpenAI class to verify API key is passed
with patch(
"local_deep_research.llm.providers.implementations.openai.ChatOpenAI"
) as mock_openai:
# Create a mock LLM instance
mock_llm = Mock()
mock_openai.return_value = mock_llm
# Simply get the LLM to verify the API key is passed
from local_deep_research.config.llm_config import get_llm
llm = get_llm(settings_snapshot=openai_settings_snapshot)
# Verify API key was passed correctly
assert mock_openai.called
assert (
mock_openai.call_args[1]["api_key"]
== "sk-test-1234567890abcdef"
)
assert mock_openai.call_args[1]["model"] == "gpt-3.5-turbo"
assert llm is not None
def test_openai_with_custom_endpoint(self, openai_settings_snapshot):
"""Test OpenAI with custom API endpoint (e.g., Azure OpenAI)."""
# Add custom endpoint to settings
custom_settings = openai_settings_snapshot.copy()
custom_settings["llm.openai.api_base"] = (
"https://custom-openai.azure.com"
)
custom_settings["llm.openai.api_key"] = "custom-azure-key"
with patch(
"local_deep_research.llm.providers.implementations.openai.ChatOpenAI"
) as mock_openai:
mock_llm_instance = Mock()
mock_openai.return_value = mock_llm_instance
get_llm(settings_snapshot=custom_settings)
# Verify custom endpoint and key were used
call_args = mock_openai.call_args
assert call_args is not None
assert len(call_args) > 1
assert call_args[1]["api_key"] == "custom-azure-key"
assert (
call_args[1].get("openai_api_base")
== "https://custom-openai.azure.com"
)
def test_openai_error_handling_invalid_key(self, openai_settings_snapshot):
"""Test error handling when OpenAI API key is invalid."""
with patch(
"local_deep_research.llm.providers.implementations.openai.ChatOpenAI"
) as mock_openai:
# Simulate OpenAI authentication error
mock_openai.side_effect = Exception("Invalid API key provided")
# Should raise exception with clear message
with pytest.raises(Exception, match="Invalid API key"):
get_llm(settings_snapshot=openai_settings_snapshot)
def test_openai_model_selection(self, openai_settings_snapshot):
"""Test different OpenAI model selections."""
models_to_test = [
"gpt-4",
"gpt-4-turbo-preview",
"gpt-3.5-turbo-16k",
"gpt-4-1106-preview",
]
for model in models_to_test:
settings = openai_settings_snapshot.copy()
settings["llm.model"] = model
with patch(
"local_deep_research.llm.providers.implementations.openai.ChatOpenAI"
) as mock_openai:
mock_llm_instance = Mock()
mock_openai.return_value = mock_llm_instance
get_llm(settings_snapshot=settings)
# Verify correct model was selected
assert mock_openai.call_args[1]["model"] == model
def test_openai_streaming_configuration(self, openai_settings_snapshot):
"""Test OpenAI streaming configuration."""
# Add streaming setting
streaming_settings = openai_settings_snapshot.copy()
streaming_settings["llm.streaming"] = True
with patch(
"local_deep_research.llm.providers.implementations.openai.ChatOpenAI"
) as mock_openai:
mock_llm_instance = Mock()
mock_openai.return_value = mock_llm_instance
get_llm(settings_snapshot=streaming_settings)
# Verify streaming was enabled
assert mock_openai.call_args[1].get("streaming") is True
def test_openai_retry_configuration(self, openai_settings_snapshot):
"""Test OpenAI retry and timeout configuration."""
# Add retry settings
retry_settings = openai_settings_snapshot.copy()
retry_settings["llm.max_retries"] = 3
retry_settings["llm.request_timeout"] = 60
with patch(
"local_deep_research.llm.providers.implementations.openai.ChatOpenAI"
) as mock_openai:
mock_llm_instance = Mock()
mock_openai.return_value = mock_llm_instance
get_llm(settings_snapshot=retry_settings)
# Verify retry configuration
call_args = mock_openai.call_args
assert call_args[1].get("max_retries") == 3
assert call_args[1].get("request_timeout") == 60
def test_openai_organization_id(self, openai_settings_snapshot):
"""Test OpenAI organization ID configuration."""
# Add organization ID
org_settings = openai_settings_snapshot.copy()
org_settings["llm.openai.organization"] = "org-test123"
with patch(
"local_deep_research.llm.providers.implementations.openai.ChatOpenAI"
) as mock_openai:
mock_llm_instance = Mock()
mock_openai.return_value = mock_llm_instance
get_llm(settings_snapshot=org_settings)
# Verify organization ID was passed
assert (
mock_openai.call_args[1].get("openai_organization")
== "org-test123"
)
def test_openai_full_integration(self, openai_settings_snapshot):
"""Full integration test verifying all OpenAI configuration parameters."""
# Test full OpenAI configuration
full_settings = openai_settings_snapshot.copy()
full_settings["llm.streaming"] = True
full_settings["llm.max_retries"] = 5
full_settings["llm.request_timeout"] = 120
full_settings["llm.openai.organization"] = "org-123"
with patch(
"local_deep_research.llm.providers.implementations.openai.ChatOpenAI"
) as mock_openai:
# Create mock LLM
mock_llm = Mock()
mock_openai.return_value = mock_llm
from local_deep_research.config.llm_config import get_llm
# Get LLM with full configuration
get_llm(settings_snapshot=full_settings)
# Verify all parameters were passed correctly
assert mock_openai.called
call_kwargs = mock_openai.call_args[1]
# Core parameters
assert call_kwargs["api_key"] == "sk-test-1234567890abcdef"
assert call_kwargs["model"] == "gpt-3.5-turbo"
assert call_kwargs["temperature"] == 0.7
# Additional parameters
assert call_kwargs["streaming"] is True
assert call_kwargs["max_retries"] == 5
assert call_kwargs["request_timeout"] == 120
assert call_kwargs["openai_organization"] == "org-123"
# Verify max_tokens was set
assert "max_tokens" in call_kwargs
assert call_kwargs["max_tokens"] > 0