Skip to content

Commit c0dfec0

Browse files
committed
test: improve coverage for new features
1 parent e66d622 commit c0dfec0

File tree

3 files changed

+97
-15
lines changed

3 files changed

+97
-15
lines changed

tests/test_ai_service.py

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,13 @@ def test_generate_commit_message_success(ai_service, mock_git_file):
8585
"usage": {"prompt_tokens": 100, "completion_tokens": 50, "total_tokens": 150},
8686
}
8787

88-
ai_service.session.post = MagicMock(return_value=MagicMock(status_code=200, json=lambda: mock_response))
88+
ai_service.session.post = MagicMock(
89+
return_value=MagicMock(status_code=200, json=lambda: mock_response)
90+
)
8991

90-
suggestion, usage = ai_service.generate_commit_message("test diff", [mock_git_file("test.py")])
92+
suggestion, usage = ai_service.generate_commit_message(
93+
"test diff", [mock_git_file("test.py")]
94+
)
9195

9296
assert isinstance(suggestion, CommitSuggestion)
9397
assert suggestion.title == "✨ feat: add new feature"
@@ -97,7 +101,9 @@ def test_generate_commit_message_success(ai_service, mock_git_file):
97101
def test_generate_commit_message_api_error(ai_service, mock_git_file):
98102
"""Test handling of API errors."""
99103
ai_service.session.post = MagicMock(
100-
return_value=MagicMock(status_code=400, json=lambda: {"error": {"message": "API Error"}})
104+
return_value=MagicMock(
105+
status_code=400, json=lambda: {"error": {"message": "API Error"}}
106+
)
101107
)
102108

103109
with pytest.raises(ValueError) as exc_info:
@@ -113,7 +119,9 @@ def test_generate_commit_message_invalid_json(ai_service, mock_git_file):
113119
"usage": {"prompt_tokens": 100, "completion_tokens": 50, "total_tokens": 150},
114120
}
115121

116-
ai_service.session.post = MagicMock(return_value=MagicMock(status_code=200, json=lambda: mock_response))
122+
ai_service.session.post = MagicMock(
123+
return_value=MagicMock(status_code=200, json=lambda: mock_response)
124+
)
117125

118126
with pytest.raises(ValueError) as exc_info:
119127
ai_service.generate_commit_message("test diff", [mock_git_file("test.py")])
@@ -123,7 +131,9 @@ def test_generate_commit_message_invalid_json(ai_service, mock_git_file):
123131

124132
def test_generate_commit_message_network_error(ai_service, mock_git_file):
125133
"""Test handling of network errors."""
126-
ai_service.session.post = MagicMock(side_effect=requests.exceptions.RequestException("Network Error"))
134+
ai_service.session.post = MagicMock(
135+
side_effect=requests.exceptions.RequestException("Network Error")
136+
)
127137

128138
with pytest.raises(ValueError) as exc_info:
129139
ai_service.generate_commit_message("test diff", [mock_git_file("test.py")])
@@ -141,7 +151,12 @@ def test_generate_commit_message_retries(mock_sleep, ai_service, mock_git_file):
141151
"content": json.dumps(
142152
{
143153
"title": "✨ feat: retry success",
144-
"body": {"Features": {"emoji": "✨", "changes": ["Added new functionality"]}},
154+
"body": {
155+
"Features": {
156+
"emoji": "✨",
157+
"changes": ["Added new functionality"],
158+
}
159+
},
145160
"summary": "Added new feature",
146161
}
147162
)
@@ -156,7 +171,9 @@ def test_generate_commit_message_retries(mock_sleep, ai_service, mock_git_file):
156171
MagicMock(status_code=200, json=lambda: mock_response),
157172
]
158173
)
159-
suggestion, _ = ai_service.generate_commit_message("diff", [mock_git_file("test.py")])
174+
suggestion, _ = ai_service.generate_commit_message(
175+
"diff", [mock_git_file("test.py")]
176+
)
160177
assert suggestion.title == "✨ feat: retry success"
161178
assert ai_service.session.post.call_count == 2
162179

@@ -182,3 +199,17 @@ def test_ai_service_missing_api_key():
182199
AIService(api_key=None)
183200

184201
assert "API key is required" in str(exc_info.value)
202+
203+
204+
@patch("time.sleep", return_value=None)
205+
def test_generate_commit_message_retries_exhausted(
206+
mock_sleep, ai_service, mock_git_file
207+
):
208+
"""Should raise error after exhausting all retries."""
209+
ai_service.session.post = MagicMock(
210+
side_effect=requests.exceptions.RequestException("temp")
211+
)
212+
with pytest.raises(ValueError) as exc_info:
213+
ai_service.generate_commit_message("diff", [mock_git_file("test.py")])
214+
assert "API Request failed" in str(exc_info.value)
215+
assert ai_service.session.post.call_count == 3

tests/test_analyzer.py

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
"""Tests for commit analyzer module."""
22

3-
43
import pytest
54

65
from commitloom.config.settings import config
@@ -52,7 +51,9 @@ def test_analyze_diff_complexity_token_limit_exceeded(analyzer, mock_git_file):
5251
def test_analyze_diff_complexity_many_files(analyzer, mock_git_file):
5352
"""Test analysis when many files are changed."""
5453
diff = "Multiple file changes"
55-
files = [mock_git_file(f"file{i}.py") for i in range(config.max_files_threshold + 1)]
54+
files = [
55+
mock_git_file(f"file{i}.py") for i in range(config.max_files_threshold + 1)
56+
]
5657

5758
analysis = analyzer.analyze_diff_complexity(diff, files)
5859

@@ -63,7 +64,9 @@ def test_analyze_diff_complexity_many_files(analyzer, mock_git_file):
6364
def test_analyze_diff_complexity_expensive_change(analyzer, mock_git_file):
6465
"""Test analysis of an expensive change."""
6566
# Create a diff that will be expensive (>0.10€)
66-
tokens_for_10_cents = int((0.10 * 1_000_000) / config.model_costs[config.default_model].input)
67+
tokens_for_10_cents = int(
68+
(0.10 * 1_000_000) / config.model_costs[config.default_model].input
69+
)
6770
diff = "diff --git a/expensive.py b/expensive.py\n" + (
6871
"+" + "x" * tokens_for_10_cents * config.token_estimation_ratio + "\n"
6972
)
@@ -115,7 +118,9 @@ def test_analyze_diff_complexity_multiple_conditions(analyzer, mock_git_file):
115118
# 1. Many files
116119
# 2. Moderate cost
117120
# 3. One large file
118-
files = [mock_git_file(f"file{i}.py") for i in range(config.max_files_threshold + 1)]
121+
files = [
122+
mock_git_file(f"file{i}.py") for i in range(config.max_files_threshold + 1)
123+
]
119124
tokens = config.token_limit * 0.8
120125
diff = "x" * int(tokens * config.token_estimation_ratio)
121126

@@ -189,3 +194,26 @@ def test_get_cost_context():
189194
assert "moderate" in CommitAnalyzer.get_cost_context(0.05)
190195
assert "expensive" in CommitAnalyzer.get_cost_context(0.1)
191196
assert "very expensive" in CommitAnalyzer.get_cost_context(1.0)
197+
198+
199+
def test_estimate_tokens_and_cost_unknown_model(capsys):
200+
"""Fallback to zero cost for unknown model."""
201+
tokens, cost = CommitAnalyzer.estimate_tokens_and_cost("test", model="unknown")
202+
captured = capsys.readouterr()
203+
assert "Cost estimation is not available" in captured.out
204+
assert tokens >= 0
205+
assert cost == 0
206+
207+
208+
def test_analyze_diff_complexity_moderate_cost(analyzer, mock_git_file):
209+
"""Should warn about moderate cost without marking complex."""
210+
tokens_for_six_cents = int(
211+
(0.06 * 1_000_000) / config.model_costs[config.default_model].input
212+
)
213+
diff = "diff --git a/mod.py b/mod.py\n" + (
214+
"+" + "x" * tokens_for_six_cents * config.token_estimation_ratio + "\n"
215+
)
216+
files = [mock_git_file("mod.py")]
217+
analysis = analyzer.analyze_diff_complexity(diff, files)
218+
assert any("moderate" in str(w) for w in analysis.warnings)
219+
assert analysis.is_complex

tests/test_cli_handler.py

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,10 @@ def test_handle_commit_success(cli):
8383

8484
def test_handle_commit_complex_changes(cli):
8585
"""Test handling complex changes."""
86-
mock_files = [GitFile(f"test{i}.py", "A", old_path=None, size=100, hash="abc123") for i in range(4)]
86+
mock_files = [
87+
GitFile(f"test{i}.py", "A", old_path=None, size=100, hash="abc123")
88+
for i in range(4)
89+
]
8790
cli.git.get_staged_files = MagicMock(return_value=mock_files)
8891
cli.git.create_commit = MagicMock(return_value=True)
8992

@@ -121,7 +124,9 @@ def test_handle_commit_api_error(cli):
121124
"""Test handling API error."""
122125
mock_file = GitFile("test.py", "A", old_path=None, size=100, hash="abc123")
123126
cli.git.get_staged_files = MagicMock(return_value=[mock_file])
124-
cli.ai_service.generate_commit_message = MagicMock(side_effect=Exception("API error"))
127+
cli.ai_service.generate_commit_message = MagicMock(
128+
side_effect=Exception("API error")
129+
)
125130

126131
with pytest.raises(SystemExit) as exc:
127132
cli.run(auto_commit=True)
@@ -145,7 +150,9 @@ def test_create_batches_with_ignored_files(cli):
145150

146151
def test_create_batches_git_error(cli):
147152
"""Test batch creation with git error."""
148-
cli.git.get_staged_files = MagicMock(side_effect=subprocess.CalledProcessError(1, "git"))
153+
cli.git.get_staged_files = MagicMock(
154+
side_effect=subprocess.CalledProcessError(1, "git")
155+
)
149156

150157
batches = cli._create_batches([])
151158

@@ -222,7 +229,10 @@ def test_debug_mode(cli):
222229

223230
def test_process_files_in_batches_error(cli):
224231
"""Test error handling in batch processing."""
225-
mock_files = [GitFile(f"test{i}.py", "A", old_path=None, size=100, hash="abc123") for i in range(4)]
232+
mock_files = [
233+
GitFile(f"test{i}.py", "A", old_path=None, size=100, hash="abc123")
234+
for i in range(4)
235+
]
226236
cli.git.get_diff = MagicMock(side_effect=GitError("Git error"))
227237

228238
with pytest.raises(SystemExit) as exc:
@@ -281,3 +291,16 @@ def test_maybe_create_branch_not_complex(cli):
281291
mock_console.confirm_branch_creation.return_value = True
282292
cli._maybe_create_branch(analysis)
283293
cli.git.create_and_checkout_branch.assert_not_called()
294+
295+
296+
def test_process_single_commit_maybe_create_branch_once(cli, mock_git_file):
297+
"""_maybe_create_branch should be invoked only once."""
298+
cli.auto_commit = True
299+
cli._maybe_create_branch = MagicMock()
300+
cli.git.create_commit = MagicMock(return_value=True)
301+
file = mock_git_file("test.py")
302+
with patch(
303+
"commitloom.cli.cli_handler.metrics_manager.start_commit_tracking"
304+
), patch("commitloom.cli.cli_handler.metrics_manager.finish_commit_tracking"):
305+
cli._process_single_commit([file])
306+
cli._maybe_create_branch.assert_called_once()

0 commit comments

Comments
 (0)