8c4791d03f
Three test failures identified by the batched test suite, all rooted
in the Phase 3 lazy-import refactor of src/ai_client.py.
FIX 1: UnboundLocalError in _ensure_gemini_client
- _ensure_gemini_client had a latent bug: creds was assigned inside
`if _gemini_client is None:` but used on the next line. When the
client was already cached, the assignment was skipped and the next
line raised UnboundLocalError. Moved the Client() construction
inside the if block to match creds' scope.
- This affected test_ai_cache_tracking.py and (downstream)
test_gui_updates.py::test_telemetry_data_updates_correctly.
FIX 2: Phase 3 removed top-level `import requests` from ai_client.py.
- test_discussion_compression.py::test_discussion_compression_deepseek
did `patch("src.ai_client.requests.post", ...)` which no longer works.
- Updated the test to mock _require_warmed to return a fake requests
module with `.post()`, matching the new lazy-import pattern.
FIX 3: _require_warmed could not import dotted names like `google.genai.types`
- The google-genai library has a self-referential __init__.py that
does `from .client import Client` which transitively does
`from .types import HttpOptions`. Importing `google.genai.types`
FIRST (before the parent package is fully loaded) hit a "partially
initialized module" circular import.
- Enhanced _require_warmed to pre-import parent packages for dotted
names: walks `name.split(".")` and imports each parent (if not in
sys.modules) before the leaf import. O(n) extra imports per call
on first use; subsequent calls are O(1) sys.modules hit.
TESTS:
- test_ai_cache_tracking.py: 2/2 PASS
- test_discussion_compression.py: 4/4 PASS
- 29/29 PASS across the sampled test files that were failing
(test_subagent_summarization, test_tool_access_exclusion,
test_tier4_interceptor, test_gui2_mcp, test_gui_updates,
test_headless_service)
ARCHITECTURAL NOTE: The _require_warmed enhancement is a small
but important robustness fix. The google-genai library's
__init__.py chain is a known source of fragility; the parent-
pre-import pattern is the recommended workaround.
77 lines
3.1 KiB
Python
77 lines
3.1 KiB
Python
import pytest
|
|
from unittest.mock import MagicMock, patch
|
|
from src import ai_client
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def reset_ai_client():
|
|
ai_client.reset_session()
|
|
yield
|
|
ai_client.reset_session()
|
|
|
|
def test_discussion_compression_gemini():
|
|
ai_client.set_provider("gemini", "gemini-2.5-flash-lite")
|
|
|
|
mock_gemini = MagicMock()
|
|
mock_response = MagicMock()
|
|
mock_response.text = "This is a compressed summary."
|
|
mock_gemini.models.generate_content.return_value = mock_response
|
|
|
|
with patch("src.ai_client._gemini_client", mock_gemini), \
|
|
patch("src.ai_client._ensure_gemini_client"):
|
|
result = ai_client.run_discussion_compression("User: Hello\nAI: Hi there!")
|
|
|
|
assert result == "This is a compressed summary."
|
|
mock_gemini.models.generate_content.assert_called_once()
|
|
args, kwargs = mock_gemini.models.generate_content.call_args
|
|
assert "[HISTORY]" in kwargs["contents"]
|
|
assert "User: Hello" in kwargs["contents"]
|
|
|
|
def test_discussion_compression_anthropic():
|
|
ai_client.set_provider("anthropic", "claude-3-haiku")
|
|
|
|
mock_anthropic = MagicMock()
|
|
mock_response = MagicMock()
|
|
mock_block = MagicMock()
|
|
mock_block.text = "Anthropic summary."
|
|
mock_response.content = [mock_block]
|
|
mock_anthropic.messages.create.return_value = mock_response
|
|
|
|
with patch("src.ai_client._anthropic_client", mock_anthropic), \
|
|
patch("src.ai_client._ensure_anthropic_client"):
|
|
result = ai_client.run_discussion_compression("Some history")
|
|
|
|
assert result == "Anthropic summary."
|
|
mock_anthropic.messages.create.assert_called_once()
|
|
kwargs = mock_anthropic.messages.create.call_args[1]
|
|
assert "Some history" in kwargs["messages"][0]["content"]
|
|
|
|
def test_discussion_compression_deepseek():
|
|
ai_client.set_provider("deepseek", "deepseek-chat")
|
|
|
|
mock_response = MagicMock()
|
|
mock_response.json.return_value = {"choices": [{"message": {"content": "DeepSeek summary."}}]}
|
|
|
|
# Phase 3 (startup_speedup_20260606) removed the top-level 'import requests'
|
|
# from src/ai_client.py. The module is now resolved via _require_warmed()
|
|
# at call time. We mock _require_warmed to return a fake requests module
|
|
# that has the .post() method.
|
|
mock_requests_mod = MagicMock()
|
|
mock_requests_mod.post.return_value = mock_response
|
|
|
|
with patch("src.ai_client._require_warmed", return_value=mock_requests_mod), \
|
|
patch("src.ai_client._load_credentials", return_value={"deepseek": {"api_key": "test"}}):
|
|
result = ai_client.run_discussion_compression("DeepSeek history")
|
|
|
|
assert result == "DeepSeek summary."
|
|
|
|
def test_discussion_compression_gemini_cli():
|
|
ai_client.set_provider("gemini_cli", "gemini-1.5-flash")
|
|
|
|
mock_adapter = MagicMock()
|
|
mock_adapter.send.return_value = {"text": "CLI summary."}
|
|
|
|
with patch("src.ai_client.GeminiCliAdapter", return_value=mock_adapter):
|
|
result = ai_client.run_discussion_compression("CLI history")
|
|
|
|
assert result == "CLI summary."
|