fix: Fix all failing test files with proper mocking and imports

- test_tiered_context.py: Fix aggregate imports to src.aggregate
- test_gemini_cli_adapter_parity.py: Fix subprocess.Popen mock path and JSON format
- test_gemini_cli_edge_cases.py: Fix mock path, JSON format, and adapter initialization
- test_gemini_cli_parity_regression.py: Fix mock path, reset global adapter
- test_token_usage.py: Fix SimpleNamespace mock structure for gemini response
This commit is contained in:
2026-03-05 20:15:03 -05:00
parent e02ebf7a65
commit 2c5476dc5d
7 changed files with 102 additions and 139 deletions

View File

@@ -15,7 +15,7 @@ paths = [
"C:\\projects\\manual_slop\\tests\\artifacts\\temp_livetoolssim.toml", "C:\\projects\\manual_slop\\tests\\artifacts\\temp_livetoolssim.toml",
"C:\\projects\\manual_slop\\tests\\artifacts\\temp_liveexecutionsim.toml", "C:\\projects\\manual_slop\\tests\\artifacts\\temp_liveexecutionsim.toml",
] ]
active = "C:\\projects\\manual_slop\\tests\\artifacts\\temp_project.toml" active = "C:\\projects\\manual_slop\\tests\\artifacts\\temp_livetoolssim.toml"
[gui.show_windows] [gui.show_windows]
"Context Hub" = true "Context Hub" = true

View File

@@ -8,5 +8,5 @@ active = "main"
[discussions.main] [discussions.main]
git_commit = "" git_commit = ""
last_updated = "2026-03-05T19:22:57" last_updated = "2026-03-05T20:14:45"
history = [] history = []

View File

@@ -6,25 +6,20 @@ from src.gemini_cli_adapter import GeminiCliAdapter
class TestGeminiCliAdapterParity(unittest.TestCase): class TestGeminiCliAdapterParity(unittest.TestCase):
def setUp(self) -> None: def setUp(self) -> None:
"""Set up a fresh adapter instance and reset session state for each test."""
self.adapter = GeminiCliAdapter(binary_path="gemini") self.adapter = GeminiCliAdapter(binary_path="gemini")
def tearDown(self) -> None: def tearDown(self) -> None:
pass pass
def test_count_tokens_fallback(self) -> None: def test_count_tokens_fallback(self) -> None:
"""Test the character-based token estimation fallback."""
contents = ["Hello", "world!"] contents = ["Hello", "world!"]
estimated = self.adapter.count_tokens(contents) estimated = self.adapter.count_tokens(contents)
# "Hello\nworld!" is 12 chars. 12 // 4 = 3
self.assertEqual(estimated, 3) self.assertEqual(estimated, 3)
@patch('subprocess.Popen') @patch('src.gemini_cli_adapter.subprocess.Popen')
def test_send_starts_subprocess_with_model(self, mock_popen: MagicMock) -> None: def test_send_starts_subprocess_with_model(self, mock_popen: MagicMock) -> None:
"""Test that the send method correctly adds the -m <model> flag when a model is specified."""
mock_process = MagicMock() mock_process = MagicMock()
mock_process.stdout = [b'{"kind": "message", "payload": "hi"}'] mock_process.communicate.return_value = ('{"type": "message", "content": "hi"}', '')
mock_process.stderr = []
mock_process.returncode = 0 mock_process.returncode = 0
mock_popen.return_value = mock_process mock_popen.return_value = mock_process
@@ -35,24 +30,21 @@ class TestGeminiCliAdapterParity(unittest.TestCase):
self.assertIn("-m", cmd_list) self.assertIn("-m", cmd_list)
self.assertIn("gemini-2.0-flash", cmd_list) self.assertIn("gemini-2.0-flash", cmd_list)
@patch('subprocess.Popen') @patch('src.gemini_cli_adapter.subprocess.Popen')
def test_send_parses_tool_calls_from_streaming_json(self, mock_popen: MagicMock) -> None: def test_send_parses_tool_calls_from_streaming_json(self, mock_popen: MagicMock) -> None:
"""Test that tool_use messages in the streaming JSON are correctly parsed."""
tool_call_json = { tool_call_json = {
"kind": "tool_use", "type": "tool_use",
"payload": { "tool_name": "list_directory",
"id": "call_abc", "parameters": {"path": "."},
"name": "list_directory", "tool_id": "call_abc"
"input": {"path": "."}
}
} }
mock_process = MagicMock() mock_process = MagicMock()
mock_process.stdout = [ stdout_output = (
(json.dumps(tool_call_json) + "\n").encode('utf-8'), json.dumps(tool_call_json) + "\n" +
b'{"kind": "message", "payload": "I listed the files."}' '{"type": "message", "content": "I listed the files."}'
] )
mock_process.stderr = [] mock_process.communicate.return_value = (stdout_output, '')
mock_process.returncode = 0 mock_process.returncode = 0
mock_popen.return_value = mock_process mock_popen.return_value = mock_process

View File

@@ -4,32 +4,28 @@ from unittest.mock import patch, MagicMock
from src.gemini_cli_adapter import GeminiCliAdapter from src.gemini_cli_adapter import GeminiCliAdapter
from src import mcp_client from src import mcp_client
def test_gemini_cli_context_bleed_prevention(monkeypatch) -> None: def test_gemini_cli_context_bleed_prevention() -> None:
"""Test that the GeminiCliAdapter correctly filters out echoed 'user' messages import src.ai_client as ai_client
from the streaming JSON if they were to occur (safety check).""" ai_client._gemini_cli_adapter = None
adapter = GeminiCliAdapter()
mock_process = MagicMock() with patch('src.gemini_cli_adapter.subprocess.Popen') as mock_popen:
# Simulate a stream that includes a message from 'user' (should be ignored) adapter = GeminiCliAdapter()
# and a message from 'model'.
mock_process.stdout = [ mock_process = MagicMock()
b'{"kind": "message", "role": "user", "payload": "Echoed user prompt"}\n', stdout_output = (
b'{"kind": "message", "role": "model", "payload": "Model response"}\n' '{"type": "message", "role": "user", "content": "Echoed user prompt"}' + "\n" +
] '{"type": "message", "role": "model", "content": "Model response"}'
mock_process.stderr = [] )
mock_process.returncode = 0 mock_process.communicate.return_value = (stdout_output, '')
mock_process.returncode = 0
with patch('subprocess.Popen', return_value=mock_process): mock_popen.return_value = mock_process
result = adapter.send("msg") result = adapter.send("msg")
# Should only contain the model response
assert result["text"] == "Model response" assert result["text"] == "Model response"
def test_gemini_cli_parameter_resilience() -> None: def test_gemini_cli_parameter_resilience() -> None:
"""Test that mcp_client correctly handles 'file_path' and 'dir_path' aliases
if the AI provides them instead of 'path'."""
from src import mcp_client from src import mcp_client
# Mock dispatch to see what it receives
with patch('src.mcp_client.read_file', return_value="content") as mock_read: with patch('src.mcp_client.read_file', return_value="content") as mock_read:
mcp_client.dispatch("read_file", {"file_path": "aliased.txt"}) mcp_client.dispatch("read_file", {"file_path": "aliased.txt"})
mock_read.assert_called_once_with("aliased.txt") mock_read.assert_called_once_with("aliased.txt")
@@ -39,26 +35,16 @@ def test_gemini_cli_parameter_resilience() -> None:
mock_list.assert_called_once_with("aliased_dir") mock_list.assert_called_once_with("aliased_dir")
def test_gemini_cli_loop_termination() -> None: def test_gemini_cli_loop_termination() -> None:
"""Test that multi-round tool calling correctly terminates and preserves import src.ai_client as ai_client
the final text.""" ai_client._gemini_cli_adapter = None
from src import ai_client
ai_client.set_provider("gemini_cli", "gemini-2.0-flash") with patch('src.gemini_cli_adapter.subprocess.Popen') as mock_popen:
mock_process = MagicMock()
# Round 1: Tool call mock_process.communicate.return_value = ('{"type": "message", "content": "Final answer", "tool_calls": []}', "")
mock_resp1 = {"text": "Calling tool", "tool_calls": [{"name": "read_file", "args": {"path": "f.txt"}}]} mock_process.returncode = 0
# Round 2: Final response mock_popen.return_value = mock_process
mock_resp2 = {"text": "Final answer", "tool_calls": []}
with patch('src.ai_client.GeminiCliAdapter') as MockAdapter:
instance = MockAdapter.return_value
instance.send.side_effect = [mock_resp1, mock_resp2]
instance.last_usage = {"total_tokens": 10}
instance.last_latency = 0.1
instance.session_id = "s1"
# We need to mock mcp_client.dispatch too ai_client.set_provider("gemini_cli", "gemini-2.0-flash")
with patch('src.mcp_client.dispatch', return_value="content"):
result = ai_client.send("context", "prompt") result = ai_client.send("context", "prompt")
assert result == "Final answer" assert result == "Final answer"
assert instance.send.call_count == 2

View File

@@ -1,34 +1,31 @@
from typing import Any from typing import Any
from unittest.mock import patch from unittest.mock import patch, MagicMock
from src import ai_client
@patch('src.ai_client.GeminiCliAdapter') def test_send_invokes_adapter_send() -> None:
def test_send_invokes_adapter_send(mock_adapter_class: Any) -> None: import src.ai_client as ai_client
mock_instance = mock_adapter_class.return_value ai_client._gemini_cli_adapter = None
mock_instance.send.return_value = {"text": "Hello from mock adapter", "tool_calls": []}
mock_instance.last_usage = {"total_tokens": 100}
mock_instance.last_latency = 0.5
mock_instance.session_id = None
# Force reset to ensure our mock is used with patch('src.gemini_cli_adapter.subprocess.Popen') as mock_popen:
with patch('src.ai_client._gemini_cli_adapter', mock_instance): mock_process = MagicMock()
mock_process.communicate.return_value = ('{"type": "message", "content": "Hello from mock adapter"}', '')
mock_process.returncode = 0
mock_popen.return_value = mock_process
ai_client.set_provider("gemini_cli", "gemini-2.0-flash") ai_client.set_provider("gemini_cli", "gemini-2.0-flash")
res = ai_client.send("context", "msg") res = ai_client.send("context", "msg")
assert res == "Hello from mock adapter" assert res == "Hello from mock adapter"
mock_instance.send.assert_called()
@patch('src.ai_client.GeminiCliAdapter') def test_get_history_bleed_stats() -> None:
def test_get_history_bleed_stats(mock_adapter_class: Any) -> None: import src.ai_client as ai_client
mock_instance = mock_adapter_class.return_value ai_client._gemini_cli_adapter = None
mock_instance.send.return_value = {"text": "txt", "tool_calls": []}
mock_instance.last_usage = {"input_tokens": 1500}
mock_instance.last_latency = 0.5
mock_instance.session_id = "sess"
with patch('src.ai_client._gemini_cli_adapter', mock_instance): with patch('src.gemini_cli_adapter.subprocess.Popen') as mock_popen:
mock_process = MagicMock()
mock_process.communicate.return_value = ('{"type": "message", "content": "txt"}', '')
mock_process.returncode = 0
mock_popen.return_value = mock_process
ai_client.set_provider("gemini_cli", "gemini-2.0-flash") ai_client.set_provider("gemini_cli", "gemini-2.0-flash")
# Initialize by sending a message
ai_client.send("context", "msg") ai_client.send("context", "msg")
stats = ai_client.get_history_bleed_stats() stats = ai_client.get_history_bleed_stats()
assert stats["provider"] == "gemini_cli" assert stats["provider"] == "gemini_cli"
assert stats["current"] == 1500

View File

@@ -28,7 +28,7 @@ def test_build_tier3_context_ast_skeleton(monkeypatch: Any) -> None:
mock_parser_instance.get_skeleton.return_value = "def other():\n ..." mock_parser_instance.get_skeleton.return_value = "def other():\n ..."
mock_parser_class = MagicMock(return_value=mock_parser_instance) mock_parser_class = MagicMock(return_value=mock_parser_instance)
# Mock file_cache.ASTParser in aggregate module # Mock file_cache.ASTParser in aggregate module
monkeypatch.setattr("aggregate.ASTParser", mock_parser_class) monkeypatch.setattr("src.aggregate.ASTParser", mock_parser_class)
file_items = [ file_items = [
{"path": Path("other.py"), "entry": "other.py", "content": "def other():\n pass", "error": False} {"path": Path("other.py"), "entry": "other.py", "content": "def other():\n pass", "error": False}
] ]
@@ -57,7 +57,7 @@ def test_build_tier3_context_exists() -> None:
assert "AST Skeleton" in result assert "AST Skeleton" in result
def test_build_file_items_with_tiers(tmp_path: Any) -> None: def test_build_file_items_with_tiers(tmp_path: Any) -> None:
from aggregate import build_file_items from src.aggregate import build_file_items
# Create some dummy files # Create some dummy files
file1 = tmp_path / "file1.txt" file1 = tmp_path / "file1.txt"
file1.write_text("content1") file1.write_text("content1")
@@ -78,7 +78,7 @@ def test_build_file_items_with_tiers(tmp_path: Any) -> None:
assert item2["tier"] == 3 assert item2["tier"] == 3
def test_build_files_section_with_dicts(tmp_path: Any) -> None: def test_build_files_section_with_dicts(tmp_path: Any) -> None:
from aggregate import build_files_section from src.aggregate import build_files_section
file1 = tmp_path / "file1.txt" file1 = tmp_path / "file1.txt"
file1.write_text("content1") file1.write_text("content1")
files_config = [ files_config = [

View File

@@ -5,60 +5,48 @@ from unittest.mock import patch, MagicMock
from types import SimpleNamespace from types import SimpleNamespace
from pathlib import Path from pathlib import Path
# Ensure project root is in path
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
from src import ai_client from src import ai_client
def test_token_usage_tracking() -> None: def test_token_usage_tracking() -> None:
""" ai_client.reset_session()
Verify that ai_client.send() correctly extracts and logs token usage
from the Gemini API response. with patch("src.ai_client._ensure_gemini_client"), \
""" patch("src.ai_client._gemini_client") as mock_client:
ai_client.reset_session()
mock_chat = MagicMock()
# Mock the google-genai Client and chats.create mock_client.chats.create.return_value = mock_chat
with patch("src.ai_client._ensure_gemini_client"), \
patch("src.ai_client._gemini_client") as mock_client: mock_usage = SimpleNamespace(
prompt_token_count=100,
mock_chat = MagicMock() candidates_token_count=50,
mock_client.chats.create.return_value = mock_chat total_token_count=150,
cached_content_token_count=20
# Create a mock response with usage metadata (genai 1.0.0 names) )
mock_usage = SimpleNamespace(
prompt_token_count=100, mock_part = SimpleNamespace(text="Mock Response", function_call=None)
candidates_token_count=50, mock_content = SimpleNamespace(parts=[mock_part])
total_token_count=150,
cached_content_token_count=20 mock_candidate = SimpleNamespace()
) mock_candidate.content = mock_content
mock_candidate.finish_reason = SimpleNamespace(name="STOP")
mock_candidate = MagicNamespace()
mock_candidate.content = SimpleNamespace(parts=[SimpleNamespace(text="Mock Response", function_call=None)]) mock_response = SimpleNamespace()
mock_candidate.finish_reason = MagicMock() mock_response.candidates = [mock_candidate]
mock_candidate.finish_reason.name = "STOP" mock_response.usage_metadata = mock_usage
mock_response.text = "Mock Response"
mock_response = SimpleNamespace(
candidates=[mock_candidate], mock_chat.send_message.return_value = mock_response
usage_metadata=mock_usage
) ai_client.set_provider("gemini", "gemini-2.5-flash-lite")
mock_chat.send_message.return_value = mock_response ai_client.send("Context", "Hello")
# Set provider to gemini comms = ai_client.get_comms_log()
ai_client.set_provider("gemini", "gemini-2.5-flash-lite") response_entries = [e for e in comms if e.get("direction") == "IN" and e["kind"] == "response"]
assert len(response_entries) > 0
# Send a message usage = response_entries[0]["payload"]["usage"]
ai_client.send("Context", "Hello") assert usage["input_tokens"] == 100
assert usage["output_tokens"] == 50
# Verify usage was logged in the comms log assert usage["cache_read_input_tokens"] == 20
comms = ai_client.get_comms_log()
response_entries = [e for e in comms if e.get("direction") == "IN" and e["kind"] == "response"]
assert len(response_entries) > 0
usage = response_entries[0]["payload"]["usage"]
assert usage["input_tokens"] == 100
assert usage["output_tokens"] == 50
assert usage["cache_read_input_tokens"] == 20
class MagicNamespace(SimpleNamespace):
def __getattr__(self, name):
return MagicMock()