Skip to content
This repository was archived by the owner on Apr 23, 2025. It is now read-only.

Commit ac352bb

Browse files
committed
Improve test coverage for tools modules and gemini model
1 parent 5b3a3be commit ac352bb

8 files changed

Lines changed: 1807 additions & 963 deletions

File tree

src/cli_code/models/gemini.py

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -565,20 +565,26 @@ def _manage_context_window(self):
565565
# --- Tool Definition Helper ---
566566
def _create_tool_definitions(self) -> list | None:
567567
"""Dynamically create Tool definitions from AVAILABLE_TOOLS."""
568-
# NOTE: This assumes get_function_declaration() returns objects compatible with or convertible to genai Tools
568+
# Fix: AVAILABLE_TOOLS is a dictionary, not a function
569569
declarations = []
570-
for tool_name, tool_instance in AVAILABLE_TOOLS.items():
571-
if hasattr(tool_instance, "get_function_declaration"):
572-
declaration_obj = tool_instance.get_function_declaration()
573-
if declaration_obj:
574-
# Assuming declaration_obj is structured correctly or needs conversion
575-
# For now, append directly. May need adjustment based on actual object structure.
576-
declarations.append(declaration_obj)
577-
log.debug(f"Generated tool definition for tool: {tool_name}")
570+
for tool_name, tool_class in AVAILABLE_TOOLS.items():
571+
try:
572+
# Instantiate the tool
573+
tool_instance = tool_class()
574+
if hasattr(tool_instance, "get_function_declaration"):
575+
declaration_obj = tool_instance.get_function_declaration()
576+
if declaration_obj:
577+
# Assuming declaration_obj is structured correctly or needs conversion
578+
# For now, append directly. May need adjustment based on actual object structure.
579+
declarations.append(declaration_obj)
580+
log.debug(f"Generated tool definition for tool: {tool_name}")
581+
else:
582+
log.warning(f"Tool {tool_name} has 'get_function_declaration' but it returned None.")
578583
else:
579-
log.warning(f"Tool {tool_name} has 'get_function_declaration' but it returned None.")
580-
else:
581-
log.warning(f"Tool {tool_name} does not have a 'get_function_declaration' method. Skipping.")
584+
log.warning(f"Tool {tool_name} does not have a 'get_function_declaration' method. Skipping.")
585+
except Exception as e:
586+
log.error(f"Error instantiating tool '{tool_name}': {e}")
587+
continue
582588

583589
log.info(f"Created {len(declarations)} tool definitions for native tool use.")
584590
# The return type of this function might need to be adjusted based on how
Lines changed: 305 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,305 @@
1+
"""
2+
Tests for quality_tools module.
3+
"""
4+
import os
5+
import subprocess
6+
import pytest
7+
from unittest.mock import patch, MagicMock
8+
9+
# Direct import for coverage tracking
10+
import src.cli_code.tools.quality_tools
11+
from src.cli_code.tools.quality_tools import (
12+
_run_quality_command,
13+
LinterCheckerTool,
14+
FormatterTool
15+
)
16+
17+
18+
def test_linter_checker_tool_init():
19+
"""Test LinterCheckerTool initialization."""
20+
tool = LinterCheckerTool()
21+
assert tool.name == "linter_checker"
22+
assert "Runs a code linter" in tool.description
23+
24+
25+
def test_formatter_tool_init():
26+
"""Test FormatterTool initialization."""
27+
tool = FormatterTool()
28+
assert tool.name == "formatter"
29+
assert "Runs a code formatter" in tool.description
30+
31+
32+
@patch("subprocess.run")
33+
def test_run_quality_command_success(mock_run):
34+
"""Test _run_quality_command with successful command execution."""
35+
# Setup mock
36+
mock_process = MagicMock()
37+
mock_process.returncode = 0
38+
mock_process.stdout = "Command output"
39+
mock_process.stderr = ""
40+
mock_run.return_value = mock_process
41+
42+
# Execute function
43+
result = _run_quality_command(["test", "command"], "TestTool")
44+
45+
# Verify results
46+
assert "TestTool Result (Exit Code: 0)" in result
47+
assert "Command output" in result
48+
assert "-- Errors --" not in result
49+
mock_run.assert_called_once_with(
50+
["test", "command"],
51+
capture_output=True,
52+
text=True,
53+
check=False,
54+
timeout=120
55+
)
56+
57+
58+
@patch("subprocess.run")
59+
def test_run_quality_command_with_errors(mock_run):
60+
"""Test _run_quality_command with command that outputs errors."""
61+
# Setup mock
62+
mock_process = MagicMock()
63+
mock_process.returncode = 1
64+
mock_process.stdout = "Command output"
65+
mock_process.stderr = "Error message"
66+
mock_run.return_value = mock_process
67+
68+
# Execute function
69+
result = _run_quality_command(["test", "command"], "TestTool")
70+
71+
# Verify results
72+
assert "TestTool Result (Exit Code: 1)" in result
73+
assert "Command output" in result
74+
assert "-- Errors --" in result
75+
assert "Error message" in result
76+
77+
78+
@patch("subprocess.run")
79+
def test_run_quality_command_no_output(mock_run):
80+
"""Test _run_quality_command with command that produces no output."""
81+
# Setup mock
82+
mock_process = MagicMock()
83+
mock_process.returncode = 0
84+
mock_process.stdout = ""
85+
mock_process.stderr = ""
86+
mock_run.return_value = mock_process
87+
88+
# Execute function
89+
result = _run_quality_command(["test", "command"], "TestTool")
90+
91+
# Verify results
92+
assert "TestTool Result (Exit Code: 0)" in result
93+
assert "(No output)" in result
94+
95+
96+
@patch("subprocess.run")
97+
def test_run_quality_command_long_output(mock_run):
98+
"""Test _run_quality_command with command that produces very long output."""
99+
# Setup mock
100+
mock_process = MagicMock()
101+
mock_process.returncode = 0
102+
mock_process.stdout = "A" * 3000 # Longer than 2000 char limit
103+
mock_process.stderr = ""
104+
mock_run.return_value = mock_process
105+
106+
# Execute function
107+
result = _run_quality_command(["test", "command"], "TestTool")
108+
109+
# Verify results
110+
assert "... (output truncated)" in result
111+
assert len(result) < 3000
112+
113+
114+
def test_run_quality_command_file_not_found():
115+
"""Test _run_quality_command with non-existent command."""
116+
# Set up side effect
117+
with patch("subprocess.run", side_effect=FileNotFoundError("No such file or directory: 'nonexistent'")):
118+
# Execute function
119+
result = _run_quality_command(["nonexistent"], "TestTool")
120+
121+
# Verify results
122+
assert "Error: Command 'nonexistent' not found" in result
123+
assert "Is 'nonexistent' installed and in PATH?" in result
124+
125+
126+
def test_run_quality_command_timeout():
127+
"""Test _run_quality_command with command that times out."""
128+
# Set up side effect
129+
with patch("subprocess.run", side_effect=subprocess.TimeoutExpired(cmd="slow_command", timeout=120)):
130+
# Execute function
131+
result = _run_quality_command(["slow_command"], "TestTool")
132+
133+
# Verify results
134+
assert "Error: TestTool run timed out" in result
135+
assert "2 minutes" in result
136+
137+
138+
def test_run_quality_command_unexpected_error():
139+
"""Test _run_quality_command with unexpected error."""
140+
# Set up side effect
141+
with patch("subprocess.run", side_effect=Exception("Unexpected error")):
142+
# Execute function
143+
result = _run_quality_command(["command"], "TestTool")
144+
145+
# Verify results
146+
assert "Error running TestTool" in result
147+
assert "Unexpected error" in result
148+
149+
150+
@patch("src.cli_code.tools.quality_tools._run_quality_command")
151+
def test_linter_checker_with_defaults(mock_run_command):
152+
"""Test LinterCheckerTool with default parameters."""
153+
# Setup mock
154+
mock_run_command.return_value = "Linter output"
155+
156+
# Execute tool
157+
tool = LinterCheckerTool()
158+
result = tool.execute()
159+
160+
# Verify results
161+
assert result == "Linter output"
162+
mock_run_command.assert_called_once()
163+
args, kwargs = mock_run_command.call_args
164+
assert args[0] == ["ruff", "check", os.path.abspath(".")]
165+
assert args[1] == "Linter"
166+
167+
168+
@patch("src.cli_code.tools.quality_tools._run_quality_command")
169+
def test_linter_checker_with_custom_path(mock_run_command):
170+
"""Test LinterCheckerTool with custom path."""
171+
# Setup mock
172+
mock_run_command.return_value = "Linter output"
173+
174+
# Execute tool
175+
tool = LinterCheckerTool()
176+
result = tool.execute(path="src")
177+
178+
# Verify results
179+
assert result == "Linter output"
180+
mock_run_command.assert_called_once()
181+
args, kwargs = mock_run_command.call_args
182+
assert args[0] == ["ruff", "check", os.path.abspath("src")]
183+
184+
185+
@patch("src.cli_code.tools.quality_tools._run_quality_command")
186+
def test_linter_checker_with_custom_command(mock_run_command):
187+
"""Test LinterCheckerTool with custom linter command."""
188+
# Setup mock
189+
mock_run_command.return_value = "Linter output"
190+
191+
# Execute tool
192+
tool = LinterCheckerTool()
193+
result = tool.execute(linter_command="flake8")
194+
195+
# Verify results
196+
assert result == "Linter output"
197+
mock_run_command.assert_called_once()
198+
args, kwargs = mock_run_command.call_args
199+
assert args[0] == ["flake8", os.path.abspath(".")]
200+
201+
202+
@patch("src.cli_code.tools.quality_tools._run_quality_command")
203+
def test_linter_checker_with_complex_command(mock_run_command):
204+
"""Test LinterCheckerTool with complex command including arguments."""
205+
# Setup mock
206+
mock_run_command.return_value = "Linter output"
207+
208+
# Execute tool
209+
tool = LinterCheckerTool()
210+
result = tool.execute(linter_command="flake8 --max-line-length=100")
211+
212+
# Verify results
213+
assert result == "Linter output"
214+
mock_run_command.assert_called_once()
215+
args, kwargs = mock_run_command.call_args
216+
assert args[0] == ["flake8", "--max-line-length=100", os.path.abspath(".")]
217+
218+
219+
def test_linter_checker_with_parent_directory_traversal():
220+
"""Test LinterCheckerTool with path containing parent directory traversal."""
221+
tool = LinterCheckerTool()
222+
result = tool.execute(path="../dangerous")
223+
224+
# Verify results
225+
assert "Error: Invalid path" in result
226+
assert "Cannot access parent directories" in result
227+
228+
229+
@patch("src.cli_code.tools.quality_tools._run_quality_command")
230+
def test_formatter_with_defaults(mock_run_command):
231+
"""Test FormatterTool with default parameters."""
232+
# Setup mock
233+
mock_run_command.return_value = "Formatter output"
234+
235+
# Execute tool
236+
tool = FormatterTool()
237+
result = tool.execute()
238+
239+
# Verify results
240+
assert result == "Formatter output"
241+
mock_run_command.assert_called_once()
242+
args, kwargs = mock_run_command.call_args
243+
assert args[0] == ["black", os.path.abspath(".")]
244+
assert args[1] == "Formatter"
245+
246+
247+
@patch("src.cli_code.tools.quality_tools._run_quality_command")
248+
def test_formatter_with_custom_path(mock_run_command):
249+
"""Test FormatterTool with custom path."""
250+
# Setup mock
251+
mock_run_command.return_value = "Formatter output"
252+
253+
# Execute tool
254+
tool = FormatterTool()
255+
result = tool.execute(path="src")
256+
257+
# Verify results
258+
assert result == "Formatter output"
259+
mock_run_command.assert_called_once()
260+
args, kwargs = mock_run_command.call_args
261+
assert args[0] == ["black", os.path.abspath("src")]
262+
263+
264+
@patch("src.cli_code.tools.quality_tools._run_quality_command")
265+
def test_formatter_with_custom_command(mock_run_command):
266+
"""Test FormatterTool with custom formatter command."""
267+
# Setup mock
268+
mock_run_command.return_value = "Formatter output"
269+
270+
# Execute tool
271+
tool = FormatterTool()
272+
result = tool.execute(formatter_command="prettier")
273+
274+
# Verify results
275+
assert result == "Formatter output"
276+
mock_run_command.assert_called_once()
277+
args, kwargs = mock_run_command.call_args
278+
assert args[0] == ["prettier", os.path.abspath(".")]
279+
280+
281+
@patch("src.cli_code.tools.quality_tools._run_quality_command")
282+
def test_formatter_with_complex_command(mock_run_command):
283+
"""Test FormatterTool with complex command including arguments."""
284+
# Setup mock
285+
mock_run_command.return_value = "Formatter output"
286+
287+
# Execute tool
288+
tool = FormatterTool()
289+
result = tool.execute(formatter_command="prettier --write")
290+
291+
# Verify results
292+
assert result == "Formatter output"
293+
mock_run_command.assert_called_once()
294+
args, kwargs = mock_run_command.call_args
295+
assert args[0] == ["prettier", "--write", os.path.abspath(".")]
296+
297+
298+
def test_formatter_with_parent_directory_traversal():
299+
"""Test FormatterTool with path containing parent directory traversal."""
300+
tool = FormatterTool()
301+
result = tool.execute(path="../dangerous")
302+
303+
# Verify results
304+
assert "Error: Invalid path" in result
305+
assert "Cannot access parent directories" in result

0 commit comments

Comments
 (0)