git-commit-msg-ai 1.5.0__tar.gz → 1.5.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {git_commit_msg_ai-1.5.0 → git_commit_msg_ai-1.5.1}/PKG-INFO +1 -1
- {git_commit_msg_ai-1.5.0 → git_commit_msg_ai-1.5.1}/git_commit_msg_ai/ai_client.py +12 -12
- {git_commit_msg_ai-1.5.0 → git_commit_msg_ai-1.5.1}/git_commit_msg_ai/cli.py +12 -12
- {git_commit_msg_ai-1.5.0 → git_commit_msg_ai-1.5.1}/git_commit_msg_ai/editor.py +15 -15
- git_commit_msg_ai-1.5.1/git_commit_msg_ai/git_ops.py +52 -0
- {git_commit_msg_ai-1.5.0 → git_commit_msg_ai-1.5.1}/git_commit_msg_ai.egg-info/PKG-INFO +1 -1
- {git_commit_msg_ai-1.5.0 → git_commit_msg_ai-1.5.1}/pyproject.toml +2 -2
- {git_commit_msg_ai-1.5.0 → git_commit_msg_ai-1.5.1}/tests/test_ai_client.py +39 -39
- {git_commit_msg_ai-1.5.0 → git_commit_msg_ai-1.5.1}/tests/test_cli.py +64 -64
- git_commit_msg_ai-1.5.1/tests/test_editor.py +245 -0
- {git_commit_msg_ai-1.5.0 → git_commit_msg_ai-1.5.1}/tests/test_exceptions.py +3 -3
- {git_commit_msg_ai-1.5.0 → git_commit_msg_ai-1.5.1}/tests/test_git_ops.py +48 -48
- git_commit_msg_ai-1.5.0/git_commit_msg_ai/git_ops.py +0 -52
- git_commit_msg_ai-1.5.0/tests/test_editor.py +0 -245
- {git_commit_msg_ai-1.5.0 → git_commit_msg_ai-1.5.1}/README.md +0 -0
- {git_commit_msg_ai-1.5.0 → git_commit_msg_ai-1.5.1}/git_commit_msg_ai/__init__.py +0 -0
- {git_commit_msg_ai-1.5.0 → git_commit_msg_ai-1.5.1}/git_commit_msg_ai/exceptions.py +0 -0
- {git_commit_msg_ai-1.5.0 → git_commit_msg_ai-1.5.1}/git_commit_msg_ai.egg-info/SOURCES.txt +0 -0
- {git_commit_msg_ai-1.5.0 → git_commit_msg_ai-1.5.1}/git_commit_msg_ai.egg-info/dependency_links.txt +0 -0
- {git_commit_msg_ai-1.5.0 → git_commit_msg_ai-1.5.1}/git_commit_msg_ai.egg-info/entry_points.txt +0 -0
- {git_commit_msg_ai-1.5.0 → git_commit_msg_ai-1.5.1}/git_commit_msg_ai.egg-info/requires.txt +0 -0
- {git_commit_msg_ai-1.5.0 → git_commit_msg_ai-1.5.1}/git_commit_msg_ai.egg-info/top_level.txt +0 -0
- {git_commit_msg_ai-1.5.0 → git_commit_msg_ai-1.5.1}/setup.cfg +0 -0
|
@@ -16,7 +16,7 @@ SYSTEM_PROMPT: Final[str] = textwrap.dedent("""\
|
|
|
16
16
|
- If there is a breaking change, add a blank line after the body followed by "BREAKING CHANGE: <description of what breaks and why>
|
|
17
17
|
Types: feat, fix, docs, style, refactor, test, chore""")
|
|
18
18
|
|
|
19
|
-
MODEL: Final[str] =
|
|
19
|
+
MODEL: Final[str] = "claude-haiku-4-5-20251001"
|
|
20
20
|
MAX_TOKENS: Final[int] = 1024
|
|
21
21
|
|
|
22
22
|
|
|
@@ -24,29 +24,29 @@ def generate_commit_message(diff: str) -> str:
|
|
|
24
24
|
try:
|
|
25
25
|
anthropic_client = anthropic.Anthropic()
|
|
26
26
|
|
|
27
|
-
logger.debug(
|
|
27
|
+
logger.debug("Calling Anthropic API: model=%s max_tokens=%d", MODEL, MAX_TOKENS)
|
|
28
28
|
anthropic_api_response = anthropic_client.messages.create(
|
|
29
29
|
model=MODEL,
|
|
30
30
|
max_tokens=MAX_TOKENS,
|
|
31
31
|
system=SYSTEM_PROMPT,
|
|
32
|
-
messages=[{
|
|
32
|
+
messages=[{"role": "user", "content": diff}],
|
|
33
33
|
)
|
|
34
34
|
except anthropic.AuthenticationError:
|
|
35
|
-
logger.error(
|
|
36
|
-
raise AIError(
|
|
35
|
+
logger.error("Anthropic authentication error")
|
|
36
|
+
raise AIError("Anthropic API key is missing or invalid. Set the ANTHROPIC_API_KEY environment variable.")
|
|
37
37
|
except anthropic.RateLimitError:
|
|
38
|
-
logger.error(
|
|
39
|
-
raise AIError(
|
|
38
|
+
logger.error("Anthropic rate limit error")
|
|
39
|
+
raise AIError("Anthropic API rate limit reached. Wait a moment and try again.")
|
|
40
40
|
except anthropic.APIConnectionError:
|
|
41
|
-
logger.error(
|
|
42
|
-
raise AIError(
|
|
41
|
+
logger.error("Anthropic API connection error")
|
|
42
|
+
raise AIError("Could not reach the Anthropic API. Check your network connection.")
|
|
43
43
|
except anthropic.APIStatusError as error:
|
|
44
|
-
logger.error(
|
|
45
|
-
raise AIError(f
|
|
44
|
+
logger.error("Anthropic API status error: %s", error.status_code)
|
|
45
|
+
raise AIError(f"Anthropic API returned an error: {error.status_code}.")
|
|
46
46
|
|
|
47
47
|
anthropic_api_response_message = anthropic_api_response.content[0]
|
|
48
48
|
commit_message = cast(anthropic.types.TextBlock, anthropic_api_response_message).text.strip()
|
|
49
49
|
|
|
50
|
-
logger.info(
|
|
50
|
+
logger.info("Commit message generated: %d chars", len(commit_message))
|
|
51
51
|
|
|
52
52
|
return commit_message
|
|
@@ -6,20 +6,20 @@ from typing import Final
|
|
|
6
6
|
from git_commit_msg_ai import ai_client, editor, git_ops
|
|
7
7
|
from git_commit_msg_ai.exceptions import GitCommitAIError
|
|
8
8
|
|
|
9
|
-
LOG_LEVEL_ENV_VAR: Final[str] =
|
|
9
|
+
LOG_LEVEL_ENV_VAR: Final[str] = "GIT_COMMIT_AI_LOG_LEVEL"
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
def main() -> None:
|
|
13
|
-
log_level_name = os.environ.get(LOG_LEVEL_ENV_VAR,
|
|
13
|
+
log_level_name = os.environ.get(LOG_LEVEL_ENV_VAR, "WARNING").upper()
|
|
14
14
|
numeric_log_level = getattr(logging, log_level_name, None)
|
|
15
15
|
|
|
16
16
|
if not isinstance(numeric_log_level, int):
|
|
17
17
|
numeric_log_level = logging.WARNING
|
|
18
18
|
|
|
19
|
-
logging.basicConfig(level=numeric_log_level, format=
|
|
19
|
+
logging.basicConfig(level=numeric_log_level, format="%(levelname)s:%(name)s:%(message)s", stream=sys.stderr)
|
|
20
20
|
|
|
21
21
|
if not isinstance(getattr(logging, log_level_name, None), int):
|
|
22
|
-
logging.warning(
|
|
22
|
+
logging.warning("Invalid %s value %r, defaulting to WARNING", LOG_LEVEL_ENV_VAR, log_level_name)
|
|
23
23
|
|
|
24
24
|
try:
|
|
25
25
|
diff = git_ops.get_staged_diff()
|
|
@@ -27,25 +27,25 @@ def main() -> None:
|
|
|
27
27
|
print(commit_message)
|
|
28
28
|
|
|
29
29
|
print()
|
|
30
|
-
user_selection = input(
|
|
30
|
+
user_selection = input("[a]ccept / [e]dit / [r]eject: ").strip().lower()
|
|
31
31
|
|
|
32
|
-
if user_selection ==
|
|
32
|
+
if user_selection == "a":
|
|
33
33
|
git_ops.commit(commit_message)
|
|
34
|
-
elif user_selection ==
|
|
34
|
+
elif user_selection == "e":
|
|
35
35
|
updated_commit_message = editor.open_in_editor(commit_message)
|
|
36
36
|
git_ops.commit(updated_commit_message)
|
|
37
|
-
elif user_selection ==
|
|
38
|
-
print(
|
|
37
|
+
elif user_selection == "r":
|
|
38
|
+
print("User rejected the generated commit message. No commit made.")
|
|
39
39
|
else:
|
|
40
|
-
print(
|
|
40
|
+
print("Invalid selection.")
|
|
41
41
|
sys.exit(1)
|
|
42
42
|
except GitCommitAIError as error:
|
|
43
43
|
print(str(error))
|
|
44
44
|
sys.exit(1)
|
|
45
45
|
except (KeyboardInterrupt, EOFError):
|
|
46
|
-
print(
|
|
46
|
+
print("Aborted.")
|
|
47
47
|
sys.exit(1)
|
|
48
48
|
|
|
49
49
|
|
|
50
|
-
if __name__ ==
|
|
50
|
+
if __name__ == "__main__": # pragma: no cover
|
|
51
51
|
main()
|
|
@@ -11,49 +11,49 @@ logger = logging.getLogger(__name__)
|
|
|
11
11
|
|
|
12
12
|
def get_default_editor() -> str:
|
|
13
13
|
current_platform = platform.system()
|
|
14
|
-
is_windows = current_platform ==
|
|
14
|
+
is_windows = current_platform == "Windows"
|
|
15
15
|
|
|
16
|
-
return
|
|
16
|
+
return "notepad" if is_windows else "vi"
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
def open_in_editor(initial_text: str) -> str:
|
|
20
20
|
try:
|
|
21
|
-
with tempfile.NamedTemporaryFile(suffix=
|
|
21
|
+
with tempfile.NamedTemporaryFile(suffix=".txt", mode="w", delete=False) as temp_file:
|
|
22
22
|
temp_file.write(initial_text)
|
|
23
23
|
temp_file_path = temp_file.name
|
|
24
24
|
except OSError:
|
|
25
|
-
logger.error(
|
|
26
|
-
raise EditorError(
|
|
25
|
+
logger.error("Could not create temporary file")
|
|
26
|
+
raise EditorError("Could not create a temporary file.")
|
|
27
27
|
|
|
28
|
-
logger.debug(
|
|
28
|
+
logger.debug("Temporary file created: %s", temp_file_path)
|
|
29
29
|
|
|
30
30
|
platform_default_editor = get_default_editor()
|
|
31
|
-
editor_command = os.environ.get(
|
|
31
|
+
editor_command = os.environ.get("EDITOR", platform_default_editor)
|
|
32
32
|
|
|
33
|
-
logger.debug(
|
|
33
|
+
logger.debug("Selected editor: %s", editor_command)
|
|
34
34
|
|
|
35
35
|
try:
|
|
36
36
|
try:
|
|
37
|
-
logger.debug(
|
|
37
|
+
logger.debug("Opening editor: %s %s", editor_command, temp_file_path)
|
|
38
38
|
subprocess.run([editor_command, temp_file_path], check=True)
|
|
39
39
|
except FileNotFoundError:
|
|
40
|
-
logger.error(
|
|
40
|
+
logger.error("Editor not found: %s", editor_command)
|
|
41
41
|
raise EditorError(f'Editor "{editor_command}" was not found. Set the EDITOR environment variable to a valid editor.')
|
|
42
42
|
except subprocess.CalledProcessError:
|
|
43
|
-
logger.error(
|
|
43
|
+
logger.error("Editor exited with error: %s", editor_command)
|
|
44
44
|
raise EditorError(f'Editor "{editor_command}" exited with an error.')
|
|
45
45
|
|
|
46
|
-
logger.debug(
|
|
46
|
+
logger.debug("Editor closed, reading edited content")
|
|
47
47
|
|
|
48
48
|
try:
|
|
49
49
|
with open(temp_file_path) as temp_file:
|
|
50
50
|
edited_text = temp_file.read().strip()
|
|
51
51
|
except OSError:
|
|
52
|
-
logger.error(
|
|
53
|
-
raise EditorError(
|
|
52
|
+
logger.error("Could not read temporary file: %s", temp_file_path)
|
|
53
|
+
raise EditorError("Could not read the edited commit message from the temporary file.")
|
|
54
54
|
finally:
|
|
55
55
|
os.unlink(temp_file_path)
|
|
56
56
|
|
|
57
|
-
logger.debug(
|
|
57
|
+
logger.debug("Edited text read: %d chars", len(edited_text))
|
|
58
58
|
|
|
59
59
|
return edited_text
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import subprocess
|
|
3
|
+
from typing import Final
|
|
4
|
+
|
|
5
|
+
from git_commit_msg_ai.exceptions import GitError
|
|
6
|
+
|
|
7
|
+
GIT_COMMAND: Final[str] = "git"
|
|
8
|
+
DIFF_SUBCOMMAND: Final[str] = "diff"
|
|
9
|
+
CACHED_FLAG: Final[str] = "--cached"
|
|
10
|
+
COMMIT_SUBCOMMAND: Final[str] = "commit"
|
|
11
|
+
MESSAGE_FLAG: Final[str] = "-m"
|
|
12
|
+
UTF8_ENCODING: Final[str] = "utf-8"
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def get_staged_diff() -> str:
|
|
18
|
+
try:
|
|
19
|
+
logger.debug("Running: %s %s %s", GIT_COMMAND, DIFF_SUBCOMMAND, CACHED_FLAG)
|
|
20
|
+
raw_diff_bytes = subprocess.check_output([GIT_COMMAND, DIFF_SUBCOMMAND, CACHED_FLAG])
|
|
21
|
+
staged_diff = raw_diff_bytes.decode(UTF8_ENCODING)
|
|
22
|
+
except FileNotFoundError:
|
|
23
|
+
logger.error("git not found on PATH")
|
|
24
|
+
raise GitError("git is not installed or not on PATH.")
|
|
25
|
+
except subprocess.CalledProcessError:
|
|
26
|
+
logger.error("git diff --cached exited non-zero")
|
|
27
|
+
raise GitError("Failed to get staged diff. Are you inside a git repository?")
|
|
28
|
+
except UnicodeDecodeError:
|
|
29
|
+
logger.error("Staged diff could not be decoded as UTF-8")
|
|
30
|
+
raise GitError("Staged diff contains bytes that could not be decoded as UTF-8.")
|
|
31
|
+
|
|
32
|
+
logger.debug("Staged diff received: %d chars", len(staged_diff))
|
|
33
|
+
|
|
34
|
+
if not staged_diff:
|
|
35
|
+
logger.warning("No staged changes found")
|
|
36
|
+
raise GitError("No staged changes found. Stage files with git add before running.")
|
|
37
|
+
|
|
38
|
+
return staged_diff
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def commit(message: str) -> None:
|
|
42
|
+
try:
|
|
43
|
+
logger.debug("Running: %s %s %s <message>", GIT_COMMAND, COMMIT_SUBCOMMAND, MESSAGE_FLAG)
|
|
44
|
+
subprocess.run([GIT_COMMAND, COMMIT_SUBCOMMAND, MESSAGE_FLAG, message], check=True)
|
|
45
|
+
except FileNotFoundError:
|
|
46
|
+
logger.error("git not found on PATH")
|
|
47
|
+
raise GitError("git is not installed or not on PATH.")
|
|
48
|
+
except subprocess.CalledProcessError:
|
|
49
|
+
logger.error("git commit exited non-zero")
|
|
50
|
+
raise GitError("git commit failed.")
|
|
51
|
+
|
|
52
|
+
logger.info("Commit created successfully")
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "git-commit-msg-ai"
|
|
7
|
-
version = "1.5.
|
|
7
|
+
version = "1.5.1"
|
|
8
8
|
description = "AI-powered git commit message generator following Conventional Commits"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -30,7 +30,7 @@ strict = true
|
|
|
30
30
|
|
|
31
31
|
[tool.pytest.ini_options]
|
|
32
32
|
testpaths = ["tests"]
|
|
33
|
-
addopts = "--cov --cov-report=term-missing"
|
|
33
|
+
addopts = "--cov --cov-report=term-missing --cov-report=html --cov-fail-under=80"
|
|
34
34
|
|
|
35
35
|
[tool.coverage.run]
|
|
36
36
|
source = ["git_commit_msg_ai"]
|
|
@@ -23,136 +23,136 @@ def _make_status_error(exception_class: type[anthropic.APIStatusError], status_c
|
|
|
23
23
|
mock_response = MagicMock()
|
|
24
24
|
mock_response.status_code = status_code
|
|
25
25
|
|
|
26
|
-
return exception_class(
|
|
26
|
+
return exception_class("error", response=mock_response, body={})
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
class TestGenerateCommitMessage:
|
|
30
30
|
def test_returns_stripped_commit_message(self) -> None:
|
|
31
31
|
with ExitStack() as stack:
|
|
32
|
-
mock_anthropic_class = stack.enter_context(patch(
|
|
32
|
+
mock_anthropic_class = stack.enter_context(patch("git_commit_msg_ai.ai_client.anthropic.Anthropic"))
|
|
33
33
|
mock_client = MagicMock()
|
|
34
|
-
mock_client.messages.create.return_value = _make_api_response(
|
|
34
|
+
mock_client.messages.create.return_value = _make_api_response(" feat: add feature ")
|
|
35
35
|
mock_anthropic_class.return_value = mock_client
|
|
36
36
|
|
|
37
|
-
result = generate_commit_message(
|
|
37
|
+
result = generate_commit_message("diff content")
|
|
38
38
|
|
|
39
|
-
assert result ==
|
|
39
|
+
assert result == "feat: add feature"
|
|
40
40
|
|
|
41
41
|
def test_raises_ai_error_on_authentication_error(self) -> None:
|
|
42
42
|
with ExitStack() as stack:
|
|
43
|
-
mock_anthropic_class = stack.enter_context(patch(
|
|
43
|
+
mock_anthropic_class = stack.enter_context(patch("git_commit_msg_ai.ai_client.anthropic.Anthropic"))
|
|
44
44
|
mock_client = MagicMock()
|
|
45
45
|
mock_client.messages.create.side_effect = _make_status_error(anthropic.AuthenticationError, 401)
|
|
46
46
|
mock_anthropic_class.return_value = mock_client
|
|
47
47
|
|
|
48
|
-
with pytest.raises(AIError, match=
|
|
49
|
-
generate_commit_message(
|
|
48
|
+
with pytest.raises(AIError, match="ANTHROPIC_API_KEY"):
|
|
49
|
+
generate_commit_message("diff content")
|
|
50
50
|
|
|
51
51
|
def test_raises_ai_error_on_rate_limit_error(self) -> None:
|
|
52
52
|
with ExitStack() as stack:
|
|
53
|
-
mock_anthropic_class = stack.enter_context(patch(
|
|
53
|
+
mock_anthropic_class = stack.enter_context(patch("git_commit_msg_ai.ai_client.anthropic.Anthropic"))
|
|
54
54
|
mock_client = MagicMock()
|
|
55
55
|
mock_client.messages.create.side_effect = _make_status_error(anthropic.RateLimitError, 429)
|
|
56
56
|
mock_anthropic_class.return_value = mock_client
|
|
57
57
|
|
|
58
|
-
with pytest.raises(AIError, match=
|
|
59
|
-
generate_commit_message(
|
|
58
|
+
with pytest.raises(AIError, match="rate limit"):
|
|
59
|
+
generate_commit_message("diff content")
|
|
60
60
|
|
|
61
61
|
def test_raises_ai_error_on_api_connection_error(self) -> None:
|
|
62
62
|
mock_request = MagicMock()
|
|
63
63
|
|
|
64
64
|
with ExitStack() as stack:
|
|
65
|
-
mock_anthropic_class = stack.enter_context(patch(
|
|
65
|
+
mock_anthropic_class = stack.enter_context(patch("git_commit_msg_ai.ai_client.anthropic.Anthropic"))
|
|
66
66
|
mock_client = MagicMock()
|
|
67
67
|
mock_client.messages.create.side_effect = anthropic.APIConnectionError(request=mock_request)
|
|
68
68
|
mock_anthropic_class.return_value = mock_client
|
|
69
69
|
|
|
70
|
-
with pytest.raises(AIError, match=
|
|
71
|
-
generate_commit_message(
|
|
70
|
+
with pytest.raises(AIError, match="network"):
|
|
71
|
+
generate_commit_message("diff content")
|
|
72
72
|
|
|
73
73
|
def test_raises_ai_error_on_api_status_error_with_status_code(self) -> None:
|
|
74
74
|
with ExitStack() as stack:
|
|
75
|
-
mock_anthropic_class = stack.enter_context(patch(
|
|
75
|
+
mock_anthropic_class = stack.enter_context(patch("git_commit_msg_ai.ai_client.anthropic.Anthropic"))
|
|
76
76
|
mock_client = MagicMock()
|
|
77
77
|
mock_client.messages.create.side_effect = _make_status_error(anthropic.APIStatusError, 500)
|
|
78
78
|
mock_anthropic_class.return_value = mock_client
|
|
79
79
|
|
|
80
|
-
with pytest.raises(AIError, match=
|
|
81
|
-
generate_commit_message(
|
|
80
|
+
with pytest.raises(AIError, match="500"):
|
|
81
|
+
generate_commit_message("diff content")
|
|
82
82
|
|
|
83
83
|
def test_logs_debug_before_api_call(self, caplog: pytest.LogCaptureFixture) -> None:
|
|
84
|
-
with caplog.at_level(logging.DEBUG, logger=
|
|
84
|
+
with caplog.at_level(logging.DEBUG, logger="git_commit_msg_ai.ai_client"):
|
|
85
85
|
with ExitStack() as stack:
|
|
86
|
-
mock_anthropic_class = stack.enter_context(patch(
|
|
86
|
+
mock_anthropic_class = stack.enter_context(patch("git_commit_msg_ai.ai_client.anthropic.Anthropic"))
|
|
87
87
|
mock_client = MagicMock()
|
|
88
|
-
mock_client.messages.create.return_value = _make_api_response(
|
|
88
|
+
mock_client.messages.create.return_value = _make_api_response("feat: add feature")
|
|
89
89
|
mock_anthropic_class.return_value = mock_client
|
|
90
|
-
generate_commit_message(
|
|
90
|
+
generate_commit_message("diff content")
|
|
91
91
|
|
|
92
92
|
messages = [r.message for r in caplog.records]
|
|
93
93
|
assert any(MODEL in m and str(MAX_TOKENS) in m for m in messages)
|
|
94
94
|
|
|
95
95
|
def test_logs_info_after_message_generated(self, caplog: pytest.LogCaptureFixture) -> None:
|
|
96
|
-
with caplog.at_level(logging.INFO, logger=
|
|
96
|
+
with caplog.at_level(logging.INFO, logger="git_commit_msg_ai.ai_client"):
|
|
97
97
|
with ExitStack() as stack:
|
|
98
|
-
mock_anthropic_class = stack.enter_context(patch(
|
|
98
|
+
mock_anthropic_class = stack.enter_context(patch("git_commit_msg_ai.ai_client.anthropic.Anthropic"))
|
|
99
99
|
mock_client = MagicMock()
|
|
100
|
-
mock_client.messages.create.return_value = _make_api_response(
|
|
100
|
+
mock_client.messages.create.return_value = _make_api_response("feat: add feature")
|
|
101
101
|
mock_anthropic_class.return_value = mock_client
|
|
102
|
-
generate_commit_message(
|
|
102
|
+
generate_commit_message("diff content")
|
|
103
103
|
|
|
104
|
-
assert any(r.levelno == logging.INFO and
|
|
104
|
+
assert any(r.levelno == logging.INFO and "Commit message generated" in r.message for r in caplog.records)
|
|
105
105
|
|
|
106
106
|
def test_logs_error_on_authentication_error(self, caplog: pytest.LogCaptureFixture) -> None:
|
|
107
|
-
with caplog.at_level(logging.ERROR, logger=
|
|
107
|
+
with caplog.at_level(logging.ERROR, logger="git_commit_msg_ai.ai_client"):
|
|
108
108
|
with ExitStack() as stack:
|
|
109
|
-
mock_anthropic_class = stack.enter_context(patch(
|
|
109
|
+
mock_anthropic_class = stack.enter_context(patch("git_commit_msg_ai.ai_client.anthropic.Anthropic"))
|
|
110
110
|
mock_client = MagicMock()
|
|
111
111
|
mock_client.messages.create.side_effect = _make_status_error(anthropic.AuthenticationError, 401)
|
|
112
112
|
mock_anthropic_class.return_value = mock_client
|
|
113
113
|
|
|
114
114
|
with pytest.raises(AIError):
|
|
115
|
-
generate_commit_message(
|
|
115
|
+
generate_commit_message("diff content")
|
|
116
116
|
|
|
117
117
|
assert any(r.levelno == logging.ERROR for r in caplog.records)
|
|
118
118
|
|
|
119
119
|
def test_logs_error_on_rate_limit_error(self, caplog: pytest.LogCaptureFixture) -> None:
|
|
120
|
-
with caplog.at_level(logging.ERROR, logger=
|
|
120
|
+
with caplog.at_level(logging.ERROR, logger="git_commit_msg_ai.ai_client"):
|
|
121
121
|
with ExitStack() as stack:
|
|
122
|
-
mock_anthropic_class = stack.enter_context(patch(
|
|
122
|
+
mock_anthropic_class = stack.enter_context(patch("git_commit_msg_ai.ai_client.anthropic.Anthropic"))
|
|
123
123
|
mock_client = MagicMock()
|
|
124
124
|
mock_client.messages.create.side_effect = _make_status_error(anthropic.RateLimitError, 429)
|
|
125
125
|
mock_anthropic_class.return_value = mock_client
|
|
126
126
|
|
|
127
127
|
with pytest.raises(AIError):
|
|
128
|
-
generate_commit_message(
|
|
128
|
+
generate_commit_message("diff content")
|
|
129
129
|
|
|
130
130
|
assert any(r.levelno == logging.ERROR for r in caplog.records)
|
|
131
131
|
|
|
132
132
|
def test_logs_error_on_api_connection_error(self, caplog: pytest.LogCaptureFixture) -> None:
|
|
133
133
|
mock_request = MagicMock()
|
|
134
134
|
|
|
135
|
-
with caplog.at_level(logging.ERROR, logger=
|
|
135
|
+
with caplog.at_level(logging.ERROR, logger="git_commit_msg_ai.ai_client"):
|
|
136
136
|
with ExitStack() as stack:
|
|
137
|
-
mock_anthropic_class = stack.enter_context(patch(
|
|
137
|
+
mock_anthropic_class = stack.enter_context(patch("git_commit_msg_ai.ai_client.anthropic.Anthropic"))
|
|
138
138
|
mock_client = MagicMock()
|
|
139
139
|
mock_client.messages.create.side_effect = anthropic.APIConnectionError(request=mock_request)
|
|
140
140
|
mock_anthropic_class.return_value = mock_client
|
|
141
141
|
|
|
142
142
|
with pytest.raises(AIError):
|
|
143
|
-
generate_commit_message(
|
|
143
|
+
generate_commit_message("diff content")
|
|
144
144
|
|
|
145
145
|
assert any(r.levelno == logging.ERROR for r in caplog.records)
|
|
146
146
|
|
|
147
147
|
def test_logs_error_on_api_status_error(self, caplog: pytest.LogCaptureFixture) -> None:
|
|
148
|
-
with caplog.at_level(logging.ERROR, logger=
|
|
148
|
+
with caplog.at_level(logging.ERROR, logger="git_commit_msg_ai.ai_client"):
|
|
149
149
|
with ExitStack() as stack:
|
|
150
|
-
mock_anthropic_class = stack.enter_context(patch(
|
|
150
|
+
mock_anthropic_class = stack.enter_context(patch("git_commit_msg_ai.ai_client.anthropic.Anthropic"))
|
|
151
151
|
mock_client = MagicMock()
|
|
152
152
|
mock_client.messages.create.side_effect = _make_status_error(anthropic.APIStatusError, 500)
|
|
153
153
|
mock_anthropic_class.return_value = mock_client
|
|
154
154
|
|
|
155
155
|
with pytest.raises(AIError):
|
|
156
|
-
generate_commit_message(
|
|
156
|
+
generate_commit_message("diff content")
|
|
157
157
|
|
|
158
|
-
assert any(r.levelno == logging.ERROR and
|
|
158
|
+
assert any(r.levelno == logging.ERROR and "500" in r.message for r in caplog.records)
|