Skip to content

Commit 37c88f5

Browse files
committed
🩹 fix(version): fix typo: reclusive -> recursive
1 parent 10037b6 commit 37c88f5

File tree

9 files changed

+895
-300
lines changed

9 files changed

+895
-300
lines changed

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ exclude_lines = [
112112
"if __name__ == .__main__.:",
113113
"class .*\\bProtocol\\):",
114114
"@(abc\\.)?abstractmethod",
115+
"if TYPE_CHECKING:",
115116
]
116117

117118
[dependency-groups]

tests/integration/test_version_integration.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ def test_update_version_files_package_json(self, tmp_path):
127127
next_version = Version(major=1, minor=1, patch=0)
128128

129129
# Update version files
130-
update_version_files(args, next_version, verbose=0, reclusive=False)
130+
update_version_files(args, next_version, verbose=0, recursive=False)
131131

132132
# Verify update
133133
updated_content = json.loads(package_json.read_text())
@@ -171,7 +171,7 @@ def test_update_version_files_pyproject_toml(self, tmp_path):
171171
next_version = Version(major=2, minor=0, patch=0)
172172

173173
# Update version files
174-
update_version_files(args, next_version, verbose=0, reclusive=False)
174+
update_version_files(args, next_version, verbose=0, recursive=False)
175175

176176
# Verify update
177177
updated_content = pyproject_toml.read_text()
@@ -208,7 +208,7 @@ def test_update_version_files_multiple_files(self, tmp_path):
208208
next_version = Version(major=1, minor=0, patch=1)
209209

210210
# Update version files
211-
update_version_files(args, next_version, verbose=0, reclusive=False)
211+
update_version_files(args, next_version, verbose=0, recursive=False)
212212

213213
# Verify updates
214214
updated_package_json = json.loads(package_json.read_text())
@@ -251,7 +251,7 @@ def test_update_version_files_recursive(self, tmp_path):
251251
next_version = Version(major=1, minor=1, patch=0)
252252

253253
# Update version files
254-
update_version_files(args, next_version, verbose=0, reclusive=True)
254+
update_version_files(args, next_version, verbose=0, recursive=True)
255255

256256
# Verify updates in both directories
257257
root_content = json.loads(root_package_json.read_text())
@@ -295,7 +295,7 @@ def test_update_version_files_ignore_node_modules(self, tmp_path):
295295
next_version = Version(major=2, minor=0, patch=0)
296296

297297
# Update version files
298-
update_version_files(args, next_version, verbose=0, reclusive=True)
298+
update_version_files(args, next_version, verbose=0, recursive=True)
299299

300300
# Verify root package.json was updated
301301
root_content = json.loads(root_package_json.read_text())
@@ -418,7 +418,7 @@ def test_version_workflow_with_git_repo(self, temp_git_repo):
418418

419419
# Update version files
420420
next_version = Version(major=1, minor=0, patch=1)
421-
update_version_files(args, next_version, verbose=0, reclusive=False)
421+
update_version_files(args, next_version, verbose=0, recursive=False)
422422

423423
# Verify version was updated
424424
updated_content = json.loads(package_json.read_text())
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
from unittest.mock import Mock, patch
2+
3+
import pytest
4+
from tgit.changelog import (
5+
get_changelog_by_range,
6+
get_commits,
7+
prepare_changelog_segments,
8+
)
9+
10+
11+
class TestChangelogCoverage:
12+
"""Additional tests to increase coverage for changelog.py."""
13+
14+
@patch("tgit.changelog.commit_pattern")
15+
def test_get_commits_bytes_message(self, mock_pattern):
16+
"""Test get_commits with bytes message."""
17+
repo = Mock()
18+
commit = Mock()
19+
commit.message = b"feat: bytes message"
20+
repo.iter_commits.return_value = [commit]
21+
22+
mock_pattern.match.return_value = None # Just to avoid further processing
23+
24+
get_commits(repo, "HEAD~1", "HEAD")
25+
26+
# Verify message was decoded
27+
# Since we mocked match to return None, we can't verify the result list,
28+
# but we can verify that no error occurred during decoding.
29+
30+
@patch("tgit.changelog.commit_pattern")
31+
def test_get_commits_non_string_message(self, mock_pattern):
32+
"""Test get_commits with non-string message."""
33+
repo = Mock()
34+
commit = Mock()
35+
commit.message = 12345
36+
repo.iter_commits.return_value = [commit]
37+
38+
mock_pattern.match.return_value = None
39+
40+
get_commits(repo, "HEAD~1", "HEAD")
41+
42+
def test_prepare_changelog_segments_with_latest_tag_in_file(self):
43+
"""Test prepare_changelog_segments with latest_tag_in_file."""
44+
repo = Mock()
45+
tag1 = Mock()
46+
tag1.name = "v1.0.0"
47+
tag1.commit.hexsha = "hash1"
48+
tag1.commit.committed_datetime = 1000
49+
50+
tag2 = Mock()
51+
tag2.name = "v1.1.0"
52+
tag2.commit.hexsha = "hash2"
53+
tag2.commit.committed_datetime = 2000
54+
55+
repo.tags = [tag1, tag2]
56+
repo.iter_commits.return_value = [Mock(hexsha="init")]
57+
58+
# Mock get_first_commit_hash
59+
with patch("tgit.changelog.get_first_commit_hash", return_value="init"):
60+
segments = prepare_changelog_segments(repo, latest_tag_in_file="v1.0.0")
61+
62+
assert len(segments) == 1
63+
assert segments[0].from_name == "v1.0.0"
64+
assert segments[0].to_name == "v1.1.0"
65+
66+
@patch("tgit.changelog.get_commits")
67+
@patch("tgit.changelog.group_commits_by_type")
68+
@patch("tgit.changelog.generate_changelog")
69+
def test_get_changelog_by_range_no_remote(self, mock_gen, mock_group, mock_get_commits):
70+
"""Test get_changelog_by_range when remote is missing."""
71+
repo = Mock()
72+
repo.remote.side_effect = ValueError("No remote")
73+
74+
with pytest.warns(UserWarning, match="Origin not found"):
75+
get_changelog_by_range(repo, "HEAD~1", "HEAD")
76+
77+
mock_gen.assert_called_once()
78+
assert mock_gen.call_args[0][3] is None # remote_uri should be None

tests/unit/test_commit_coverage.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
from unittest.mock import Mock, patch
2+
3+
import pytest
4+
from tgit.commit import (
5+
CommitArgs,
6+
_supports_reasoning,
7+
get_ai_command,
8+
get_file_change_sizes,
9+
handle_commit,
10+
)
11+
12+
13+
class TestCommitCoverage:
14+
"""Additional tests to increase coverage for commit.py."""
15+
16+
def test_supports_reasoning_empty(self):
17+
"""Test _supports_reasoning with empty model."""
18+
assert _supports_reasoning("") is False
19+
assert _supports_reasoning(None) is False
20+
21+
def test_get_file_change_sizes_value_error(self):
22+
"""Test get_file_change_sizes with invalid numstat."""
23+
repo = Mock()
24+
# Simulate a case where numstat returns invalid integer
25+
repo.git.diff.return_value = "invalid\tinvalid\timage.png"
26+
27+
sizes = get_file_change_sizes(repo)
28+
assert sizes["image.png"] == 0
29+
30+
@patch("tgit.commit.git.Repo")
31+
@patch("tgit.commit.get_filtered_diff_files")
32+
def test_get_ai_command_no_diff(self, mock_filter, mock_repo):
33+
"""Test get_ai_command when there is no diff after filtering."""
34+
repo = Mock()
35+
mock_repo.return_value = repo
36+
37+
# Return some files to pass the first check
38+
mock_filter.return_value = (["file.txt"], [])
39+
40+
# But make git diff return empty string
41+
repo.git.diff.return_value = ""
42+
repo.active_branch.name = "main"
43+
44+
assert get_ai_command() is None
45+
46+
@patch("tgit.commit.git.Repo")
47+
@patch("tgit.commit.get_filtered_diff_files")
48+
@patch("tgit.commit._generate_commit_with_ai")
49+
def test_get_ai_command_ai_failure(self, mock_gen, mock_filter, mock_repo):
50+
"""Test get_ai_command when AI generation fails."""
51+
repo = Mock()
52+
mock_repo.return_value = repo
53+
mock_filter.return_value = (["file.txt"], [])
54+
repo.git.diff.return_value = "diff content"
55+
repo.active_branch.name = "main"
56+
57+
mock_gen.return_value = None
58+
59+
assert get_ai_command() is None
60+
61+
@patch("tgit.commit.get_ai_command")
62+
def test_handle_commit_ai_command_none(self, mock_get_ai):
63+
"""Test handle_commit when get_ai_command returns None."""
64+
mock_get_ai.return_value = None
65+
66+
args = CommitArgs(
67+
message=["feat"],
68+
emoji=False,
69+
breaking=False,
70+
ai=False,
71+
)
72+
73+
handle_commit(args)
74+
# Should return without error and without running command

tests/unit/test_utils_coverage.py

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import json
2+
from pathlib import Path
3+
from unittest.mock import Mock, patch
4+
5+
import pytest
6+
from click.testing import CliRunner
7+
from tgit.cli import app
8+
from tgit.utils import (
9+
load_global_settings,
10+
load_workspace_settings,
11+
set_global_settings,
12+
)
13+
14+
15+
class TestUtilsCoverage:
16+
"""Additional tests to increase coverage for utils and cli."""
17+
18+
def test_load_global_settings_empty_json(self, tmp_path):
19+
"""Test load_global_settings with empty JSON (returns None)."""
20+
settings_path = tmp_path / ".tgit" / "settings.json"
21+
settings_path.parent.mkdir(parents=True)
22+
settings_path.write_text("null")
23+
24+
with patch("tgit.utils.Path.home", return_value=tmp_path):
25+
settings = load_global_settings()
26+
assert settings == {}
27+
28+
def test_load_workspace_settings_empty_json(self, tmp_path):
29+
"""Test load_workspace_settings with empty JSON (returns None)."""
30+
settings_path = tmp_path / ".tgit" / "settings.json"
31+
settings_path.parent.mkdir(parents=True)
32+
settings_path.write_text("null")
33+
34+
with patch("tgit.utils.Path.cwd", return_value=tmp_path):
35+
settings = load_workspace_settings()
36+
assert settings == {}
37+
38+
def test_set_global_settings(self, tmp_path):
39+
"""Test set_global_settings."""
40+
with patch("tgit.utils.Path.home", return_value=tmp_path):
41+
set_global_settings("test_key", "test_value")
42+
43+
settings_path = tmp_path / ".tgit" / "settings.json"
44+
assert settings_path.exists()
45+
content = json.loads(settings_path.read_text())
46+
assert content["test_key"] == "test_value"
47+
48+
# Test updating existing settings
49+
set_global_settings("test_key_2", "test_value_2")
50+
content = json.loads(settings_path.read_text())
51+
assert content["test_key"] == "test_value"
52+
assert content["test_key_2"] == "test_value_2"
53+
54+
def test_set_global_settings_with_null_file(self, tmp_path):
55+
"""Test set_global_settings when file exists but is null."""
56+
settings_path = tmp_path / ".tgit" / "settings.json"
57+
settings_path.parent.mkdir(parents=True)
58+
settings_path.write_text("null")
59+
60+
with patch("tgit.utils.Path.home", return_value=tmp_path):
61+
set_global_settings("test_key", "test_value")
62+
63+
content = json.loads(settings_path.read_text())
64+
assert content["test_key"] == "test_value"
65+
66+
def test_load_global_settings_missing_file(self, tmp_path):
67+
"""Test load_global_settings when file is missing."""
68+
with patch("tgit.utils.Path.home", return_value=tmp_path):
69+
settings = load_global_settings()
70+
assert settings == {}
71+
72+
def test_load_workspace_settings_missing_file(self, tmp_path):
73+
"""Test load_workspace_settings when file is missing."""
74+
with patch("tgit.utils.Path.cwd", return_value=tmp_path):
75+
settings = load_workspace_settings()
76+
assert settings == {}
77+
78+
@patch("tgit.cli.threading.Thread")
79+
def test_cli_app_import_openai(self, mock_thread):
80+
"""Test cli app triggers openai import."""
81+
# Mock Thread to run target immediately
82+
def run_target(target=None, **kwargs):
83+
target()
84+
return Mock()
85+
86+
mock_thread.side_effect = run_target
87+
88+
runner = CliRunner()
89+
# Invoke with a subcommand to ensure group function runs
90+
# We use a non-existent command to trigger group execution before error?
91+
# Or use 'version' command if available.
92+
# Let's use 'settings' command which is added.
93+
result = runner.invoke(app, ["settings", "--help"])
94+
95+
assert result.exit_code == 0
96+
mock_thread.assert_called()
97+
98+
@patch("tgit.cli.threading.Thread")
99+
def test_cli_app_import_openai_exception(self, mock_thread):
100+
"""Test cli app handles openai import exception."""
101+
def run_target(target=None, **kwargs):
102+
# Mock import to raise exception
103+
with patch("builtins.__import__", side_effect=ImportError("fail")):
104+
target()
105+
return Mock()
106+
107+
mock_thread.side_effect = run_target
108+
109+
runner = CliRunner()
110+
result = runner.invoke(app, ["settings", "--help"])
111+
112+
assert result.exit_code == 0
113+
mock_thread.assert_called()
114+
115+
def test_version_callback(self):
116+
"""Test version callback."""
117+
runner = CliRunner()
118+
result = runner.invoke(app, ["--version"])
119+
assert result.exit_code == 0
120+
assert "TGIT - ver." in result.output

tests/unit/test_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2186,7 +2186,7 @@ def test_update_version_files_verbose_mode(self, mock_console, mock_get_detected
21862186
)
21872187
next_version = Version(2, 0, 0)
21882188

2189-
update_version_files(args, next_version, 1, reclusive=True)
2189+
update_version_files(args, next_version, 1, recursive=True)
21902190

21912191
# Check verbose output
21922192
mock_console.print.assert_any_call("Current path: [cyan bold]/test")

0 commit comments

Comments
 (0)