git-commit-msg-ai 1.4.0__tar.gz → 1.4.2__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.4.0 → git_commit_msg_ai-1.4.2}/PKG-INFO +38 -4
- {git_commit_msg_ai-1.4.0 → git_commit_msg_ai-1.4.2}/README.md +35 -3
- {git_commit_msg_ai-1.4.0 → git_commit_msg_ai-1.4.2}/git_commit_msg_ai/git_ops.py +10 -2
- {git_commit_msg_ai-1.4.0 → git_commit_msg_ai-1.4.2}/git_commit_msg_ai.egg-info/PKG-INFO +38 -4
- {git_commit_msg_ai-1.4.0 → git_commit_msg_ai-1.4.2}/git_commit_msg_ai.egg-info/requires.txt +2 -0
- {git_commit_msg_ai-1.4.0 → git_commit_msg_ai-1.4.2}/pyproject.toml +2 -2
- {git_commit_msg_ai-1.4.0 → git_commit_msg_ai-1.4.2}/tests/test_ai_client.py +11 -5
- git_commit_msg_ai-1.4.2/tests/test_cli.py +149 -0
- {git_commit_msg_ai-1.4.0 → git_commit_msg_ai-1.4.2}/tests/test_editor.py +25 -18
- {git_commit_msg_ai-1.4.0 → git_commit_msg_ai-1.4.2}/tests/test_git_ops.py +25 -14
- git_commit_msg_ai-1.4.0/tests/test_cli.py +0 -139
- {git_commit_msg_ai-1.4.0 → git_commit_msg_ai-1.4.2}/git_commit_msg_ai/__init__.py +0 -0
- {git_commit_msg_ai-1.4.0 → git_commit_msg_ai-1.4.2}/git_commit_msg_ai/ai_client.py +0 -0
- {git_commit_msg_ai-1.4.0 → git_commit_msg_ai-1.4.2}/git_commit_msg_ai/cli.py +0 -0
- {git_commit_msg_ai-1.4.0 → git_commit_msg_ai-1.4.2}/git_commit_msg_ai/editor.py +0 -0
- {git_commit_msg_ai-1.4.0 → git_commit_msg_ai-1.4.2}/git_commit_msg_ai/exceptions.py +0 -0
- {git_commit_msg_ai-1.4.0 → git_commit_msg_ai-1.4.2}/git_commit_msg_ai.egg-info/SOURCES.txt +0 -0
- {git_commit_msg_ai-1.4.0 → git_commit_msg_ai-1.4.2}/git_commit_msg_ai.egg-info/dependency_links.txt +0 -0
- {git_commit_msg_ai-1.4.0 → git_commit_msg_ai-1.4.2}/git_commit_msg_ai.egg-info/entry_points.txt +0 -0
- {git_commit_msg_ai-1.4.0 → git_commit_msg_ai-1.4.2}/git_commit_msg_ai.egg-info/top_level.txt +0 -0
- {git_commit_msg_ai-1.4.0 → git_commit_msg_ai-1.4.2}/setup.cfg +0 -0
- {git_commit_msg_ai-1.4.0 → git_commit_msg_ai-1.4.2}/tests/test_exceptions.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: git-commit-msg-ai
|
|
3
|
-
Version: 1.4.
|
|
3
|
+
Version: 1.4.2
|
|
4
4
|
Summary: AI-powered git commit message generator following Conventional Commits
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
Requires-Python: >=3.10
|
|
@@ -11,6 +11,8 @@ Requires-Dist: mypy; extra == "dev"
|
|
|
11
11
|
Requires-Dist: ruff; extra == "dev"
|
|
12
12
|
Requires-Dist: pytest; extra == "dev"
|
|
13
13
|
Requires-Dist: pytest-cov; extra == "dev"
|
|
14
|
+
Requires-Dist: build; extra == "dev"
|
|
15
|
+
Requires-Dist: twine; extra == "dev"
|
|
14
16
|
|
|
15
17
|
# git-commit-msg-ai
|
|
16
18
|
|
|
@@ -29,6 +31,38 @@ AI-powered git commit message generator that follows the [Conventional Commits](
|
|
|
29
31
|
[System.Environment]::SetEnvironmentVariable('ANTHROPIC_API_KEY', 'sk-ant-...', 'User') # Windows
|
|
30
32
|
```
|
|
31
33
|
|
|
34
|
+
## Development Setup
|
|
35
|
+
|
|
36
|
+
Clone the repository, create a virtual environment, and install the project in editable mode with all dev dependencies:
|
|
37
|
+
|
|
38
|
+
```sh
|
|
39
|
+
python -m venv .venv
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Activate the virtual environment:
|
|
43
|
+
|
|
44
|
+
```sh
|
|
45
|
+
# macOS/Linux
|
|
46
|
+
source .venv/bin/activate
|
|
47
|
+
|
|
48
|
+
# Windows PowerShell
|
|
49
|
+
.venv\Scripts\Activate.ps1
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Install the project and dev dependencies:
|
|
53
|
+
|
|
54
|
+
```sh
|
|
55
|
+
pip install -e ".[dev]"
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
After activation the `git-commit-msg-ai` entry-point is on your PATH. You can also run the dev toolchain:
|
|
59
|
+
|
|
60
|
+
```sh
|
|
61
|
+
pytest # run tests with coverage
|
|
62
|
+
ruff check . # lint
|
|
63
|
+
mypy . # type-check
|
|
64
|
+
```
|
|
65
|
+
|
|
32
66
|
## Installation
|
|
33
67
|
|
|
34
68
|
```sh
|
|
@@ -53,9 +87,9 @@ The tool will:
|
|
|
53
87
|
[a]ccept / [e]dit / [r]eject:
|
|
54
88
|
```
|
|
55
89
|
|
|
56
|
-
- **a**
|
|
57
|
-
- **e**
|
|
58
|
-
- **r**
|
|
90
|
+
- **a** - commits immediately with the generated message
|
|
91
|
+
- **e** - opens the message in your `$EDITOR` (defaults to `notepad` on Windows, `vi` on Linux/macOS), lets you modify it, then commits
|
|
92
|
+
- **r** - exits without committing
|
|
59
93
|
|
|
60
94
|
## Commit message format
|
|
61
95
|
|
|
@@ -15,6 +15,38 @@ AI-powered git commit message generator that follows the [Conventional Commits](
|
|
|
15
15
|
[System.Environment]::SetEnvironmentVariable('ANTHROPIC_API_KEY', 'sk-ant-...', 'User') # Windows
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
+
## Development Setup
|
|
19
|
+
|
|
20
|
+
Clone the repository, create a virtual environment, and install the project in editable mode with all dev dependencies:
|
|
21
|
+
|
|
22
|
+
```sh
|
|
23
|
+
python -m venv .venv
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Activate the virtual environment:
|
|
27
|
+
|
|
28
|
+
```sh
|
|
29
|
+
# macOS/Linux
|
|
30
|
+
source .venv/bin/activate
|
|
31
|
+
|
|
32
|
+
# Windows PowerShell
|
|
33
|
+
.venv\Scripts\Activate.ps1
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Install the project and dev dependencies:
|
|
37
|
+
|
|
38
|
+
```sh
|
|
39
|
+
pip install -e ".[dev]"
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
After activation the `git-commit-msg-ai` entry-point is on your PATH. You can also run the dev toolchain:
|
|
43
|
+
|
|
44
|
+
```sh
|
|
45
|
+
pytest # run tests with coverage
|
|
46
|
+
ruff check . # lint
|
|
47
|
+
mypy . # type-check
|
|
48
|
+
```
|
|
49
|
+
|
|
18
50
|
## Installation
|
|
19
51
|
|
|
20
52
|
```sh
|
|
@@ -39,9 +71,9 @@ The tool will:
|
|
|
39
71
|
[a]ccept / [e]dit / [r]eject:
|
|
40
72
|
```
|
|
41
73
|
|
|
42
|
-
- **a**
|
|
43
|
-
- **e**
|
|
44
|
-
- **r**
|
|
74
|
+
- **a** - commits immediately with the generated message
|
|
75
|
+
- **e** - opens the message in your `$EDITOR` (defaults to `notepad` on Windows, `vi` on Linux/macOS), lets you modify it, then commits
|
|
76
|
+
- **r** - exits without committing
|
|
45
77
|
|
|
46
78
|
## Commit message format
|
|
47
79
|
|
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
import subprocess
|
|
2
|
+
from typing import Final
|
|
2
3
|
|
|
3
4
|
from git_commit_msg_ai.exceptions import GitError
|
|
4
5
|
|
|
6
|
+
GIT_COMMAND: Final[str] = 'git'
|
|
7
|
+
DIFF_SUBCOMMAND: Final[str] = 'diff'
|
|
8
|
+
CACHED_FLAG: Final[str] = '--cached'
|
|
9
|
+
COMMIT_SUBCOMMAND: Final[str] = 'commit'
|
|
10
|
+
MESSAGE_FLAG: Final[str] = '-m'
|
|
11
|
+
UTF8_ENCODING: Final[str] = 'utf-8'
|
|
12
|
+
|
|
5
13
|
|
|
6
14
|
def get_staged_diff() -> str:
|
|
7
15
|
try:
|
|
8
|
-
staged_diff = subprocess.check_output([
|
|
16
|
+
staged_diff = subprocess.check_output([GIT_COMMAND, DIFF_SUBCOMMAND, CACHED_FLAG]).decode(UTF8_ENCODING)
|
|
9
17
|
except FileNotFoundError:
|
|
10
18
|
raise GitError('git is not installed or not on PATH.')
|
|
11
19
|
except subprocess.CalledProcessError:
|
|
@@ -21,7 +29,7 @@ def get_staged_diff() -> str:
|
|
|
21
29
|
|
|
22
30
|
def commit(message: str) -> None:
|
|
23
31
|
try:
|
|
24
|
-
subprocess.run([
|
|
32
|
+
subprocess.run([GIT_COMMAND, COMMIT_SUBCOMMAND, MESSAGE_FLAG, message], check=True)
|
|
25
33
|
except FileNotFoundError:
|
|
26
34
|
raise GitError('git is not installed or not on PATH.')
|
|
27
35
|
except subprocess.CalledProcessError:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: git-commit-msg-ai
|
|
3
|
-
Version: 1.4.
|
|
3
|
+
Version: 1.4.2
|
|
4
4
|
Summary: AI-powered git commit message generator following Conventional Commits
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
Requires-Python: >=3.10
|
|
@@ -11,6 +11,8 @@ Requires-Dist: mypy; extra == "dev"
|
|
|
11
11
|
Requires-Dist: ruff; extra == "dev"
|
|
12
12
|
Requires-Dist: pytest; extra == "dev"
|
|
13
13
|
Requires-Dist: pytest-cov; extra == "dev"
|
|
14
|
+
Requires-Dist: build; extra == "dev"
|
|
15
|
+
Requires-Dist: twine; extra == "dev"
|
|
14
16
|
|
|
15
17
|
# git-commit-msg-ai
|
|
16
18
|
|
|
@@ -29,6 +31,38 @@ AI-powered git commit message generator that follows the [Conventional Commits](
|
|
|
29
31
|
[System.Environment]::SetEnvironmentVariable('ANTHROPIC_API_KEY', 'sk-ant-...', 'User') # Windows
|
|
30
32
|
```
|
|
31
33
|
|
|
34
|
+
## Development Setup
|
|
35
|
+
|
|
36
|
+
Clone the repository, create a virtual environment, and install the project in editable mode with all dev dependencies:
|
|
37
|
+
|
|
38
|
+
```sh
|
|
39
|
+
python -m venv .venv
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Activate the virtual environment:
|
|
43
|
+
|
|
44
|
+
```sh
|
|
45
|
+
# macOS/Linux
|
|
46
|
+
source .venv/bin/activate
|
|
47
|
+
|
|
48
|
+
# Windows PowerShell
|
|
49
|
+
.venv\Scripts\Activate.ps1
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Install the project and dev dependencies:
|
|
53
|
+
|
|
54
|
+
```sh
|
|
55
|
+
pip install -e ".[dev]"
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
After activation the `git-commit-msg-ai` entry-point is on your PATH. You can also run the dev toolchain:
|
|
59
|
+
|
|
60
|
+
```sh
|
|
61
|
+
pytest # run tests with coverage
|
|
62
|
+
ruff check . # lint
|
|
63
|
+
mypy . # type-check
|
|
64
|
+
```
|
|
65
|
+
|
|
32
66
|
## Installation
|
|
33
67
|
|
|
34
68
|
```sh
|
|
@@ -53,9 +87,9 @@ The tool will:
|
|
|
53
87
|
[a]ccept / [e]dit / [r]eject:
|
|
54
88
|
```
|
|
55
89
|
|
|
56
|
-
- **a**
|
|
57
|
-
- **e**
|
|
58
|
-
- **r**
|
|
90
|
+
- **a** - commits immediately with the generated message
|
|
91
|
+
- **e** - opens the message in your `$EDITOR` (defaults to `notepad` on Windows, `vi` on Linux/macOS), lets you modify it, then commits
|
|
92
|
+
- **r** - exits without committing
|
|
59
93
|
|
|
60
94
|
## Commit message format
|
|
61
95
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "git-commit-msg-ai"
|
|
7
|
-
version = "1.4.
|
|
7
|
+
version = "1.4.2"
|
|
8
8
|
description = "AI-powered git commit message generator following Conventional Commits"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -15,7 +15,7 @@ dependencies = ["anthropic"]
|
|
|
15
15
|
git-commit-msg-ai = "git_commit_msg_ai.cli:main"
|
|
16
16
|
|
|
17
17
|
[project.optional-dependencies]
|
|
18
|
-
dev = ["mypy", "ruff", "pytest", "pytest-cov"]
|
|
18
|
+
dev = ["mypy", "ruff", "pytest", "pytest-cov", "build", "twine"]
|
|
19
19
|
|
|
20
20
|
[tool.ruff]
|
|
21
21
|
target-version = "py310"
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from contextlib import ExitStack
|
|
1
2
|
from unittest.mock import MagicMock, patch
|
|
2
3
|
|
|
3
4
|
import anthropic
|
|
@@ -26,7 +27,8 @@ def _make_status_error(exception_class: type[anthropic.APIStatusError], status_c
|
|
|
26
27
|
|
|
27
28
|
class TestGenerateCommitMessage:
|
|
28
29
|
def test_returns_stripped_commit_message(self) -> None:
|
|
29
|
-
with
|
|
30
|
+
with ExitStack() as stack:
|
|
31
|
+
mock_anthropic_class = stack.enter_context(patch('git_commit_msg_ai.ai_client.anthropic.Anthropic'))
|
|
30
32
|
mock_client = MagicMock()
|
|
31
33
|
mock_client.messages.create.return_value = _make_api_response(' feat: add feature ')
|
|
32
34
|
mock_anthropic_class.return_value = mock_client
|
|
@@ -36,7 +38,8 @@ class TestGenerateCommitMessage:
|
|
|
36
38
|
assert result == 'feat: add feature'
|
|
37
39
|
|
|
38
40
|
def test_raises_ai_error_on_authentication_error(self) -> None:
|
|
39
|
-
with
|
|
41
|
+
with ExitStack() as stack:
|
|
42
|
+
mock_anthropic_class = stack.enter_context(patch('git_commit_msg_ai.ai_client.anthropic.Anthropic'))
|
|
40
43
|
mock_client = MagicMock()
|
|
41
44
|
mock_client.messages.create.side_effect = _make_status_error(anthropic.AuthenticationError, 401)
|
|
42
45
|
mock_anthropic_class.return_value = mock_client
|
|
@@ -45,7 +48,8 @@ class TestGenerateCommitMessage:
|
|
|
45
48
|
generate_commit_message('diff content')
|
|
46
49
|
|
|
47
50
|
def test_raises_ai_error_on_rate_limit_error(self) -> None:
|
|
48
|
-
with
|
|
51
|
+
with ExitStack() as stack:
|
|
52
|
+
mock_anthropic_class = stack.enter_context(patch('git_commit_msg_ai.ai_client.anthropic.Anthropic'))
|
|
49
53
|
mock_client = MagicMock()
|
|
50
54
|
mock_client.messages.create.side_effect = _make_status_error(anthropic.RateLimitError, 429)
|
|
51
55
|
mock_anthropic_class.return_value = mock_client
|
|
@@ -56,7 +60,8 @@ class TestGenerateCommitMessage:
|
|
|
56
60
|
def test_raises_ai_error_on_api_connection_error(self) -> None:
|
|
57
61
|
mock_request = MagicMock()
|
|
58
62
|
|
|
59
|
-
with
|
|
63
|
+
with ExitStack() as stack:
|
|
64
|
+
mock_anthropic_class = stack.enter_context(patch('git_commit_msg_ai.ai_client.anthropic.Anthropic'))
|
|
60
65
|
mock_client = MagicMock()
|
|
61
66
|
mock_client.messages.create.side_effect = anthropic.APIConnectionError(request=mock_request)
|
|
62
67
|
mock_anthropic_class.return_value = mock_client
|
|
@@ -65,7 +70,8 @@ class TestGenerateCommitMessage:
|
|
|
65
70
|
generate_commit_message('diff content')
|
|
66
71
|
|
|
67
72
|
def test_raises_ai_error_on_api_status_error_with_status_code(self) -> None:
|
|
68
|
-
with
|
|
73
|
+
with ExitStack() as stack:
|
|
74
|
+
mock_anthropic_class = stack.enter_context(patch('git_commit_msg_ai.ai_client.anthropic.Anthropic'))
|
|
69
75
|
mock_client = MagicMock()
|
|
70
76
|
mock_client.messages.create.side_effect = _make_status_error(anthropic.APIStatusError, 500)
|
|
71
77
|
mock_anthropic_class.return_value = mock_client
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
from contextlib import ExitStack
|
|
2
|
+
from unittest.mock import MagicMock, patch
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
6
|
+
from git_commit_msg_ai.cli import main
|
|
7
|
+
from git_commit_msg_ai.exceptions import AIError, GitError
|
|
8
|
+
|
|
9
|
+
COMMIT_MESSAGE = 'feat: add feature'
|
|
10
|
+
EDITED_COMMIT_MESSAGE = 'edited commit message'
|
|
11
|
+
STAGED_DIFF = 'diff content'
|
|
12
|
+
GENERIC_COMMIT_MESSAGE = 'generic commit message'
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class TestMain:
|
|
16
|
+
def _run_with_input(self, user_input: str, commit_message: str = COMMIT_MESSAGE) -> tuple[MagicMock, MagicMock, MagicMock]:
|
|
17
|
+
mock_git_ops = MagicMock()
|
|
18
|
+
mock_git_ops.get_staged_diff.return_value = STAGED_DIFF
|
|
19
|
+
|
|
20
|
+
mock_ai_client = MagicMock()
|
|
21
|
+
mock_ai_client.generate_commit_message.return_value = commit_message
|
|
22
|
+
|
|
23
|
+
mock_editor = MagicMock()
|
|
24
|
+
mock_editor.open_in_editor.return_value = EDITED_COMMIT_MESSAGE
|
|
25
|
+
|
|
26
|
+
with ExitStack() as stack:
|
|
27
|
+
stack.enter_context(patch('git_commit_msg_ai.cli.git_ops', mock_git_ops))
|
|
28
|
+
stack.enter_context(patch('git_commit_msg_ai.cli.ai_client', mock_ai_client))
|
|
29
|
+
stack.enter_context(patch('git_commit_msg_ai.cli.editor', mock_editor))
|
|
30
|
+
stack.enter_context(patch('builtins.input', return_value=user_input))
|
|
31
|
+
main()
|
|
32
|
+
|
|
33
|
+
return mock_git_ops, mock_ai_client, mock_editor
|
|
34
|
+
|
|
35
|
+
def test_accept_calls_commit_with_generated_message(self) -> None:
|
|
36
|
+
mock_git_ops, _, _ = self._run_with_input('a', COMMIT_MESSAGE)
|
|
37
|
+
|
|
38
|
+
mock_git_ops.commit.assert_called_once_with(COMMIT_MESSAGE)
|
|
39
|
+
|
|
40
|
+
def test_edit_opens_editor_then_commits_edited_message(self) -> None:
|
|
41
|
+
mock_git_ops, _, mock_editor = self._run_with_input('e', COMMIT_MESSAGE)
|
|
42
|
+
|
|
43
|
+
mock_editor.open_in_editor.assert_called_once_with(COMMIT_MESSAGE)
|
|
44
|
+
mock_git_ops.commit.assert_called_once_with(EDITED_COMMIT_MESSAGE)
|
|
45
|
+
|
|
46
|
+
def test_reject_does_not_commit(self) -> None:
|
|
47
|
+
mock_git_ops, _, _ = self._run_with_input('r')
|
|
48
|
+
|
|
49
|
+
mock_git_ops.commit.assert_not_called()
|
|
50
|
+
|
|
51
|
+
def test_invalid_input_exits_with_code_1(self) -> None:
|
|
52
|
+
mock_git_ops = MagicMock()
|
|
53
|
+
mock_git_ops.get_staged_diff.return_value = STAGED_DIFF
|
|
54
|
+
|
|
55
|
+
mock_ai_client = MagicMock()
|
|
56
|
+
mock_ai_client.generate_commit_message.return_value = GENERIC_COMMIT_MESSAGE
|
|
57
|
+
|
|
58
|
+
with ExitStack() as stack:
|
|
59
|
+
stack.enter_context(patch('git_commit_msg_ai.cli.git_ops', mock_git_ops))
|
|
60
|
+
stack.enter_context(patch('git_commit_msg_ai.cli.ai_client', mock_ai_client))
|
|
61
|
+
stack.enter_context(patch('git_commit_msg_ai.cli.editor'))
|
|
62
|
+
stack.enter_context(patch('builtins.input', return_value='x'))
|
|
63
|
+
with pytest.raises(SystemExit) as exc_info:
|
|
64
|
+
main()
|
|
65
|
+
|
|
66
|
+
assert exc_info.value.code == 1
|
|
67
|
+
|
|
68
|
+
def test_git_error_from_get_staged_diff_exits_with_message(self, capsys: pytest.CaptureFixture[str]) -> None:
|
|
69
|
+
mock_git_ops = MagicMock()
|
|
70
|
+
mock_git_ops.get_staged_diff.side_effect = GitError('staged error')
|
|
71
|
+
|
|
72
|
+
with ExitStack() as stack:
|
|
73
|
+
stack.enter_context(patch('git_commit_msg_ai.cli.git_ops', mock_git_ops))
|
|
74
|
+
with pytest.raises(SystemExit) as exc_info:
|
|
75
|
+
main()
|
|
76
|
+
|
|
77
|
+
assert exc_info.value.code == 1
|
|
78
|
+
assert 'staged error' in capsys.readouterr().out
|
|
79
|
+
|
|
80
|
+
def test_ai_error_from_generate_commit_message_exits_with_message(self, capsys: pytest.CaptureFixture[str]) -> None:
|
|
81
|
+
mock_git_ops = MagicMock()
|
|
82
|
+
mock_git_ops.get_staged_diff.return_value = STAGED_DIFF
|
|
83
|
+
|
|
84
|
+
mock_ai_client = MagicMock()
|
|
85
|
+
mock_ai_client.generate_commit_message.side_effect = AIError('ai error')
|
|
86
|
+
|
|
87
|
+
with ExitStack() as stack:
|
|
88
|
+
stack.enter_context(patch('git_commit_msg_ai.cli.git_ops', mock_git_ops))
|
|
89
|
+
stack.enter_context(patch('git_commit_msg_ai.cli.ai_client', mock_ai_client))
|
|
90
|
+
with pytest.raises(SystemExit) as exc_info:
|
|
91
|
+
main()
|
|
92
|
+
|
|
93
|
+
assert exc_info.value.code == 1
|
|
94
|
+
assert 'ai error' in capsys.readouterr().out
|
|
95
|
+
|
|
96
|
+
def test_git_error_from_commit_exits_with_message(self, capsys: pytest.CaptureFixture[str]) -> None:
|
|
97
|
+
mock_git_ops = MagicMock()
|
|
98
|
+
mock_git_ops.get_staged_diff.return_value = STAGED_DIFF
|
|
99
|
+
mock_git_ops.commit.side_effect = GitError('commit error')
|
|
100
|
+
|
|
101
|
+
mock_ai_client = MagicMock()
|
|
102
|
+
mock_ai_client.generate_commit_message.return_value = GENERIC_COMMIT_MESSAGE
|
|
103
|
+
|
|
104
|
+
with ExitStack() as stack:
|
|
105
|
+
stack.enter_context(patch('git_commit_msg_ai.cli.git_ops', mock_git_ops))
|
|
106
|
+
stack.enter_context(patch('git_commit_msg_ai.cli.ai_client', mock_ai_client))
|
|
107
|
+
stack.enter_context(patch('git_commit_msg_ai.cli.editor'))
|
|
108
|
+
stack.enter_context(patch('builtins.input', return_value='a'))
|
|
109
|
+
with pytest.raises(SystemExit) as exc_info:
|
|
110
|
+
main()
|
|
111
|
+
|
|
112
|
+
assert exc_info.value.code == 1
|
|
113
|
+
assert 'commit error' in capsys.readouterr().out
|
|
114
|
+
|
|
115
|
+
def test_keyboard_interrupt_exits_with_aborted_message(self, capsys: pytest.CaptureFixture[str]) -> None:
|
|
116
|
+
mock_git_ops = MagicMock()
|
|
117
|
+
mock_git_ops.get_staged_diff.return_value = STAGED_DIFF
|
|
118
|
+
|
|
119
|
+
mock_ai_client = MagicMock()
|
|
120
|
+
mock_ai_client.generate_commit_message.return_value = GENERIC_COMMIT_MESSAGE
|
|
121
|
+
|
|
122
|
+
with ExitStack() as stack:
|
|
123
|
+
stack.enter_context(patch('git_commit_msg_ai.cli.git_ops', mock_git_ops))
|
|
124
|
+
stack.enter_context(patch('git_commit_msg_ai.cli.ai_client', mock_ai_client))
|
|
125
|
+
stack.enter_context(patch('git_commit_msg_ai.cli.editor'))
|
|
126
|
+
stack.enter_context(patch('builtins.input', side_effect=KeyboardInterrupt))
|
|
127
|
+
with pytest.raises(SystemExit) as exc_info:
|
|
128
|
+
main()
|
|
129
|
+
|
|
130
|
+
assert exc_info.value.code == 1
|
|
131
|
+
assert 'Aborted.' in capsys.readouterr().out
|
|
132
|
+
|
|
133
|
+
def test_eof_error_exits_with_aborted_message(self, capsys: pytest.CaptureFixture[str]) -> None:
|
|
134
|
+
mock_git_ops = MagicMock()
|
|
135
|
+
mock_git_ops.get_staged_diff.return_value = STAGED_DIFF
|
|
136
|
+
|
|
137
|
+
mock_ai_client = MagicMock()
|
|
138
|
+
mock_ai_client.generate_commit_message.return_value = GENERIC_COMMIT_MESSAGE
|
|
139
|
+
|
|
140
|
+
with ExitStack() as stack:
|
|
141
|
+
stack.enter_context(patch('git_commit_msg_ai.cli.git_ops', mock_git_ops))
|
|
142
|
+
stack.enter_context(patch('git_commit_msg_ai.cli.ai_client', mock_ai_client))
|
|
143
|
+
stack.enter_context(patch('git_commit_msg_ai.cli.editor'))
|
|
144
|
+
stack.enter_context(patch('builtins.input', side_effect=EOFError))
|
|
145
|
+
with pytest.raises(SystemExit) as exc_info:
|
|
146
|
+
main()
|
|
147
|
+
|
|
148
|
+
assert exc_info.value.code == 1
|
|
149
|
+
assert 'Aborted.' in capsys.readouterr().out
|
|
@@ -10,6 +10,11 @@ from git_commit_msg_ai.editor import get_default_editor, open_in_editor
|
|
|
10
10
|
from git_commit_msg_ai.exceptions import EditorError
|
|
11
11
|
|
|
12
12
|
TEMP_FILE_PATH = os.path.join(tempfile.gettempdir(), 'tmpfile.txt')
|
|
13
|
+
INITIAL_TEXT = 'initial text'
|
|
14
|
+
EDITED_TEXT_WITH_WHITESPACE = ' edited text '
|
|
15
|
+
EDITED_TEXT = 'edited text'
|
|
16
|
+
GENERIC_TEXT = 'text'
|
|
17
|
+
VIM_EDITOR = 'vim'
|
|
13
18
|
|
|
14
19
|
|
|
15
20
|
def _make_named_temporary_file_mock() -> MagicMock:
|
|
@@ -35,24 +40,24 @@ class TestOpenInEditor:
|
|
|
35
40
|
with ExitStack() as stack:
|
|
36
41
|
stack.enter_context(patch('git_commit_msg_ai.editor.tempfile.NamedTemporaryFile', return_value=_make_named_temporary_file_mock()))
|
|
37
42
|
stack.enter_context(patch('git_commit_msg_ai.editor.subprocess.run'))
|
|
38
|
-
stack.enter_context(patch('git_commit_msg_ai.editor.open', return_value=_make_open_mock(
|
|
43
|
+
stack.enter_context(patch('git_commit_msg_ai.editor.open', return_value=_make_open_mock(EDITED_TEXT_WITH_WHITESPACE)))
|
|
39
44
|
stack.enter_context(patch('git_commit_msg_ai.editor.os.unlink'))
|
|
40
45
|
|
|
41
|
-
result = open_in_editor(
|
|
46
|
+
result = open_in_editor(INITIAL_TEXT)
|
|
42
47
|
|
|
43
|
-
assert result ==
|
|
48
|
+
assert result == EDITED_TEXT
|
|
44
49
|
|
|
45
50
|
def test_uses_editor_env_var(self) -> None:
|
|
46
51
|
with ExitStack() as stack:
|
|
47
52
|
stack.enter_context(patch('git_commit_msg_ai.editor.tempfile.NamedTemporaryFile', return_value=_make_named_temporary_file_mock()))
|
|
48
53
|
mock_run = stack.enter_context(patch('git_commit_msg_ai.editor.subprocess.run'))
|
|
49
|
-
stack.enter_context(patch('git_commit_msg_ai.editor.open', return_value=_make_open_mock(
|
|
54
|
+
stack.enter_context(patch('git_commit_msg_ai.editor.open', return_value=_make_open_mock(GENERIC_TEXT)))
|
|
50
55
|
stack.enter_context(patch('git_commit_msg_ai.editor.os.unlink'))
|
|
51
|
-
stack.enter_context(patch('git_commit_msg_ai.editor.os.environ.get', return_value=
|
|
56
|
+
stack.enter_context(patch('git_commit_msg_ai.editor.os.environ.get', return_value=VIM_EDITOR))
|
|
52
57
|
|
|
53
|
-
open_in_editor(
|
|
58
|
+
open_in_editor(INITIAL_TEXT)
|
|
54
59
|
|
|
55
|
-
mock_run.assert_called_once_with([
|
|
60
|
+
mock_run.assert_called_once_with([VIM_EDITOR, TEMP_FILE_PATH], check=True)
|
|
56
61
|
|
|
57
62
|
def test_falls_back_to_platform_default_when_editor_not_set(self) -> None:
|
|
58
63
|
sentinel_editor = 'test-sentinel-editor'
|
|
@@ -60,12 +65,12 @@ class TestOpenInEditor:
|
|
|
60
65
|
with ExitStack() as stack:
|
|
61
66
|
stack.enter_context(patch('git_commit_msg_ai.editor.tempfile.NamedTemporaryFile', return_value=_make_named_temporary_file_mock()))
|
|
62
67
|
mock_run = stack.enter_context(patch('git_commit_msg_ai.editor.subprocess.run'))
|
|
63
|
-
stack.enter_context(patch('git_commit_msg_ai.editor.open', return_value=_make_open_mock(
|
|
68
|
+
stack.enter_context(patch('git_commit_msg_ai.editor.open', return_value=_make_open_mock(GENERIC_TEXT)))
|
|
64
69
|
stack.enter_context(patch('git_commit_msg_ai.editor.os.unlink'))
|
|
65
70
|
stack.enter_context(patch('git_commit_msg_ai.editor.get_default_editor', return_value=sentinel_editor))
|
|
66
71
|
stack.enter_context(patch.dict('os.environ', {}, clear=True))
|
|
67
72
|
|
|
68
|
-
open_in_editor(
|
|
73
|
+
open_in_editor(INITIAL_TEXT)
|
|
69
74
|
|
|
70
75
|
mock_run.assert_called_once_with([sentinel_editor, TEMP_FILE_PATH], check=True)
|
|
71
76
|
|
|
@@ -75,7 +80,7 @@ class TestOpenInEditor:
|
|
|
75
80
|
mock_unlink = stack.enter_context(patch('git_commit_msg_ai.editor.os.unlink'))
|
|
76
81
|
|
|
77
82
|
with pytest.raises(EditorError, match='temporary file'):
|
|
78
|
-
open_in_editor(
|
|
83
|
+
open_in_editor(INITIAL_TEXT)
|
|
79
84
|
|
|
80
85
|
mock_unlink.assert_not_called()
|
|
81
86
|
|
|
@@ -84,22 +89,22 @@ class TestOpenInEditor:
|
|
|
84
89
|
stack.enter_context(patch('git_commit_msg_ai.editor.tempfile.NamedTemporaryFile', return_value=_make_named_temporary_file_mock()))
|
|
85
90
|
stack.enter_context(patch('git_commit_msg_ai.editor.subprocess.run', side_effect=FileNotFoundError))
|
|
86
91
|
mock_unlink = stack.enter_context(patch('git_commit_msg_ai.editor.os.unlink'))
|
|
87
|
-
stack.enter_context(patch('git_commit_msg_ai.editor.os.environ.get', return_value=
|
|
92
|
+
stack.enter_context(patch('git_commit_msg_ai.editor.os.environ.get', return_value=VIM_EDITOR))
|
|
88
93
|
|
|
89
94
|
with pytest.raises(EditorError, match='was not found'):
|
|
90
|
-
open_in_editor(
|
|
95
|
+
open_in_editor(INITIAL_TEXT)
|
|
91
96
|
|
|
92
97
|
mock_unlink.assert_called_once_with(TEMP_FILE_PATH)
|
|
93
98
|
|
|
94
99
|
def test_raises_editor_error_when_editor_exits_with_error(self) -> None:
|
|
95
100
|
with ExitStack() as stack:
|
|
96
101
|
stack.enter_context(patch('git_commit_msg_ai.editor.tempfile.NamedTemporaryFile', return_value=_make_named_temporary_file_mock()))
|
|
97
|
-
stack.enter_context(patch('git_commit_msg_ai.editor.subprocess.run', side_effect=subprocess.CalledProcessError(1,
|
|
102
|
+
stack.enter_context(patch('git_commit_msg_ai.editor.subprocess.run', side_effect=subprocess.CalledProcessError(1, VIM_EDITOR)))
|
|
98
103
|
mock_unlink = stack.enter_context(patch('git_commit_msg_ai.editor.os.unlink'))
|
|
99
|
-
stack.enter_context(patch('git_commit_msg_ai.editor.os.environ.get', return_value=
|
|
104
|
+
stack.enter_context(patch('git_commit_msg_ai.editor.os.environ.get', return_value=VIM_EDITOR))
|
|
100
105
|
|
|
101
106
|
with pytest.raises(EditorError, match='exited with an error'):
|
|
102
|
-
open_in_editor(
|
|
107
|
+
open_in_editor(INITIAL_TEXT)
|
|
103
108
|
|
|
104
109
|
mock_unlink.assert_called_once_with(TEMP_FILE_PATH)
|
|
105
110
|
|
|
@@ -111,20 +116,22 @@ class TestOpenInEditor:
|
|
|
111
116
|
mock_unlink = stack.enter_context(patch('git_commit_msg_ai.editor.os.unlink'))
|
|
112
117
|
|
|
113
118
|
with pytest.raises(EditorError, match='Could not read'):
|
|
114
|
-
open_in_editor(
|
|
119
|
+
open_in_editor(INITIAL_TEXT)
|
|
115
120
|
|
|
116
121
|
mock_unlink.assert_called_once_with(TEMP_FILE_PATH)
|
|
117
122
|
|
|
118
123
|
|
|
119
124
|
class TestGetDefaultEditor:
|
|
120
125
|
def test_returns_notepad_on_windows(self) -> None:
|
|
121
|
-
with
|
|
126
|
+
with ExitStack() as stack:
|
|
127
|
+
stack.enter_context(patch('git_commit_msg_ai.editor.platform.system', return_value='Windows'))
|
|
122
128
|
result = get_default_editor()
|
|
123
129
|
|
|
124
130
|
assert result == 'notepad'
|
|
125
131
|
|
|
126
132
|
def test_returns_vi_on_non_windows(self) -> None:
|
|
127
|
-
with
|
|
133
|
+
with ExitStack() as stack:
|
|
134
|
+
stack.enter_context(patch('git_commit_msg_ai.editor.platform.system', return_value='Linux'))
|
|
128
135
|
result = get_default_editor()
|
|
129
136
|
|
|
130
137
|
assert result == 'vi'
|
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
import subprocess
|
|
2
|
+
from contextlib import ExitStack
|
|
2
3
|
from unittest.mock import MagicMock, patch
|
|
3
4
|
|
|
4
5
|
import pytest
|
|
5
6
|
|
|
6
7
|
from git_commit_msg_ai.exceptions import GitError
|
|
7
|
-
from git_commit_msg_ai.git_ops import commit, get_staged_diff
|
|
8
|
+
from git_commit_msg_ai.git_ops import COMMIT_SUBCOMMAND, GIT_COMMAND, MESSAGE_FLAG, commit, get_staged_diff
|
|
9
|
+
|
|
10
|
+
COMMIT_MESSAGE = 'my message'
|
|
8
11
|
|
|
9
12
|
|
|
10
13
|
class TestGetStagedDiff:
|
|
11
14
|
def test_returns_decoded_diff(self) -> None:
|
|
12
|
-
with
|
|
15
|
+
with ExitStack() as stack:
|
|
16
|
+
mock_check_output = stack.enter_context(patch('git_commit_msg_ai.git_ops.subprocess.check_output'))
|
|
13
17
|
mock_check_output.return_value = b'diff content'
|
|
14
18
|
|
|
15
19
|
result = get_staged_diff()
|
|
@@ -17,14 +21,16 @@ class TestGetStagedDiff:
|
|
|
17
21
|
assert result == 'diff content'
|
|
18
22
|
|
|
19
23
|
def test_raises_git_error_when_git_not_found(self) -> None:
|
|
20
|
-
with
|
|
24
|
+
with ExitStack() as stack:
|
|
25
|
+
mock_check_output = stack.enter_context(patch('git_commit_msg_ai.git_ops.subprocess.check_output'))
|
|
21
26
|
mock_check_output.side_effect = FileNotFoundError
|
|
22
27
|
|
|
23
28
|
with pytest.raises(GitError, match='not installed'):
|
|
24
29
|
get_staged_diff()
|
|
25
30
|
|
|
26
31
|
def test_raises_git_error_on_called_process_error(self) -> None:
|
|
27
|
-
with
|
|
32
|
+
with ExitStack() as stack:
|
|
33
|
+
mock_check_output = stack.enter_context(patch('git_commit_msg_ai.git_ops.subprocess.check_output'))
|
|
28
34
|
mock_check_output.side_effect = subprocess.CalledProcessError(1, 'git')
|
|
29
35
|
|
|
30
36
|
with pytest.raises(GitError, match='git repository'):
|
|
@@ -34,14 +40,16 @@ class TestGetStagedDiff:
|
|
|
34
40
|
mock_bytes = MagicMock()
|
|
35
41
|
mock_bytes.decode.side_effect = UnicodeDecodeError('utf-8', b'', 0, 1, 'reason')
|
|
36
42
|
|
|
37
|
-
with
|
|
43
|
+
with ExitStack() as stack:
|
|
44
|
+
mock_check_output = stack.enter_context(patch('git_commit_msg_ai.git_ops.subprocess.check_output'))
|
|
38
45
|
mock_check_output.return_value = mock_bytes
|
|
39
46
|
|
|
40
47
|
with pytest.raises(GitError, match='UTF-8'):
|
|
41
48
|
get_staged_diff()
|
|
42
49
|
|
|
43
50
|
def test_raises_git_error_when_diff_is_empty(self) -> None:
|
|
44
|
-
with
|
|
51
|
+
with ExitStack() as stack:
|
|
52
|
+
mock_check_output = stack.enter_context(patch('git_commit_msg_ai.git_ops.subprocess.check_output'))
|
|
45
53
|
mock_check_output.return_value = b''
|
|
46
54
|
|
|
47
55
|
with pytest.raises(GitError, match='No staged changes'):
|
|
@@ -50,21 +58,24 @@ class TestGetStagedDiff:
|
|
|
50
58
|
|
|
51
59
|
class TestCommit:
|
|
52
60
|
def test_calls_subprocess_run_with_correct_args(self) -> None:
|
|
53
|
-
with
|
|
54
|
-
|
|
61
|
+
with ExitStack() as stack:
|
|
62
|
+
mock_run = stack.enter_context(patch('git_commit_msg_ai.git_ops.subprocess.run'))
|
|
63
|
+
commit(COMMIT_MESSAGE)
|
|
55
64
|
|
|
56
|
-
mock_run.assert_called_once_with([
|
|
65
|
+
mock_run.assert_called_once_with([GIT_COMMAND, COMMIT_SUBCOMMAND, MESSAGE_FLAG, COMMIT_MESSAGE], check=True)
|
|
57
66
|
|
|
58
67
|
def test_raises_git_error_when_git_not_found(self) -> None:
|
|
59
|
-
with
|
|
68
|
+
with ExitStack() as stack:
|
|
69
|
+
mock_run = stack.enter_context(patch('git_commit_msg_ai.git_ops.subprocess.run'))
|
|
60
70
|
mock_run.side_effect = FileNotFoundError
|
|
61
71
|
|
|
62
72
|
with pytest.raises(GitError, match='not installed'):
|
|
63
|
-
commit(
|
|
73
|
+
commit(COMMIT_MESSAGE)
|
|
64
74
|
|
|
65
75
|
def test_raises_git_error_on_called_process_error(self) -> None:
|
|
66
|
-
with
|
|
67
|
-
mock_run
|
|
76
|
+
with ExitStack() as stack:
|
|
77
|
+
mock_run = stack.enter_context(patch('git_commit_msg_ai.git_ops.subprocess.run'))
|
|
78
|
+
mock_run.side_effect = subprocess.CalledProcessError(1, GIT_COMMAND)
|
|
68
79
|
|
|
69
80
|
with pytest.raises(GitError, match='git commit failed'):
|
|
70
|
-
commit(
|
|
81
|
+
commit(COMMIT_MESSAGE)
|
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
from unittest.mock import MagicMock, patch
|
|
2
|
-
|
|
3
|
-
import pytest
|
|
4
|
-
|
|
5
|
-
from git_commit_msg_ai.cli import main
|
|
6
|
-
from git_commit_msg_ai.exceptions import AIError, GitError
|
|
7
|
-
|
|
8
|
-
COMMIT_MESSAGE = 'feat: add feature'
|
|
9
|
-
EDITED_COMMIT_MESSAGE = 'edited commit message'
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class TestMain:
|
|
13
|
-
def _run_with_input(self, user_input: str, commit_message: str = COMMIT_MESSAGE) -> tuple[MagicMock, MagicMock, MagicMock]:
|
|
14
|
-
mock_git_ops = MagicMock()
|
|
15
|
-
mock_git_ops.get_staged_diff.return_value = 'diff content'
|
|
16
|
-
|
|
17
|
-
mock_ai_client = MagicMock()
|
|
18
|
-
mock_ai_client.generate_commit_message.return_value = commit_message
|
|
19
|
-
|
|
20
|
-
mock_editor = MagicMock()
|
|
21
|
-
mock_editor.open_in_editor.return_value = EDITED_COMMIT_MESSAGE
|
|
22
|
-
|
|
23
|
-
with patch('git_commit_msg_ai.cli.git_ops', mock_git_ops):
|
|
24
|
-
with patch('git_commit_msg_ai.cli.ai_client', mock_ai_client):
|
|
25
|
-
with patch('git_commit_msg_ai.cli.editor', mock_editor):
|
|
26
|
-
with patch('builtins.input', return_value=user_input):
|
|
27
|
-
main()
|
|
28
|
-
|
|
29
|
-
return mock_git_ops, mock_ai_client, mock_editor
|
|
30
|
-
|
|
31
|
-
def test_accept_calls_commit_with_generated_message(self) -> None:
|
|
32
|
-
mock_git_ops, _, _ = self._run_with_input('a', COMMIT_MESSAGE)
|
|
33
|
-
|
|
34
|
-
mock_git_ops.commit.assert_called_once_with(COMMIT_MESSAGE)
|
|
35
|
-
|
|
36
|
-
def test_edit_opens_editor_then_commits_edited_message(self) -> None:
|
|
37
|
-
mock_git_ops, _, mock_editor = self._run_with_input('e', COMMIT_MESSAGE)
|
|
38
|
-
|
|
39
|
-
mock_editor.open_in_editor.assert_called_once_with(COMMIT_MESSAGE)
|
|
40
|
-
mock_git_ops.commit.assert_called_once_with(EDITED_COMMIT_MESSAGE)
|
|
41
|
-
|
|
42
|
-
def test_reject_does_not_commit(self) -> None:
|
|
43
|
-
mock_git_ops, _, _ = self._run_with_input('r')
|
|
44
|
-
|
|
45
|
-
mock_git_ops.commit.assert_not_called()
|
|
46
|
-
|
|
47
|
-
def test_invalid_input_exits_with_code_1(self) -> None:
|
|
48
|
-
mock_git_ops = MagicMock()
|
|
49
|
-
mock_git_ops.get_staged_diff.return_value = 'diff content'
|
|
50
|
-
|
|
51
|
-
mock_ai_client = MagicMock()
|
|
52
|
-
mock_ai_client.generate_commit_message.return_value = 'msg'
|
|
53
|
-
|
|
54
|
-
with patch('git_commit_msg_ai.cli.git_ops', mock_git_ops):
|
|
55
|
-
with patch('git_commit_msg_ai.cli.ai_client', mock_ai_client):
|
|
56
|
-
with patch('git_commit_msg_ai.cli.editor'):
|
|
57
|
-
with patch('builtins.input', return_value='x'):
|
|
58
|
-
with pytest.raises(SystemExit) as exc_info:
|
|
59
|
-
main()
|
|
60
|
-
|
|
61
|
-
assert exc_info.value.code == 1
|
|
62
|
-
|
|
63
|
-
def test_git_error_from_get_staged_diff_exits_with_message(self, capsys: pytest.CaptureFixture[str]) -> None:
|
|
64
|
-
mock_git_ops = MagicMock()
|
|
65
|
-
mock_git_ops.get_staged_diff.side_effect = GitError('staged error')
|
|
66
|
-
|
|
67
|
-
with patch('git_commit_msg_ai.cli.git_ops', mock_git_ops):
|
|
68
|
-
with pytest.raises(SystemExit) as exc_info:
|
|
69
|
-
main()
|
|
70
|
-
|
|
71
|
-
assert exc_info.value.code == 1
|
|
72
|
-
assert 'staged error' in capsys.readouterr().out
|
|
73
|
-
|
|
74
|
-
def test_ai_error_from_generate_commit_message_exits_with_message(self, capsys: pytest.CaptureFixture[str]) -> None:
|
|
75
|
-
mock_git_ops = MagicMock()
|
|
76
|
-
mock_git_ops.get_staged_diff.return_value = 'diff content'
|
|
77
|
-
|
|
78
|
-
mock_ai_client = MagicMock()
|
|
79
|
-
mock_ai_client.generate_commit_message.side_effect = AIError('ai error')
|
|
80
|
-
|
|
81
|
-
with patch('git_commit_msg_ai.cli.git_ops', mock_git_ops):
|
|
82
|
-
with patch('git_commit_msg_ai.cli.ai_client', mock_ai_client):
|
|
83
|
-
with pytest.raises(SystemExit) as exc_info:
|
|
84
|
-
main()
|
|
85
|
-
|
|
86
|
-
assert exc_info.value.code == 1
|
|
87
|
-
assert 'ai error' in capsys.readouterr().out
|
|
88
|
-
|
|
89
|
-
def test_git_error_from_commit_exits_with_message(self, capsys: pytest.CaptureFixture[str]) -> None:
|
|
90
|
-
mock_git_ops = MagicMock()
|
|
91
|
-
mock_git_ops.get_staged_diff.return_value = 'diff content'
|
|
92
|
-
mock_git_ops.commit.side_effect = GitError('commit error')
|
|
93
|
-
|
|
94
|
-
mock_ai_client = MagicMock()
|
|
95
|
-
mock_ai_client.generate_commit_message.return_value = 'msg'
|
|
96
|
-
|
|
97
|
-
with patch('git_commit_msg_ai.cli.git_ops', mock_git_ops):
|
|
98
|
-
with patch('git_commit_msg_ai.cli.ai_client', mock_ai_client):
|
|
99
|
-
with patch('git_commit_msg_ai.cli.editor'):
|
|
100
|
-
with patch('builtins.input', return_value='a'):
|
|
101
|
-
with pytest.raises(SystemExit) as exc_info:
|
|
102
|
-
main()
|
|
103
|
-
|
|
104
|
-
assert exc_info.value.code == 1
|
|
105
|
-
assert 'commit error' in capsys.readouterr().out
|
|
106
|
-
|
|
107
|
-
def test_keyboard_interrupt_exits_with_aborted_message(self, capsys: pytest.CaptureFixture[str]) -> None:
|
|
108
|
-
mock_git_ops = MagicMock()
|
|
109
|
-
mock_git_ops.get_staged_diff.return_value = 'diff content'
|
|
110
|
-
|
|
111
|
-
mock_ai_client = MagicMock()
|
|
112
|
-
mock_ai_client.generate_commit_message.return_value = 'msg'
|
|
113
|
-
|
|
114
|
-
with patch('git_commit_msg_ai.cli.git_ops', mock_git_ops):
|
|
115
|
-
with patch('git_commit_msg_ai.cli.ai_client', mock_ai_client):
|
|
116
|
-
with patch('git_commit_msg_ai.cli.editor'):
|
|
117
|
-
with patch('builtins.input', side_effect=KeyboardInterrupt):
|
|
118
|
-
with pytest.raises(SystemExit) as exc_info:
|
|
119
|
-
main()
|
|
120
|
-
|
|
121
|
-
assert exc_info.value.code == 1
|
|
122
|
-
assert 'Aborted.' in capsys.readouterr().out
|
|
123
|
-
|
|
124
|
-
def test_eof_error_exits_with_aborted_message(self, capsys: pytest.CaptureFixture[str]) -> None:
|
|
125
|
-
mock_git_ops = MagicMock()
|
|
126
|
-
mock_git_ops.get_staged_diff.return_value = 'diff content'
|
|
127
|
-
|
|
128
|
-
mock_ai_client = MagicMock()
|
|
129
|
-
mock_ai_client.generate_commit_message.return_value = 'msg'
|
|
130
|
-
|
|
131
|
-
with patch('git_commit_msg_ai.cli.git_ops', mock_git_ops):
|
|
132
|
-
with patch('git_commit_msg_ai.cli.ai_client', mock_ai_client):
|
|
133
|
-
with patch('git_commit_msg_ai.cli.editor'):
|
|
134
|
-
with patch('builtins.input', side_effect=EOFError):
|
|
135
|
-
with pytest.raises(SystemExit) as exc_info:
|
|
136
|
-
main()
|
|
137
|
-
|
|
138
|
-
assert exc_info.value.code == 1
|
|
139
|
-
assert 'Aborted.' in capsys.readouterr().out
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{git_commit_msg_ai-1.4.0 → git_commit_msg_ai-1.4.2}/git_commit_msg_ai.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{git_commit_msg_ai-1.4.0 → git_commit_msg_ai-1.4.2}/git_commit_msg_ai.egg-info/entry_points.txt
RENAMED
|
File without changes
|
{git_commit_msg_ai-1.4.0 → git_commit_msg_ai-1.4.2}/git_commit_msg_ai.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|