Skip to content

Commit b1e31cc

Browse files
authored
test: add unit tests case (#38)
* test(commit_commands): add unit tests for commit handling and parser setup * refactor(tests): update test structure and enable pytest for testing framework * refactor(tests): remove redundant assertion in commit command tests * refactor(tests): remove unnecessary mock assertions in commit command tests * test(web_config): add tests for LLM and Jira web server configuration * fix(config_commands): improve error handling for LLM config file reading * refactor(config_commands): move import of recursive_search_git_folder to utils module * fix(tests): update patch path for recursive_search_git_folder in config_commands tests * test(doc_commands): add unit tests for document generation commands and error handling
1 parent ecc3c7e commit b1e31cc

9 files changed

Lines changed: 829 additions & 4 deletions

File tree

.vscode/settings.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"python.testing.pytestEnabled": true,
3+
"python.testing.unittestEnabled": false,
4+
"python.testing.nosetestsEnabled": false,
5+
"python.testing.pytestArgs": [
6+
"tests"
7+
],
8+
"python.defaultInterpreterPath": "${workspaceFolder}/.venv/bin/python",
9+
"python.testing.autoTestDiscoverOnSaveEnabled": true,
10+
"python.testing.unittestArgs": [
11+
"-v",
12+
"-s",
13+
"./tests",
14+
"-p",
15+
"test_*.py"
16+
]
17+
}

penify_hook/commands/config_commands.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from pathlib import Path
99
from threading import Thread
1010
import logging
11+
from penify_hook.utils import recursive_search_git_folder
1112

1213

1314
def get_penify_config() -> Path:
@@ -16,8 +17,6 @@ def get_penify_config() -> Path:
1617
This function searches for the .penify file in the current directory
1718
and its parent directories until it finds it or reaches the home directory.
1819
"""
19-
from penify_hook.utils import recursive_search_git_folder
20-
2120
current_dir = os.getcwd()
2221
home_dir = recursive_search_git_folder(current_dir)
2322

@@ -55,8 +54,9 @@ def save_llm_config(model, api_base, api_key):
5554
try:
5655
with open(penify_file, 'r') as f:
5756
config = json.load(f)
58-
except json.JSONDecodeError:
59-
pass
57+
except (json.JSONDecodeError, IOError, OSError) as e:
58+
print(f"Error reading configuration file: {str(e)}")
59+
# Continue with empty config
6060

6161
# Update or add LLM configuration
6262
config['llm'] = {

pytest.ini

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
[pytest]
2+
python_files = test_*.py
3+
python_classes = Test*
4+
python_functions = test_*
5+
testpaths = tests

tests/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Empty file to make tests directory a proper Python package

tests/conftest.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import pytest
2+
import os
3+
import sys
4+
5+
# Add project root to sys.path for imports to work
6+
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
7+
8+
# Common fixtures can be added here

tests/test_commit_commands.py

Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
import os
2+
import sys
3+
import pytest
4+
from unittest.mock import patch, MagicMock, call
5+
6+
from penify_hook.commands.commit_commands import commit_code, setup_commit_parser, handle_commit
7+
8+
class TestCommitCommands:
9+
10+
@pytest.fixture
11+
def mock_api_client(self):
12+
with patch('penify_hook.api_client.APIClient', create=True) as mock:
13+
api_client_instance = MagicMock()
14+
mock.return_value = api_client_instance
15+
yield mock, api_client_instance
16+
17+
@pytest.fixture
18+
def mock_llm_client(self):
19+
with patch('penify_hook.llm_client.LLMClient', create=True) as mock:
20+
llm_client_instance = MagicMock()
21+
mock.return_value = llm_client_instance
22+
yield mock, llm_client_instance
23+
24+
@pytest.fixture
25+
def mock_jira_client(self):
26+
with patch('penify_hook.jira_client.JiraClient', create=True) as mock:
27+
jira_instance = MagicMock()
28+
jira_instance.is_connected.return_value = True
29+
mock.return_value = jira_instance
30+
yield mock, jira_instance
31+
32+
@pytest.fixture
33+
def mock_commit_doc_gen(self):
34+
with patch('penify_hook.commit_analyzer.CommitDocGenHook', create=True) as mock:
35+
doc_gen_instance = MagicMock()
36+
mock.return_value = doc_gen_instance
37+
yield mock, doc_gen_instance
38+
39+
@pytest.fixture
40+
def mock_git_folder_search(self):
41+
with patch('penify_hook.utils.recursive_search_git_folder', create=True) as mock:
42+
mock.return_value = '/mock/git/folder'
43+
yield mock
44+
45+
@pytest.fixture
46+
def mock_print_functions(self):
47+
with patch('penify_hook.ui_utils.print_info', create=True) as mock_info, \
48+
patch('penify_hook.ui_utils.print_warning', create=True) as mock_warning, \
49+
patch('penify_hook.ui_utils.print_error', create=True) as mock_error:
50+
yield mock_info, mock_warning, mock_error
51+
52+
@patch('penify_hook.api_client.APIClient', create=True)
53+
@patch('penify_hook.llm_client.LLMClient', create=True)
54+
@patch('penify_hook.commit_analyzer.CommitDocGenHook', create=True)
55+
@patch('penify_hook.utils.recursive_search_git_folder', create=True)
56+
@patch('penify_hook.ui_utils.print_info', create=True)
57+
@patch('penify_hook.ui_utils.print_warning', create=True)
58+
@patch('penify_hook.ui_utils.print_error', create=True)
59+
def test_commit_code_with_llm_client(self, mock_error, mock_warning, mock_info,
60+
mock_git_folder_search, mock_doc_gen,
61+
mock_llm_client, mock_api_client):
62+
# Setup mocks
63+
api_instance = MagicMock()
64+
mock_api_client.return_value = api_instance
65+
66+
llm_instance = MagicMock()
67+
mock_llm_client.return_value = llm_instance
68+
69+
doc_gen_instance = MagicMock()
70+
mock_doc_gen.return_value = doc_gen_instance
71+
72+
mock_git_folder_search.return_value = '/mock/git/folder'
73+
74+
# Call function with LLM parameters
75+
commit_code(
76+
api_url="http://api.example.com",
77+
token="api-token",
78+
message="test commit",
79+
open_terminal=False,
80+
generate_description=True,
81+
llm_model="gpt-4",
82+
llm_api_base="http://llm-api.example.com",
83+
llm_api_key="llm-api-key"
84+
)
85+
86+
# Verify calls
87+
mock_api_client.assert_called_once_with("http://api.example.com", "api-token")
88+
mock_llm_client.assert_called_once_with(
89+
model="gpt-4",
90+
api_base="http://llm-api.example.com",
91+
api_key="llm-api-key"
92+
)
93+
mock_doc_gen.assert_called_once_with('/mock/git/folder', api_instance, llm_instance, None)
94+
doc_gen_instance.run.assert_called_once_with("test commit", False, True)
95+
96+
@patch('penify_hook.api_client.APIClient', create=True)
97+
@patch('penify_hook.llm_client.LLMClient', create=True)
98+
@patch('penify_hook.jira_client.JiraClient', create=True)
99+
@patch('penify_hook.commit_analyzer.CommitDocGenHook', create=True)
100+
@patch('penify_hook.utils.recursive_search_git_folder', create=True)
101+
@patch('penify_hook.ui_utils.print_info', create=True)
102+
@patch('penify_hook.ui_utils.print_warning', create=True)
103+
@patch('penify_hook.ui_utils.print_error', create=True)
104+
def test_commit_code_with_jira_client(self, mock_error, mock_warning, mock_info,
105+
mock_git_folder_search, mock_doc_gen,
106+
mock_jira_client, mock_llm_client, mock_api_client):
107+
# Setup mocks
108+
api_instance = MagicMock()
109+
mock_api_client.return_value = api_instance
110+
111+
llm_instance = MagicMock()
112+
mock_llm_client.return_value = llm_instance
113+
114+
jira_instance = MagicMock()
115+
jira_instance.is_connected.return_value = True
116+
mock_jira_client.return_value = jira_instance
117+
118+
doc_gen_instance = MagicMock()
119+
mock_doc_gen.return_value = doc_gen_instance
120+
121+
mock_git_folder_search.return_value = '/mock/git/folder'
122+
123+
# Call function with JIRA parameters
124+
commit_code(
125+
api_url="http://api.example.com",
126+
token="api-token",
127+
message="test commit",
128+
open_terminal=False,
129+
generate_description=True,
130+
llm_model="gpt-4",
131+
llm_api_base="http://llm-api.example.com",
132+
llm_api_key="llm-api-key",
133+
jira_url="https://jira.example.com",
134+
jira_user="jira-user",
135+
jira_api_token="jira-token"
136+
)
137+
138+
# Verify calls
139+
mock_jira_client.assert_called_once_with(
140+
jira_url="https://jira.example.com",
141+
jira_user="jira-user",
142+
jira_api_token="jira-token"
143+
)
144+
mock_doc_gen.assert_called_once_with('/mock/git/folder', api_instance, llm_instance, jira_instance)
145+
146+
@patch('penify_hook.api_client.APIClient', create=True)
147+
@patch('penify_hook.jira_client.JiraClient', create=True)
148+
@patch('penify_hook.commit_analyzer.CommitDocGenHook', create=True)
149+
@patch('penify_hook.utils.recursive_search_git_folder', create=True)
150+
@patch('penify_hook.ui_utils.print_info', create=True)
151+
@patch('penify_hook.ui_utils.print_warning', create=True)
152+
@patch('penify_hook.ui_utils.print_error', create=True)
153+
def test_commit_code_with_jira_connection_failure(self, mock_error, mock_warning, mock_info,
154+
mock_git_folder_search, mock_doc_gen,
155+
mock_jira_client, mock_api_client):
156+
# Setup mocks
157+
api_instance = MagicMock()
158+
mock_api_client.return_value = api_instance
159+
160+
jira_instance = MagicMock()
161+
jira_instance.is_connected.return_value = False
162+
mock_jira_client.return_value = jira_instance
163+
164+
doc_gen_instance = MagicMock()
165+
mock_doc_gen.return_value = doc_gen_instance
166+
167+
mock_git_folder_search.return_value = '/mock/git/folder'
168+
169+
# Call function
170+
commit_code(
171+
api_url="http://api.example.com",
172+
token="api-token",
173+
message="test commit",
174+
open_terminal=False,
175+
generate_description=True,
176+
llm_model=None,
177+
jira_url="https://jira.example.com",
178+
jira_user="jira-user",
179+
jira_api_token="jira-token"
180+
)
181+
182+
# Verify JIRA warning
183+
mock_doc_gen.assert_called_once_with('/mock/git/folder', api_instance, None, None)
184+
185+
@patch('penify_hook.api_client.APIClient', create=True)
186+
@patch('penify_hook.commit_analyzer.CommitDocGenHook', create=True)
187+
@patch('penify_hook.utils.recursive_search_git_folder', create=True)
188+
@patch('sys.exit')
189+
@patch('builtins.print')
190+
def test_commit_code_error_handling(self, mock_print, mock_exit,
191+
mock_git_folder_search, mock_doc_gen, mock_api_client):
192+
# Setup mocks
193+
mock_doc_gen.side_effect = Exception("Test error")
194+
mock_git_folder_search.return_value = '/mock/git/folder'
195+
196+
# Call function
197+
commit_code(
198+
api_url="http://api.example.com",
199+
token="api-token",
200+
message="test commit",
201+
open_terminal=False,
202+
generate_description=True
203+
)
204+
205+
mock_print.assert_called_once_with("Error: Test error")
206+
mock_exit.assert_called_once_with(1)
207+
208+
def test_setup_commit_parser(self):
209+
parser = MagicMock()
210+
setup_commit_parser(parser)
211+
212+
# Verify parser configuration
213+
assert parser.add_argument.call_count == 3
214+
parser.add_argument.assert_any_call("-m", "--message", required=False, help="Commit with contextual commit message.", default="N/A")
215+
parser.add_argument.assert_any_call("-e", "--terminal", action="store_true", help="Open edit terminal before committing.")
216+
parser.add_argument.assert_any_call("-d", "--description", action="store_false", help="It will generate commit message with title and description.", default=False)
217+
218+
@patch('penify_hook.commands.commit_commands.get_token')
219+
@patch('penify_hook.commands.commit_commands.get_jira_config')
220+
@patch('penify_hook.commands.commit_commands.get_llm_config')
221+
@patch('penify_hook.commands.commit_commands.commit_code')
222+
@patch('penify_hook.commands.commit_commands.print_info')
223+
@patch('penify_hook.constants.API_URL', "http://api.example.com")
224+
def test_handle_commit(self, mock_print_info, mock_commit_code, mock_get_token,
225+
mock_get_llm_config, mock_get_jira_config):
226+
# Setup mocks
227+
mock_get_llm_config.return_value = {
228+
'model': 'test-model',
229+
'api_base': 'http://llm-api.example.com',
230+
'api_key': 'llm-key'
231+
}
232+
mock_get_token.return_value = 'api-token'
233+
mock_get_jira_config.return_value = {
234+
'url': 'https://jira.example.com',
235+
'username': 'jira-user',
236+
'api_token': 'jira-token'
237+
}
238+
239+
# Create args
240+
args = MagicMock()
241+
args.message = "test commit"
242+
args.terminal = True
243+
args.description = True
244+
245+
# Call function
246+
handle_commit(args)
247+
248+
# Verify
249+
mock_print_info.assert_called_with("Generate Commit Description: True")
250+
mock_commit_code.assert_called_once_with(
251+
"http://api.example.com", 'api-token', "test commit", True, True,
252+
'test-model', 'http://llm-api.example.com', 'llm-key',
253+
'https://jira.example.com', 'jira-user', 'jira-token'
254+
)

0 commit comments

Comments
 (0)