fancygit 1.0.0__py3-none-any.whl
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.
- fancygit-1.0.0.dist-info/METADATA +169 -0
- fancygit-1.0.0.dist-info/RECORD +29 -0
- fancygit-1.0.0.dist-info/WHEEL +5 -0
- fancygit-1.0.0.dist-info/entry_points.txt +2 -0
- fancygit-1.0.0.dist-info/top_level.txt +2 -0
- src/__init__.py +1 -0
- src/colors.py +260 -0
- src/git_error.py +55 -0
- src/git_error_parser.py +43 -0
- src/git_insights.py +304 -0
- src/git_runner.py +20 -0
- src/loading_animation.py +167 -0
- src/merge_conflict.py +27 -0
- src/mermaid_export.py +430 -0
- src/ollama_client.py +142 -0
- src/output_colorizer.py +358 -0
- src/repo_state.py +29 -0
- src/utils.py +0 -0
- tests/README.md +186 -0
- tests/__init__.py +0 -0
- tests/conftest.py +61 -0
- tests/test_conflict_parser_integration.py +65 -0
- tests/test_fancygit_advanced.py +504 -0
- tests/test_fancygit_commands.py +507 -0
- tests/test_fancygit_integration.py +158 -0
- tests/test_fancygit_workflows.py +441 -0
- tests/test_git_error.py +74 -0
- tests/test_git_error_parser.py +129 -0
- tests/test_git_runner.py +118 -0
|
@@ -0,0 +1,507 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from unittest.mock import patch, MagicMock, call
|
|
3
|
+
import sys
|
|
4
|
+
import os
|
|
5
|
+
|
|
6
|
+
# Add project root to path for imports
|
|
7
|
+
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
|
|
8
|
+
|
|
9
|
+
from fancygit import FancyGit
|
|
10
|
+
|
|
11
|
+
@pytest.mark.unit
|
|
12
|
+
class TestFancyGitCommands:
|
|
13
|
+
"""Test cases for individual FancyGit commands"""
|
|
14
|
+
|
|
15
|
+
def setup_method(self):
|
|
16
|
+
"""Setup method called before each test"""
|
|
17
|
+
# Mock all the loading methods to avoid file dependencies
|
|
18
|
+
with patch.object(FancyGit, '_load_commands', return_value=[
|
|
19
|
+
'add', 'commit', 'push', 'pull', 'status', 'branch', 'checkout',
|
|
20
|
+
'merge', 'rebase', 'reset', 'log', 'diff', 'stash', 'rm', 'mv',
|
|
21
|
+
'welcome', 'confirmation', 'ai', 'colors', 'insights', 'visualize'
|
|
22
|
+
]):
|
|
23
|
+
with patch.object(FancyGit, '_load_confirmation_state', return_value=False):
|
|
24
|
+
with patch.object(FancyGit, '_load_ai_analysis_state', return_value=False):
|
|
25
|
+
with patch.object(FancyGit, '_load_output_coloring_state', return_value=False):
|
|
26
|
+
with patch.object(FancyGit, '_load_animation_type', return_value='dots'):
|
|
27
|
+
self.fancy_git = FancyGit()
|
|
28
|
+
|
|
29
|
+
@patch('src.git_runner.GitRunner.run_git_command')
|
|
30
|
+
def test_add_command_success(self, mock_run_git):
|
|
31
|
+
"""Test git add command success"""
|
|
32
|
+
mock_run_git.return_value = (0, "", "")
|
|
33
|
+
|
|
34
|
+
result = self.fancy_git.execute_command('add', 'test.txt')
|
|
35
|
+
|
|
36
|
+
assert result is True
|
|
37
|
+
mock_run_git.assert_called_once_with(['add', 'test.txt'])
|
|
38
|
+
|
|
39
|
+
@patch('src.git_runner.GitRunner.run_git_command')
|
|
40
|
+
def test_add_command_error(self, mock_run_git):
|
|
41
|
+
"""Test git add command with error"""
|
|
42
|
+
mock_run_git.return_value = (1, "", "error: pathspec 'nonexistent.txt' did not match any files")
|
|
43
|
+
|
|
44
|
+
result = self.fancy_git.execute_command('add', 'nonexistent.txt')
|
|
45
|
+
|
|
46
|
+
assert result is False
|
|
47
|
+
mock_run_git.assert_called_once_with(['add', 'nonexistent.txt'])
|
|
48
|
+
|
|
49
|
+
@patch('src.git_runner.GitRunner.run_git_command')
|
|
50
|
+
def test_commit_command_success(self, mock_run_git):
|
|
51
|
+
"""Test git commit command success"""
|
|
52
|
+
mock_run_git.return_value = (0, "[main 1234567] Test commit\n 1 file changed, 1 insertion(+)", "")
|
|
53
|
+
|
|
54
|
+
result = self.fancy_git.execute_command('commit', '-m', 'Test commit')
|
|
55
|
+
|
|
56
|
+
assert result is True
|
|
57
|
+
mock_run_git.assert_called_once_with(['commit', '-m', 'Test commit'])
|
|
58
|
+
|
|
59
|
+
@patch('src.git_runner.GitRunner.run_git_command')
|
|
60
|
+
def test_push_command_success(self, mock_run_git):
|
|
61
|
+
"""Test git push command success"""
|
|
62
|
+
mock_run_git.return_value = (0, "Enumerating objects: 3, done.\nTo github.com:user/repo.git\n 1234567..abcdefg main -> main", "")
|
|
63
|
+
|
|
64
|
+
result = self.fancy_git.execute_command('push', 'origin', 'main')
|
|
65
|
+
|
|
66
|
+
assert result is True
|
|
67
|
+
mock_run_git.assert_called_once_with(['push', 'origin', 'main'])
|
|
68
|
+
|
|
69
|
+
@patch('src.git_runner.GitRunner.run_git_command')
|
|
70
|
+
def test_pull_command_success(self, mock_run_git):
|
|
71
|
+
"""Test git pull command success"""
|
|
72
|
+
mock_run_git.return_value = (0, "Already up to date.", "")
|
|
73
|
+
|
|
74
|
+
result = self.fancy_git.execute_command('pull', 'origin', 'main')
|
|
75
|
+
|
|
76
|
+
assert result is True
|
|
77
|
+
mock_run_git.assert_called_once_with(['pull', 'origin', 'main'])
|
|
78
|
+
|
|
79
|
+
@patch('src.git_runner.GitRunner.run_git_command')
|
|
80
|
+
def test_status_command_success(self, mock_run_git):
|
|
81
|
+
"""Test git status command success"""
|
|
82
|
+
mock_run_git.return_value = (0, "On branch main\nnothing to commit, working tree clean", "")
|
|
83
|
+
|
|
84
|
+
result = self.fancy_git.execute_command('status')
|
|
85
|
+
|
|
86
|
+
assert result is True
|
|
87
|
+
mock_run_git.assert_called_once_with(['status'])
|
|
88
|
+
|
|
89
|
+
@patch('src.git_runner.GitRunner.run_git_command')
|
|
90
|
+
def test_branch_command_success(self, mock_run_git):
|
|
91
|
+
"""Test git branch command success"""
|
|
92
|
+
mock_run_git.return_value = (0, "* main\n feature-branch", "")
|
|
93
|
+
|
|
94
|
+
result = self.fancy_git.execute_command('branch')
|
|
95
|
+
|
|
96
|
+
assert result is True
|
|
97
|
+
mock_run_git.assert_called_once_with(['branch'])
|
|
98
|
+
|
|
99
|
+
@patch('src.git_runner.GitRunner.run_git_command')
|
|
100
|
+
def test_branch_create_command(self, mock_run_git):
|
|
101
|
+
"""Test git branch creation command"""
|
|
102
|
+
mock_run_git.return_value = (0, "", "")
|
|
103
|
+
|
|
104
|
+
result = self.fancy_git.execute_command('branch', 'new-feature')
|
|
105
|
+
|
|
106
|
+
assert result is True
|
|
107
|
+
mock_run_git.assert_called_once_with(['branch', 'new-feature'])
|
|
108
|
+
|
|
109
|
+
@patch('src.git_runner.GitRunner.run_git_command')
|
|
110
|
+
def test_checkout_command_success(self, mock_run_git):
|
|
111
|
+
"""Test git checkout command success"""
|
|
112
|
+
mock_run_git.return_value = (0, "Switched to branch 'feature-branch'", "")
|
|
113
|
+
|
|
114
|
+
result = self.fancy_git.execute_command('checkout', 'feature-branch')
|
|
115
|
+
|
|
116
|
+
assert result is True
|
|
117
|
+
mock_run_git.assert_called_once_with(['checkout', 'feature-branch'])
|
|
118
|
+
|
|
119
|
+
@patch('src.git_runner.GitRunner.run_git_command')
|
|
120
|
+
def test_merge_command_success(self, mock_run_git):
|
|
121
|
+
"""Test git merge command success"""
|
|
122
|
+
mock_run_git.return_value = (0, "Merge made by the 'recursive' strategy.\n 1 file changed, 1 insertion(+)", "")
|
|
123
|
+
|
|
124
|
+
result = self.fancy_git.execute_command('merge', 'feature-branch')
|
|
125
|
+
|
|
126
|
+
assert result is True
|
|
127
|
+
mock_run_git.assert_called_once_with(['merge', 'feature-branch'])
|
|
128
|
+
|
|
129
|
+
@patch('src.git_runner.GitRunner.run_git_command')
|
|
130
|
+
def test_merge_command_conflict(self, mock_run_git):
|
|
131
|
+
"""Test git merge command with conflicts"""
|
|
132
|
+
mock_run_git.return_value = (1, "", "error: Merge conflict in README.md\nAutomatic merge failed; fix conflicts and then commit the result.")
|
|
133
|
+
|
|
134
|
+
result = self.fancy_git.execute_command('merge', 'feature-branch')
|
|
135
|
+
|
|
136
|
+
assert result is False
|
|
137
|
+
mock_run_git.assert_called_once_with(['merge', 'feature-branch'])
|
|
138
|
+
|
|
139
|
+
@patch('src.git_runner.GitRunner.run_git_command')
|
|
140
|
+
def test_rebase_command_success(self, mock_run_git):
|
|
141
|
+
"""Test git rebase command success"""
|
|
142
|
+
mock_run_git.return_value = (0, "Successfully rebased and updated refs/heads/main.", "")
|
|
143
|
+
|
|
144
|
+
result = self.fancy_git.execute_command('rebase', 'origin/main')
|
|
145
|
+
|
|
146
|
+
assert result is True
|
|
147
|
+
mock_run_git.assert_called_once_with(['rebase', 'origin/main'])
|
|
148
|
+
|
|
149
|
+
@patch('src.git_runner.GitRunner.run_git_command')
|
|
150
|
+
def test_reset_command_success(self, mock_run_git):
|
|
151
|
+
"""Test git reset command success"""
|
|
152
|
+
mock_run_git.return_value = (0, "Unstaged changes after reset:\nM\ttest.py", "")
|
|
153
|
+
|
|
154
|
+
result = self.fancy_git.execute_command('reset', 'HEAD~1')
|
|
155
|
+
|
|
156
|
+
assert result is True
|
|
157
|
+
mock_run_git.assert_called_once_with(['reset', 'HEAD~1'])
|
|
158
|
+
|
|
159
|
+
@patch('src.git_runner.GitRunner.run_git_command')
|
|
160
|
+
def test_log_command_success(self, mock_run_git):
|
|
161
|
+
"""Test git log command success"""
|
|
162
|
+
mock_run_git.return_value = (0, "commit 1234567890abcdef\nAuthor: Test User <test@example.com>\nDate: Mon Jan 1 12:00:00 2024\n\n Test commit message", "")
|
|
163
|
+
|
|
164
|
+
result = self.fancy_git.execute_command('log', '--oneline', '-5')
|
|
165
|
+
|
|
166
|
+
assert result is True
|
|
167
|
+
mock_run_git.assert_called_once_with(['log', '--oneline', '-5'])
|
|
168
|
+
|
|
169
|
+
@patch('src.git_runner.GitRunner.run_git_command')
|
|
170
|
+
def test_diff_command_success(self, mock_run_git):
|
|
171
|
+
"""Test git diff command success"""
|
|
172
|
+
mock_run_git.return_value = (0, "diff --git a/test.py b/test.py\nindex 1234567..abcdefg 100644\n--- a/test.py\n+++ b/test.py\n@@ -1,3 +1,4 @@\n+new line", "")
|
|
173
|
+
|
|
174
|
+
result = self.fancy_git.execute_command('diff', 'test.py')
|
|
175
|
+
|
|
176
|
+
assert result is True
|
|
177
|
+
mock_run_git.assert_called_once_with(['diff', 'test.py'])
|
|
178
|
+
|
|
179
|
+
@patch('src.git_runner.GitRunner.run_git_command')
|
|
180
|
+
def test_stash_command_success(self, mock_run_git):
|
|
181
|
+
"""Test git stash command success"""
|
|
182
|
+
mock_run_git.return_value = (0, "Saved working directory and index state WIP on main: 1234567 Test commit", "")
|
|
183
|
+
|
|
184
|
+
result = self.fancy_git.execute_command('stash', 'push', '-m', 'WIP')
|
|
185
|
+
|
|
186
|
+
assert result is True
|
|
187
|
+
mock_run_git.assert_called_once_with(['stash', 'push', '-m', 'WIP'])
|
|
188
|
+
|
|
189
|
+
@patch('src.git_runner.GitRunner.run_git_command')
|
|
190
|
+
def test_rm_command_success(self, mock_run_git):
|
|
191
|
+
"""Test git rm command success"""
|
|
192
|
+
mock_run_git.return_value = (0, "rm 'test.txt'", "")
|
|
193
|
+
|
|
194
|
+
result = self.fancy_git.execute_command('rm', 'test.txt')
|
|
195
|
+
|
|
196
|
+
assert result is True
|
|
197
|
+
mock_run_git.assert_called_once_with(['rm', 'test.txt'])
|
|
198
|
+
|
|
199
|
+
@patch('src.git_runner.GitRunner.run_git_command')
|
|
200
|
+
def test_mv_command_success(self, mock_run_git):
|
|
201
|
+
"""Test git mv command success"""
|
|
202
|
+
mock_run_git.return_value = (0, "Renaming 'old.txt' to 'new.txt'", "")
|
|
203
|
+
|
|
204
|
+
result = self.fancy_git.execute_command('mv', 'old.txt', 'new.txt')
|
|
205
|
+
|
|
206
|
+
assert result is True
|
|
207
|
+
mock_run_git.assert_called_once_with(['mv', 'old.txt', 'new.txt'])
|
|
208
|
+
|
|
209
|
+
def test_unknown_command(self):
|
|
210
|
+
"""Test handling of unknown commands"""
|
|
211
|
+
result = self.fancy_git.execute_command('unknown_command')
|
|
212
|
+
|
|
213
|
+
assert result is False
|
|
214
|
+
|
|
215
|
+
@patch('src.git_runner.GitRunner.run_git_command')
|
|
216
|
+
def test_command_with_no_args(self, mock_run_git):
|
|
217
|
+
"""Test command execution with no arguments"""
|
|
218
|
+
mock_run_git.return_value = (0, "Git help output", "")
|
|
219
|
+
|
|
220
|
+
result = self.fancy_git.execute_command('status')
|
|
221
|
+
|
|
222
|
+
assert result is True
|
|
223
|
+
mock_run_git.assert_called_once_with(['status'])
|
|
224
|
+
|
|
225
|
+
@patch('src.git_runner.GitRunner.run_git_command')
|
|
226
|
+
def test_command_with_multiple_args(self, mock_run_git):
|
|
227
|
+
"""Test command execution with multiple arguments"""
|
|
228
|
+
mock_run_git.return_value = (0, "Branch 'test-branch' set up to track remote branch 'test' from 'origin'.", "")
|
|
229
|
+
|
|
230
|
+
result = self.fancy_git.execute_command('push', '-u', 'origin', 'test-branch')
|
|
231
|
+
|
|
232
|
+
assert result is True
|
|
233
|
+
mock_run_git.assert_called_once_with(['push', '-u', 'origin', 'test-branch'])
|
|
234
|
+
|
|
235
|
+
@patch('src.git_runner.GitRunner.run_git_command')
|
|
236
|
+
def test_command_with_complex_args(self, mock_run_git):
|
|
237
|
+
"""Test command execution with complex arguments containing quotes and special characters"""
|
|
238
|
+
mock_run_git.return_value = (0, "Commit successful", "")
|
|
239
|
+
|
|
240
|
+
result = self.fancy_git.execute_command('commit', '-m', 'Fix bug #123: Update configuration file')
|
|
241
|
+
|
|
242
|
+
assert result is True
|
|
243
|
+
mock_run_git.assert_called_once_with(['commit', '-m', 'Fix bug #123: Update configuration file'])
|
|
244
|
+
|
|
245
|
+
@pytest.mark.unit
|
|
246
|
+
class TestFancyGitSpecialCommands:
|
|
247
|
+
"""Test cases for FancyGit special commands"""
|
|
248
|
+
|
|
249
|
+
def setup_method(self):
|
|
250
|
+
"""Setup method called before each test"""
|
|
251
|
+
with patch.object(FancyGit, '_load_commands', return_value=[
|
|
252
|
+
'welcome', 'confirmation', 'ai', 'colors', 'insights', 'visualize'
|
|
253
|
+
]):
|
|
254
|
+
with patch.object(FancyGit, '_load_confirmation_state', return_value=True):
|
|
255
|
+
with patch.object(FancyGit, '_load_ai_analysis_state', return_value=False):
|
|
256
|
+
with patch.object(FancyGit, '_load_output_coloring_state', return_value=False):
|
|
257
|
+
with patch.object(FancyGit, '_load_animation_type', return_value='dots'):
|
|
258
|
+
self.fancy_git = FancyGit()
|
|
259
|
+
|
|
260
|
+
def test_welcome_command(self):
|
|
261
|
+
"""Test welcome command"""
|
|
262
|
+
result = self.fancy_git.execute_command('welcome')
|
|
263
|
+
|
|
264
|
+
assert result is True
|
|
265
|
+
# The welcome command should execute without errors
|
|
266
|
+
# We can't easily mock the welcome function due to import issues,
|
|
267
|
+
# but we can verify it returns True (successful execution)
|
|
268
|
+
|
|
269
|
+
def test_confirmation_command_toggle(self):
|
|
270
|
+
"""Test confirmation command toggle"""
|
|
271
|
+
initial_state = self.fancy_git.confirmation_enabled
|
|
272
|
+
|
|
273
|
+
result = self.fancy_git.execute_command('confirmation')
|
|
274
|
+
|
|
275
|
+
assert result is not None # The method returns the current state (bool)
|
|
276
|
+
assert self.fancy_git.confirmation_enabled != initial_state
|
|
277
|
+
|
|
278
|
+
def test_confirmation_command_enable(self):
|
|
279
|
+
"""Test confirmation command enable"""
|
|
280
|
+
result = self.fancy_git.execute_command('confirmation', 'on')
|
|
281
|
+
|
|
282
|
+
assert result is True
|
|
283
|
+
assert self.fancy_git.confirmation_enabled is True
|
|
284
|
+
|
|
285
|
+
def test_confirmation_command_disable(self):
|
|
286
|
+
"""Test confirmation command disable"""
|
|
287
|
+
result = self.fancy_git.execute_command('confirmation', 'off')
|
|
288
|
+
|
|
289
|
+
assert isinstance(result, bool) # The method returns the current state
|
|
290
|
+
assert self.fancy_git.confirmation_enabled is False
|
|
291
|
+
|
|
292
|
+
def test_confirmation_command_status(self):
|
|
293
|
+
"""Test confirmation command status"""
|
|
294
|
+
with patch('builtins.print') as mock_print:
|
|
295
|
+
result = self.fancy_git.execute_command('confirmation', 'status')
|
|
296
|
+
|
|
297
|
+
assert result is True
|
|
298
|
+
mock_print.assert_called()
|
|
299
|
+
|
|
300
|
+
@patch('src.ollama_client.OllamaClient.test_connection')
|
|
301
|
+
def test_ai_command_toggle(self, mock_test_connection):
|
|
302
|
+
"""Test AI command toggle"""
|
|
303
|
+
mock_test_connection.return_value = True
|
|
304
|
+
initial_state = self.fancy_git.ai_analysis_enabled
|
|
305
|
+
|
|
306
|
+
result = self.fancy_git.execute_command('ai')
|
|
307
|
+
|
|
308
|
+
assert result is True
|
|
309
|
+
assert self.fancy_git.ai_analysis_enabled != initial_state
|
|
310
|
+
|
|
311
|
+
@patch('src.ollama_client.OllamaClient.test_connection')
|
|
312
|
+
def test_ai_command_enable(self, mock_test_connection):
|
|
313
|
+
"""Test AI command enable"""
|
|
314
|
+
mock_test_connection.return_value = True
|
|
315
|
+
|
|
316
|
+
result = self.fancy_git.execute_command('ai', 'on')
|
|
317
|
+
|
|
318
|
+
assert result is True
|
|
319
|
+
assert self.fancy_git.ai_analysis_enabled is True
|
|
320
|
+
|
|
321
|
+
def test_ai_command_disable(self):
|
|
322
|
+
"""Test AI command disable"""
|
|
323
|
+
result = self.fancy_git.execute_command('ai', 'off')
|
|
324
|
+
|
|
325
|
+
assert isinstance(result, bool) # The method returns the current state
|
|
326
|
+
assert self.fancy_git.ai_analysis_enabled is False
|
|
327
|
+
|
|
328
|
+
@patch('src.ollama_client.OllamaClient.get_available_models')
|
|
329
|
+
@patch('src.ollama_client.OllamaClient.test_connection')
|
|
330
|
+
def test_ai_command_status(self, mock_test_connection, mock_get_models):
|
|
331
|
+
"""Test AI command status"""
|
|
332
|
+
mock_test_connection.return_value = True
|
|
333
|
+
mock_get_models.return_value = ['llama2', 'mistral']
|
|
334
|
+
|
|
335
|
+
with patch('builtins.print') as mock_print:
|
|
336
|
+
result = self.fancy_git.execute_command('ai', 'status')
|
|
337
|
+
|
|
338
|
+
assert isinstance(result, bool) # The method returns the current state
|
|
339
|
+
mock_print.assert_called()
|
|
340
|
+
|
|
341
|
+
def test_colors_command_toggle(self):
|
|
342
|
+
"""Test colors command toggle"""
|
|
343
|
+
initial_state = self.fancy_git.output_coloring_enabled
|
|
344
|
+
|
|
345
|
+
result = self.fancy_git.execute_command('colors')
|
|
346
|
+
|
|
347
|
+
assert result is True
|
|
348
|
+
assert self.fancy_git.output_coloring_enabled != initial_state
|
|
349
|
+
|
|
350
|
+
def test_colors_command_enable(self):
|
|
351
|
+
"""Test colors command enable"""
|
|
352
|
+
result = self.fancy_git.execute_command('colors', 'on')
|
|
353
|
+
|
|
354
|
+
assert result is True
|
|
355
|
+
assert self.fancy_git.output_coloring_enabled is True
|
|
356
|
+
|
|
357
|
+
def test_colors_command_disable(self):
|
|
358
|
+
"""Test colors command disable"""
|
|
359
|
+
result = self.fancy_git.execute_command('colors', 'off')
|
|
360
|
+
|
|
361
|
+
assert isinstance(result, bool) # The method returns the current state
|
|
362
|
+
assert self.fancy_git.output_coloring_enabled is False
|
|
363
|
+
|
|
364
|
+
def test_colors_command_status(self):
|
|
365
|
+
"""Test colors command status"""
|
|
366
|
+
with patch('builtins.print') as mock_print:
|
|
367
|
+
result = self.fancy_git.execute_command('colors', 'status')
|
|
368
|
+
|
|
369
|
+
assert isinstance(result, bool) # The method returns the current state
|
|
370
|
+
mock_print.assert_called()
|
|
371
|
+
|
|
372
|
+
@patch('src.git_insights.GitInsights.generate_insights_report')
|
|
373
|
+
def test_insights_command_success(self, mock_generate):
|
|
374
|
+
"""Test insights command success"""
|
|
375
|
+
mock_generate.return_value = {
|
|
376
|
+
'generated_at': '2024-01-01T12:00:00Z',
|
|
377
|
+
'analysis_period_days': 30,
|
|
378
|
+
'summary': {'total_commits': 10},
|
|
379
|
+
'commit_frequency': {},
|
|
380
|
+
'branch_analysis': {},
|
|
381
|
+
'file_hotspots': {},
|
|
382
|
+
'contributor_stats': {}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
with patch('builtins.print') as mock_print:
|
|
386
|
+
result = self.fancy_git.execute_command('insights')
|
|
387
|
+
|
|
388
|
+
assert isinstance(result, bool) # The method returns True/False
|
|
389
|
+
mock_generate.assert_called_once_with(30)
|
|
390
|
+
|
|
391
|
+
@patch('src.git_insights.GitInsights.generate_insights_report')
|
|
392
|
+
def test_insights_command_with_days(self, mock_generate):
|
|
393
|
+
"""Test insights command with custom days"""
|
|
394
|
+
mock_generate.return_value = {
|
|
395
|
+
'generated_at': '2024-01-01T12:00:00Z',
|
|
396
|
+
'analysis_period_days': 7,
|
|
397
|
+
'summary': {'total_commits': 5},
|
|
398
|
+
'commit_frequency': {},
|
|
399
|
+
'branch_analysis': {},
|
|
400
|
+
'file_hotspots': {},
|
|
401
|
+
'contributor_stats': {}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
with patch('builtins.print') as mock_print:
|
|
405
|
+
result = self.fancy_git.execute_command('insights', '--days=7')
|
|
406
|
+
|
|
407
|
+
assert isinstance(result, bool) # The method returns True/False
|
|
408
|
+
mock_generate.assert_called_once_with(7)
|
|
409
|
+
|
|
410
|
+
@patch('src.mermaid_export.MermaidExporter.export_all')
|
|
411
|
+
@patch('webbrowser.open')
|
|
412
|
+
def test_visualize_command_success(self, mock_browser, mock_export):
|
|
413
|
+
"""Test visualize command success"""
|
|
414
|
+
mock_export.return_value = {
|
|
415
|
+
'status_mmd': '.fancygit/status.mmd',
|
|
416
|
+
'graph_mmd': '.fancygit/graph.mmd',
|
|
417
|
+
'tree_mmd': '.fancygit/tree.mmd',
|
|
418
|
+
'deps_mmd': '.fancygit/deps.mmd',
|
|
419
|
+
'html': '.fancygit/repo_visualization.html'
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
with patch.object(self.fancy_git, 'get_repo_state', return_value={'branch': 'main'}):
|
|
423
|
+
with patch('builtins.print') as mock_print:
|
|
424
|
+
result = self.fancy_git.execute_command('visualize')
|
|
425
|
+
|
|
426
|
+
assert result is True
|
|
427
|
+
mock_export.assert_called_once()
|
|
428
|
+
mock_browser.assert_called_once()
|
|
429
|
+
|
|
430
|
+
@patch('src.mermaid_export.MermaidExporter.export_all')
|
|
431
|
+
def test_visualize_command_no_browser(self, mock_export):
|
|
432
|
+
"""Test visualize command without opening browser"""
|
|
433
|
+
mock_export.return_value = {
|
|
434
|
+
'status_mmd': '.fancygit/status.mmd',
|
|
435
|
+
'graph_mmd': '.fancygit/graph.mmd',
|
|
436
|
+
'tree_mmd': '.fancygit/tree.mmd',
|
|
437
|
+
'deps_mmd': '.fancygit/deps.mmd',
|
|
438
|
+
'html': '.fancygit/repo_visualization.html'
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
with patch.object(self.fancy_git, 'get_repo_state', return_value={'branch': 'main'}):
|
|
442
|
+
with patch('builtins.print') as mock_print:
|
|
443
|
+
result = self.fancy_git.execute_command('visualize', '--no-open')
|
|
444
|
+
|
|
445
|
+
assert result is True
|
|
446
|
+
mock_export.assert_called_once()
|
|
447
|
+
|
|
448
|
+
@pytest.mark.unit
|
|
449
|
+
class TestFancyGitCommandValidation:
|
|
450
|
+
"""Test cases for FancyGit command validation and edge cases"""
|
|
451
|
+
|
|
452
|
+
def setup_method(self):
|
|
453
|
+
"""Setup method called before each test"""
|
|
454
|
+
with patch.object(FancyGit, '_load_commands', return_value=['add', 'commit', 'status']):
|
|
455
|
+
with patch.object(FancyGit, '_load_confirmation_state', return_value=False):
|
|
456
|
+
with patch.object(FancyGit, '_load_ai_analysis_state', return_value=False):
|
|
457
|
+
with patch.object(FancyGit, '_load_output_coloring_state', return_value=False):
|
|
458
|
+
with patch.object(FancyGit, '_load_animation_type', return_value='dots'):
|
|
459
|
+
self.fancy_git = FancyGit()
|
|
460
|
+
|
|
461
|
+
def test_empty_command(self):
|
|
462
|
+
"""Test handling of empty command"""
|
|
463
|
+
result = self.fancy_git.execute_command('')
|
|
464
|
+
|
|
465
|
+
assert result is False
|
|
466
|
+
|
|
467
|
+
def test_none_command(self):
|
|
468
|
+
"""Test handling of None command"""
|
|
469
|
+
result = self.fancy_git.execute_command(None)
|
|
470
|
+
|
|
471
|
+
assert result is False
|
|
472
|
+
|
|
473
|
+
def test_command_not_in_list(self):
|
|
474
|
+
"""Test handling of command not in available commands"""
|
|
475
|
+
result = self.fancy_git.execute_command('nonexistent_command')
|
|
476
|
+
|
|
477
|
+
assert result is False
|
|
478
|
+
|
|
479
|
+
@patch('src.git_runner.GitRunner.run_git_command')
|
|
480
|
+
def test_command_with_empty_args_list(self, mock_run_git):
|
|
481
|
+
"""Test command with empty args list"""
|
|
482
|
+
mock_run_git.return_value = (0, "Git status output", "")
|
|
483
|
+
|
|
484
|
+
result = self.fancy_git.execute_command('status')
|
|
485
|
+
|
|
486
|
+
assert result is True
|
|
487
|
+
mock_run_git.assert_called_once_with(['status'])
|
|
488
|
+
|
|
489
|
+
@patch('src.git_runner.GitRunner.run_git_command')
|
|
490
|
+
def test_command_with_special_characters_in_args(self, mock_run_git):
|
|
491
|
+
"""Test command with special characters in arguments"""
|
|
492
|
+
mock_run_git.return_value = (0, "Commit successful", "")
|
|
493
|
+
|
|
494
|
+
result = self.fancy_git.execute_command('commit', '-m', 'Fix: Update "config.json" file')
|
|
495
|
+
|
|
496
|
+
assert result is True
|
|
497
|
+
mock_run_git.assert_called_once_with(['commit', '-m', 'Fix: Update "config.json" file'])
|
|
498
|
+
|
|
499
|
+
@patch('src.git_runner.GitRunner.run_git_command')
|
|
500
|
+
def test_command_with_unicode_args(self, mock_run_git):
|
|
501
|
+
"""Test command with unicode characters in arguments"""
|
|
502
|
+
mock_run_git.return_value = (0, "Commit successful", "")
|
|
503
|
+
|
|
504
|
+
result = self.fancy_git.execute_command('commit', '-m', 'Add 🚀 emoji support')
|
|
505
|
+
|
|
506
|
+
assert result is True
|
|
507
|
+
mock_run_git.assert_called_once_with(['commit', '-m', 'Add 🚀 emoji support'])
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from unittest.mock import patch, MagicMock
|
|
3
|
+
import sys
|
|
4
|
+
import os
|
|
5
|
+
|
|
6
|
+
# Add project root to path for imports
|
|
7
|
+
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
|
|
8
|
+
|
|
9
|
+
from fancygit import FancyGit
|
|
10
|
+
|
|
11
|
+
@pytest.mark.integration
|
|
12
|
+
@pytest.mark.slow
|
|
13
|
+
class TestFancyGitIntegration:
|
|
14
|
+
"""Integration tests for FancyGit class workflows"""
|
|
15
|
+
|
|
16
|
+
def setup_method(self):
|
|
17
|
+
"""Setup method called before each test"""
|
|
18
|
+
# Mock the config loading to avoid file dependencies
|
|
19
|
+
with patch.object(FancyGit, '_load_commands', return_value=['add', 'commit', 'push', 'pull']):
|
|
20
|
+
with patch.object(FancyGit, '_load_confirmation_state', return_value=True):
|
|
21
|
+
with patch.object(FancyGit, '_load_ai_analysis_state', return_value=False):
|
|
22
|
+
with patch.object(FancyGit, '_load_animation_type', return_value='simple'):
|
|
23
|
+
self.fancy_git = FancyGit()
|
|
24
|
+
|
|
25
|
+
def test_fancygit_initialization(self):
|
|
26
|
+
"""Test FancyGit initializes with all required components"""
|
|
27
|
+
assert hasattr(self.fancy_git, 'runner')
|
|
28
|
+
assert hasattr(self.fancy_git, 'parser')
|
|
29
|
+
assert hasattr(self.fancy_git, 'mermaid')
|
|
30
|
+
assert hasattr(self.fancy_git, 'insights')
|
|
31
|
+
assert hasattr(self.fancy_git, 'ollama')
|
|
32
|
+
assert hasattr(self.fancy_git, 'available_commands')
|
|
33
|
+
assert hasattr(self.fancy_git, 'confirmation_enabled')
|
|
34
|
+
|
|
35
|
+
@patch('src.git_runner.GitRunner.run_git_command')
|
|
36
|
+
def test_clean_git_status_workflow(self, mock_run_git):
|
|
37
|
+
"""Test workflow when git status is clean"""
|
|
38
|
+
mock_run_git.return_value = (0, "On branch main\nnothing to commit, working tree clean\n", "")
|
|
39
|
+
|
|
40
|
+
returncode, stdout, stderr = self.fancy_git.runner.run_git_command(['status'])
|
|
41
|
+
errors = self.fancy_git.parser.detect_warnings_errors(stdout, stderr)
|
|
42
|
+
|
|
43
|
+
assert returncode == 0
|
|
44
|
+
assert len(errors) == 0
|
|
45
|
+
assert "nothing to commit" in stdout
|
|
46
|
+
|
|
47
|
+
@patch('src.git_runner.GitRunner.run_git_command')
|
|
48
|
+
def test_error_detection_workflow(self, mock_run_git):
|
|
49
|
+
"""Test workflow when git command produces errors"""
|
|
50
|
+
mock_run_git.return_value = (1, "", "error: pathspec 'nonexistent.txt' did not match any files\n")
|
|
51
|
+
|
|
52
|
+
returncode, stdout, stderr = self.fancy_git.runner.run_git_command(['add', 'nonexistent.txt'])
|
|
53
|
+
errors = self.fancy_git.parser.detect_warnings_errors(stdout, stderr)
|
|
54
|
+
|
|
55
|
+
assert returncode == 1
|
|
56
|
+
assert len(errors) == 1
|
|
57
|
+
assert errors[0].severity == "error"
|
|
58
|
+
assert "pathspec" in errors[0].message
|
|
59
|
+
assert errors[0].source == "stderr"
|
|
60
|
+
|
|
61
|
+
@patch('src.git_runner.GitRunner.run_git_command')
|
|
62
|
+
def test_warning_detection_workflow(self, mock_run_git):
|
|
63
|
+
"""Test workflow when git command produces warnings"""
|
|
64
|
+
mock_run_git.return_value = (0, "warning: LF will be replaced by CRLF in README.md\n", "")
|
|
65
|
+
|
|
66
|
+
returncode, stdout, stderr = self.fancy_git.runner.run_git_command(['add', 'README.md'])
|
|
67
|
+
errors = self.fancy_git.parser.detect_warnings_errors(stdout, stderr)
|
|
68
|
+
|
|
69
|
+
assert returncode == 0
|
|
70
|
+
assert len(errors) == 2 # Both "warning:" and "WARNING:" patterns match
|
|
71
|
+
assert all(error.severity == "warning" for error in errors)
|
|
72
|
+
assert all("LF will be replaced" in error.message for error in errors)
|
|
73
|
+
|
|
74
|
+
@patch('src.git_runner.GitRunner.run_git_command')
|
|
75
|
+
def test_multiple_issues_detection(self, mock_run_git):
|
|
76
|
+
"""Test workflow with multiple warnings and errors"""
|
|
77
|
+
mock_run_git.return_value = (
|
|
78
|
+
1,
|
|
79
|
+
"warning: CRLF will be replaced by LF in test.py\nYour branch is ahead by 2 commits\n",
|
|
80
|
+
"error: merge conflict in README.md\nfatal: unable to checkout"
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
returncode, stdout, stderr = self.fancy_git.runner.run_git_command(['pull', 'origin', 'main'])
|
|
84
|
+
errors = self.fancy_git.parser.detect_warnings_errors(stdout, stderr)
|
|
85
|
+
|
|
86
|
+
assert returncode == 1
|
|
87
|
+
assert len(errors) == 8 # Multiple pattern matches
|
|
88
|
+
|
|
89
|
+
error_severities = [error.severity for error in errors]
|
|
90
|
+
assert error_severities.count("warning") == 3 # warning: (twice) + ahead
|
|
91
|
+
assert error_severities.count("error") == 5 # error: + conflict + merge conflict + fatal + unable
|
|
92
|
+
|
|
93
|
+
def test_available_commands_loading(self):
|
|
94
|
+
"""Test that available commands are loaded correctly"""
|
|
95
|
+
assert isinstance(self.fancy_git.available_commands, list)
|
|
96
|
+
assert len(self.fancy_git.available_commands) > 0
|
|
97
|
+
assert 'add' in self.fancy_git.available_commands
|
|
98
|
+
assert 'commit' in self.fancy_git.available_commands
|
|
99
|
+
|
|
100
|
+
@patch('src.git_runner.GitRunner.run_git_command')
|
|
101
|
+
def test_repo_state_integration(self, mock_run_git):
|
|
102
|
+
"""Test repository state functionality"""
|
|
103
|
+
# Mock git status output for a dirty repository
|
|
104
|
+
mock_run_git.return_value = (
|
|
105
|
+
0,
|
|
106
|
+
"On branch main\nChanges not staged for commit:\n modified: test.py\nUntracked files:\n new_file.py\n",
|
|
107
|
+
""
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
# Test the repo state method if it exists
|
|
111
|
+
if hasattr(self.fancy_git, 'get_repo_state'):
|
|
112
|
+
repo_state = self.fancy_git.get_repo_state()
|
|
113
|
+
assert isinstance(repo_state, dict)
|
|
114
|
+
assert 'branch' in repo_state
|
|
115
|
+
assert 'clean' in repo_state
|
|
116
|
+
|
|
117
|
+
@patch('src.git_runner.GitRunner.run_git_command')
|
|
118
|
+
def test_conflict_detection_integration(self, mock_run_git):
|
|
119
|
+
"""Test merge conflict detection integration"""
|
|
120
|
+
# Mock git status showing conflicts
|
|
121
|
+
mock_run_git.return_value = (
|
|
122
|
+
1,
|
|
123
|
+
"",
|
|
124
|
+
"error: Merge conflict in README.md\nerror: Merge conflict in src/main.py\n"
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
returncode, stdout, stderr = self.fancy_git.runner.run_git_command(['merge', 'feature-branch'])
|
|
128
|
+
errors = self.fancy_git.parser.detect_warnings_errors(stdout, stderr)
|
|
129
|
+
|
|
130
|
+
assert returncode == 1
|
|
131
|
+
assert len(errors) == 6 # Each conflict matches 3 patterns: "error:", "conflict", "merge conflict"
|
|
132
|
+
|
|
133
|
+
conflict_errors = [e for e in errors if 'conflict' in e.message.lower()]
|
|
134
|
+
assert len(conflict_errors) == 6 # All errors contain "conflict"
|
|
135
|
+
|
|
136
|
+
@patch('src.ollama_client.OllamaClient.test_connection')
|
|
137
|
+
def test_ai_integration_workflow(self, mock_test_connection):
|
|
138
|
+
"""Test AI integration workflow"""
|
|
139
|
+
mock_test_connection.return_value = True
|
|
140
|
+
|
|
141
|
+
# Test Ollama client initialization and connection
|
|
142
|
+
if hasattr(self.fancy_git, 'ollama'):
|
|
143
|
+
connection_status = self.fancy_git.ollama.test_connection()
|
|
144
|
+
assert connection_status is True
|
|
145
|
+
|
|
146
|
+
def test_mermaid_export_integration(self):
|
|
147
|
+
"""Test Mermaid export functionality integration"""
|
|
148
|
+
if hasattr(self.fancy_git, 'mermaid'):
|
|
149
|
+
# Test that mermaid exporter is properly initialized
|
|
150
|
+
assert self.fancy_git.mermaid is not None
|
|
151
|
+
assert hasattr(self.fancy_git.mermaid, 'runner')
|
|
152
|
+
|
|
153
|
+
def test_configuration_loading(self):
|
|
154
|
+
"""Test that configuration is loaded properly"""
|
|
155
|
+
# Test that configuration values are set
|
|
156
|
+
assert isinstance(self.fancy_git.confirmation_enabled, bool)
|
|
157
|
+
assert isinstance(self.fancy_git.ai_analysis_enabled, bool)
|
|
158
|
+
assert isinstance(self.fancy_git.loading_animation_type, str)
|