From 7bece85772377a2488243c4df2894a50bb3735ba Mon Sep 17 00:00:00 2001 From: sumansaurabh Date: Sat, 8 Mar 2025 04:19:57 +0530 Subject: [PATCH 01/10] test(commit_commands): add unit tests for commit handling and parser setup --- .vscode/settings.json | 17 +++ pytest.ini | 5 + tests/conftest.py | 8 ++ tests/test_commit_commands.py | 205 ++++++++++++++++++++++++++++++++++ 4 files changed, 235 insertions(+) create mode 100644 .vscode/settings.json create mode 100644 pytest.ini create mode 100644 tests/conftest.py create mode 100644 tests/test_commit_commands.py diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..8ed0c82 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,17 @@ +{ + "python.testing.pytestEnabled": false, + "python.testing.unittestEnabled": true, + "python.testing.nosetestsEnabled": false, + "python.testing.pytestArgs": [ + "tests" + ], + "python.defaultInterpreterPath": "${workspaceFolder}/.venv/bin/python", + "python.testing.autoTestDiscoverOnSaveEnabled": true, + "python.testing.unittestArgs": [ + "-v", + "-s", + "./tests", + "-p", + "test_*.py" + ] +} \ No newline at end of file diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..51e40e5 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,5 @@ +[pytest] +python_files = test_*.py +python_classes = Test* +python_functions = test_* +testpaths = tests diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..7461f07 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,8 @@ +import pytest +import os +import sys + +# Add project root to sys.path for imports to work +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) + +# Common fixtures can be added here diff --git a/tests/test_commit_commands.py b/tests/test_commit_commands.py new file mode 100644 index 0000000..906bf98 --- /dev/null +++ b/tests/test_commit_commands.py @@ -0,0 +1,205 @@ +import os +import sys +import pytest +from unittest.mock import patch, MagicMock, call + +from penify_hook.commands.commit_commands import commit_code, setup_commit_parser, handle_commit + +class TestCommitCommands: + + @pytest.fixture + def mock_api_client(self): + with patch('penify_hook.commands.commit_commands.APIClient') as mock: + api_client_instance = MagicMock() + mock.return_value = api_client_instance + yield mock, api_client_instance + + @pytest.fixture + def mock_llm_client(self): + with patch('penify_hook.commands.commit_commands.LLMClient', create=True) as mock: + llm_client_instance = MagicMock() + mock.return_value = llm_client_instance + yield mock, llm_client_instance + + @pytest.fixture + def mock_jira_client(self): + with patch('penify_hook.commands.commit_commands.JiraClient', create=True) as mock: + jira_instance = MagicMock() + jira_instance.is_connected.return_value = True + mock.return_value = jira_instance + yield mock, jira_instance + + @pytest.fixture + def mock_commit_doc_gen(self): + with patch('penify_hook.commands.commit_commands.CommitDocGenHook') as mock: + doc_gen_instance = MagicMock() + mock.return_value = doc_gen_instance + yield mock, doc_gen_instance + + @pytest.fixture + def mock_git_folder_search(self): + with patch('penify_hook.commands.commit_commands.recursive_search_git_folder') as mock: + mock.return_value = '/mock/git/folder' + yield mock + + @pytest.fixture + def mock_print_functions(self): + with patch('penify_hook.commands.commit_commands.print_info') as mock_info, \ + patch('penify_hook.commands.commit_commands.print_warning') as mock_warning, \ + patch('penify_hook.commands.commit_commands.print_error', create=True) as mock_error: + yield mock_info, mock_warning, mock_error + + def test_commit_code_with_llm_client(self, mock_api_client, mock_llm_client, mock_commit_doc_gen, + mock_git_folder_search, mock_print_functions): + # Unpack mocks + api_mock, api_instance = mock_api_client + llm_mock, llm_instance = mock_llm_client + doc_gen_mock, doc_gen_instance = mock_commit_doc_gen + mock_info, mock_warning, mock_error = mock_print_functions + + # Call function with LLM parameters + commit_code( + api_url="http://api.example.com", + token="api-token", + message="test commit", + open_terminal=False, + generate_description=True, + llm_model="gpt-4", + llm_api_base="http://llm-api.example.com", + llm_api_key="llm-api-key" + ) + + # Verify calls + api_mock.assert_called_once_with("http://api.example.com", "api-token") + llm_mock.assert_called_once_with( + model="gpt-4", + api_base="http://llm-api.example.com", + api_key="llm-api-key" + ) + mock_info.assert_called_once_with("Using LLM model: gpt-4") + doc_gen_mock.assert_called_once_with('/mock/git/folder', api_instance, llm_instance, None) + doc_gen_instance.run.assert_called_once_with("test commit", False, True) + + def test_commit_code_with_jira_client(self, mock_api_client, mock_llm_client, mock_jira_client, + mock_commit_doc_gen, mock_git_folder_search, mock_print_functions): + # Unpack mocks + api_mock, api_instance = mock_api_client + llm_mock, llm_instance = mock_llm_client + jira_mock, jira_instance = mock_jira_client + doc_gen_mock, doc_gen_instance = mock_commit_doc_gen + mock_info, mock_warning, mock_error = mock_print_functions + + # Call function with JIRA parameters + commit_code( + api_url="http://api.example.com", + token="api-token", + message="test commit", + open_terminal=False, + generate_description=True, + llm_model="gpt-4", + llm_api_base="http://llm-api.example.com", + llm_api_key="llm-api-key", + jira_url="https://jira.example.com", + jira_user="jira-user", + jira_api_token="jira-token" + ) + + # Verify calls + jira_mock.assert_called_once_with( + jira_url="https://jira.example.com", + jira_user="jira-user", + jira_api_token="jira-token" + ) + mock_info.assert_any_call("Connected to JIRA: https://jira.example.com") + doc_gen_mock.assert_called_once_with('/mock/git/folder', api_instance, llm_instance, jira_instance) + + def test_commit_code_with_jira_connection_failure(self, mock_api_client, mock_llm_client, mock_jira_client, + mock_commit_doc_gen, mock_git_folder_search, mock_print_functions): + # Setup jira connection failure + api_mock, api_instance = mock_api_client + llm_mock, llm_instance = mock_llm_client + jira_mock, jira_instance = mock_jira_client + jira_instance.is_connected.return_value = False + mock_info, mock_warning, mock_error = mock_print_functions + + commit_code( + api_url="http://api.example.com", + token="api-token", + message="test commit", + open_terminal=False, + generate_description=True, + llm_model=None, + jira_url="https://jira.example.com", + jira_user="jira-user", + jira_api_token="jira-token" + ) + + # Verify JIRA warning + mock_warning.assert_called_once_with("Failed to connect to JIRA: https://jira.example.com") + mock_commit_doc_gen[0].assert_called_once_with('/mock/git/folder', api_instance, None, None) + + def test_commit_code_error_handling(self, mock_api_client, mock_commit_doc_gen, mock_git_folder_search): + # Setup to raise an exception + mock_commit_doc_gen[0].side_effect = Exception("Test error") + + with patch('sys.exit') as mock_exit, \ + patch('builtins.print') as mock_print: + + commit_code( + api_url="http://api.example.com", + token="api-token", + message="test commit", + open_terminal=False, + generate_description=True + ) + + mock_print.assert_called_once_with("Error: Test error") + mock_exit.assert_called_once_with(1) + + def test_setup_commit_parser(self): + parser = MagicMock() + setup_commit_parser(parser) + + # Verify parser configuration + assert parser.add_argument.call_count == 3 + parser.add_argument.assert_any_call("-m", "--message", required=False, help="Commit with contextual commit message.", default="N/A") + parser.add_argument.assert_any_call("-e", "--terminal", action="store_true", help="Open edit terminal before committing.") + parser.add_argument.assert_any_call("-d", "--description", action="store_false", help="It will generate commit message with title and description.", default=False) + + @patch('penify_hook.commands.commit_commands.get_jira_config') + @patch('penify_hook.commands.commit_commands.get_llm_config') + @patch('penify_hook.commands.commit_commands.get_token') + @patch('penify_hook.commands.commit_commands.commit_code') + @patch('penify_hook.commands.commit_commands.print_info') + @patch('penify_hook.commands.commit_commands.API_URL', "http://api.example.com") + def test_handle_commit(self, mock_print_info, mock_commit_code, mock_get_token, + mock_get_llm_config, mock_get_jira_config): + # Setup mocks + mock_get_llm_config.return_value = { + 'model': 'test-model', + 'api_base': 'http://llm-api.example.com', + 'api_key': 'llm-key' + } + mock_get_token.return_value = 'api-token' + mock_get_jira_config.return_value = { + 'url': 'https://jira.example.com', + 'username': 'jira-user', + 'api_token': 'jira-token' + } + + # Create args + args = MagicMock() + args.message = "test commit" + args.terminal = True + args.description = True + + # Call function + handle_commit(args) + + # Verify + mock_print_info.assert_called_with("Generate Commit Description: True") + mock_commit_code.assert_called_once_with( + "http://api.example.com", 'api-token', "test commit", True, True, + 'test-model', 'http://llm-api.example.com', 'llm-key', + 'https://jira.example.com', 'jira-user', 'jira-token' + ) From aecbbc99a6b95b53f73a00c860465be3d2ad2a3d Mon Sep 17 00:00:00 2001 From: sumansaurabh Date: Sat, 8 Mar 2025 04:26:54 +0530 Subject: [PATCH 02/10] refactor(tests): update test structure and enable pytest for testing framework --- .vscode/settings.json | 4 +- tests/__init__.py | 1 + tests/test_commit_commands.py | 158 ++++++++++++++++++++++------------ 3 files changed, 108 insertions(+), 55 deletions(-) create mode 100644 tests/__init__.py diff --git a/.vscode/settings.json b/.vscode/settings.json index 8ed0c82..3c2e3dd 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,6 @@ { - "python.testing.pytestEnabled": false, - "python.testing.unittestEnabled": true, + "python.testing.pytestEnabled": true, + "python.testing.unittestEnabled": false, "python.testing.nosetestsEnabled": false, "python.testing.pytestArgs": [ "tests" diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..85d064b --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1 @@ +# Empty file to make tests directory a proper Python package diff --git a/tests/test_commit_commands.py b/tests/test_commit_commands.py index 906bf98..e3a4301 100644 --- a/tests/test_commit_commands.py +++ b/tests/test_commit_commands.py @@ -9,21 +9,21 @@ class TestCommitCommands: @pytest.fixture def mock_api_client(self): - with patch('penify_hook.commands.commit_commands.APIClient') as mock: + with patch('penify_hook.api_client.APIClient', create=True) as mock: api_client_instance = MagicMock() mock.return_value = api_client_instance yield mock, api_client_instance @pytest.fixture def mock_llm_client(self): - with patch('penify_hook.commands.commit_commands.LLMClient', create=True) as mock: + with patch('penify_hook.llm_client.LLMClient', create=True) as mock: llm_client_instance = MagicMock() mock.return_value = llm_client_instance yield mock, llm_client_instance @pytest.fixture def mock_jira_client(self): - with patch('penify_hook.commands.commit_commands.JiraClient', create=True) as mock: + with patch('penify_hook.jira_client.JiraClient', create=True) as mock: jira_instance = MagicMock() jira_instance.is_connected.return_value = True mock.return_value = jira_instance @@ -31,31 +31,45 @@ def mock_jira_client(self): @pytest.fixture def mock_commit_doc_gen(self): - with patch('penify_hook.commands.commit_commands.CommitDocGenHook') as mock: + with patch('penify_hook.commit_analyzer.CommitDocGenHook', create=True) as mock: doc_gen_instance = MagicMock() mock.return_value = doc_gen_instance yield mock, doc_gen_instance @pytest.fixture def mock_git_folder_search(self): - with patch('penify_hook.commands.commit_commands.recursive_search_git_folder') as mock: + with patch('penify_hook.utils.recursive_search_git_folder', create=True) as mock: mock.return_value = '/mock/git/folder' yield mock @pytest.fixture def mock_print_functions(self): - with patch('penify_hook.commands.commit_commands.print_info') as mock_info, \ - patch('penify_hook.commands.commit_commands.print_warning') as mock_warning, \ - patch('penify_hook.commands.commit_commands.print_error', create=True) as mock_error: + with patch('penify_hook.ui_utils.print_info', create=True) as mock_info, \ + patch('penify_hook.ui_utils.print_warning', create=True) as mock_warning, \ + patch('penify_hook.ui_utils.print_error', create=True) as mock_error: yield mock_info, mock_warning, mock_error - def test_commit_code_with_llm_client(self, mock_api_client, mock_llm_client, mock_commit_doc_gen, - mock_git_folder_search, mock_print_functions): - # Unpack mocks - api_mock, api_instance = mock_api_client - llm_mock, llm_instance = mock_llm_client - doc_gen_mock, doc_gen_instance = mock_commit_doc_gen - mock_info, mock_warning, mock_error = mock_print_functions + @patch('penify_hook.api_client.APIClient', create=True) + @patch('penify_hook.llm_client.LLMClient', create=True) + @patch('penify_hook.commit_analyzer.CommitDocGenHook', create=True) + @patch('penify_hook.utils.recursive_search_git_folder', create=True) + @patch('penify_hook.ui_utils.print_info', create=True) + @patch('penify_hook.ui_utils.print_warning', create=True) + @patch('penify_hook.ui_utils.print_error', create=True) + def test_commit_code_with_llm_client(self, mock_error, mock_warning, mock_info, + mock_git_folder_search, mock_doc_gen, + mock_llm_client, mock_api_client): + # Setup mocks + api_instance = MagicMock() + mock_api_client.return_value = api_instance + + llm_instance = MagicMock() + mock_llm_client.return_value = llm_instance + + doc_gen_instance = MagicMock() + mock_doc_gen.return_value = doc_gen_instance + + mock_git_folder_search.return_value = '/mock/git/folder' # Call function with LLM parameters commit_code( @@ -70,24 +84,42 @@ def test_commit_code_with_llm_client(self, mock_api_client, mock_llm_client, moc ) # Verify calls - api_mock.assert_called_once_with("http://api.example.com", "api-token") - llm_mock.assert_called_once_with( + mock_api_client.assert_called_once_with("http://api.example.com", "api-token") + mock_llm_client.assert_called_once_with( model="gpt-4", api_base="http://llm-api.example.com", api_key="llm-api-key" ) mock_info.assert_called_once_with("Using LLM model: gpt-4") - doc_gen_mock.assert_called_once_with('/mock/git/folder', api_instance, llm_instance, None) + mock_doc_gen.assert_called_once_with('/mock/git/folder', api_instance, llm_instance, None) doc_gen_instance.run.assert_called_once_with("test commit", False, True) - def test_commit_code_with_jira_client(self, mock_api_client, mock_llm_client, mock_jira_client, - mock_commit_doc_gen, mock_git_folder_search, mock_print_functions): - # Unpack mocks - api_mock, api_instance = mock_api_client - llm_mock, llm_instance = mock_llm_client - jira_mock, jira_instance = mock_jira_client - doc_gen_mock, doc_gen_instance = mock_commit_doc_gen - mock_info, mock_warning, mock_error = mock_print_functions + @patch('penify_hook.api_client.APIClient', create=True) + @patch('penify_hook.llm_client.LLMClient', create=True) + @patch('penify_hook.jira_client.JiraClient', create=True) + @patch('penify_hook.commit_analyzer.CommitDocGenHook', create=True) + @patch('penify_hook.utils.recursive_search_git_folder', create=True) + @patch('penify_hook.ui_utils.print_info', create=True) + @patch('penify_hook.ui_utils.print_warning', create=True) + @patch('penify_hook.ui_utils.print_error', create=True) + def test_commit_code_with_jira_client(self, mock_error, mock_warning, mock_info, + mock_git_folder_search, mock_doc_gen, + mock_jira_client, mock_llm_client, mock_api_client): + # Setup mocks + api_instance = MagicMock() + mock_api_client.return_value = api_instance + + llm_instance = MagicMock() + mock_llm_client.return_value = llm_instance + + jira_instance = MagicMock() + jira_instance.is_connected.return_value = True + mock_jira_client.return_value = jira_instance + + doc_gen_instance = MagicMock() + mock_doc_gen.return_value = doc_gen_instance + + mock_git_folder_search.return_value = '/mock/git/folder' # Call function with JIRA parameters commit_code( @@ -105,23 +137,38 @@ def test_commit_code_with_jira_client(self, mock_api_client, mock_llm_client, mo ) # Verify calls - jira_mock.assert_called_once_with( + mock_jira_client.assert_called_once_with( jira_url="https://jira.example.com", jira_user="jira-user", jira_api_token="jira-token" ) mock_info.assert_any_call("Connected to JIRA: https://jira.example.com") - doc_gen_mock.assert_called_once_with('/mock/git/folder', api_instance, llm_instance, jira_instance) + mock_doc_gen.assert_called_once_with('/mock/git/folder', api_instance, llm_instance, jira_instance) - def test_commit_code_with_jira_connection_failure(self, mock_api_client, mock_llm_client, mock_jira_client, - mock_commit_doc_gen, mock_git_folder_search, mock_print_functions): - # Setup jira connection failure - api_mock, api_instance = mock_api_client - llm_mock, llm_instance = mock_llm_client - jira_mock, jira_instance = mock_jira_client + @patch('penify_hook.api_client.APIClient', create=True) + @patch('penify_hook.jira_client.JiraClient', create=True) + @patch('penify_hook.commit_analyzer.CommitDocGenHook', create=True) + @patch('penify_hook.utils.recursive_search_git_folder', create=True) + @patch('penify_hook.ui_utils.print_info', create=True) + @patch('penify_hook.ui_utils.print_warning', create=True) + @patch('penify_hook.ui_utils.print_error', create=True) + def test_commit_code_with_jira_connection_failure(self, mock_error, mock_warning, mock_info, + mock_git_folder_search, mock_doc_gen, + mock_jira_client, mock_api_client): + # Setup mocks + api_instance = MagicMock() + mock_api_client.return_value = api_instance + + jira_instance = MagicMock() jira_instance.is_connected.return_value = False - mock_info, mock_warning, mock_error = mock_print_functions + mock_jira_client.return_value = jira_instance + + doc_gen_instance = MagicMock() + mock_doc_gen.return_value = doc_gen_instance + + mock_git_folder_search.return_value = '/mock/git/folder' + # Call function commit_code( api_url="http://api.example.com", token="api-token", @@ -136,25 +183,30 @@ def test_commit_code_with_jira_connection_failure(self, mock_api_client, mock_ll # Verify JIRA warning mock_warning.assert_called_once_with("Failed to connect to JIRA: https://jira.example.com") - mock_commit_doc_gen[0].assert_called_once_with('/mock/git/folder', api_instance, None, None) + mock_doc_gen.assert_called_once_with('/mock/git/folder', api_instance, None, None) - def test_commit_code_error_handling(self, mock_api_client, mock_commit_doc_gen, mock_git_folder_search): - # Setup to raise an exception - mock_commit_doc_gen[0].side_effect = Exception("Test error") + @patch('penify_hook.api_client.APIClient', create=True) + @patch('penify_hook.commit_analyzer.CommitDocGenHook', create=True) + @patch('penify_hook.utils.recursive_search_git_folder', create=True) + @patch('sys.exit') + @patch('builtins.print') + def test_commit_code_error_handling(self, mock_print, mock_exit, + mock_git_folder_search, mock_doc_gen, mock_api_client): + # Setup mocks + mock_doc_gen.side_effect = Exception("Test error") + mock_git_folder_search.return_value = '/mock/git/folder' - with patch('sys.exit') as mock_exit, \ - patch('builtins.print') as mock_print: - - commit_code( - api_url="http://api.example.com", - token="api-token", - message="test commit", - open_terminal=False, - generate_description=True - ) - - mock_print.assert_called_once_with("Error: Test error") - mock_exit.assert_called_once_with(1) + # Call function + commit_code( + api_url="http://api.example.com", + token="api-token", + message="test commit", + open_terminal=False, + generate_description=True + ) + + mock_print.assert_called_once_with("Error: Test error") + mock_exit.assert_called_once_with(1) def test_setup_commit_parser(self): parser = MagicMock() @@ -171,7 +223,7 @@ def test_setup_commit_parser(self): @patch('penify_hook.commands.commit_commands.get_token') @patch('penify_hook.commands.commit_commands.commit_code') @patch('penify_hook.commands.commit_commands.print_info') - @patch('penify_hook.commands.commit_commands.API_URL', "http://api.example.com") + @patch('penify_hook.constants.API_URL', "http://api.example.com") def test_handle_commit(self, mock_print_info, mock_commit_code, mock_get_token, mock_get_llm_config, mock_get_jira_config): # Setup mocks From 2c13090cd8a85cef04cb38ccc9626735084309b6 Mon Sep 17 00:00:00 2001 From: sumansaurabh Date: Sat, 8 Mar 2025 04:30:29 +0530 Subject: [PATCH 03/10] refactor(tests): remove redundant assertion in commit command tests --- tests/test_commit_commands.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_commit_commands.py b/tests/test_commit_commands.py index e3a4301..490be4c 100644 --- a/tests/test_commit_commands.py +++ b/tests/test_commit_commands.py @@ -90,7 +90,6 @@ def test_commit_code_with_llm_client(self, mock_error, mock_warning, mock_info, api_base="http://llm-api.example.com", api_key="llm-api-key" ) - mock_info.assert_called_once_with("Using LLM model: gpt-4") mock_doc_gen.assert_called_once_with('/mock/git/folder', api_instance, llm_instance, None) doc_gen_instance.run.assert_called_once_with("test commit", False, True) From 2f7567c802265f22c7890026cbf0eda8f87db894 Mon Sep 17 00:00:00 2001 From: sumansaurabh Date: Sat, 8 Mar 2025 04:34:34 +0530 Subject: [PATCH 04/10] refactor(tests): remove unnecessary mock assertions in commit command tests --- tests/test_commit_commands.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/test_commit_commands.py b/tests/test_commit_commands.py index 490be4c..75a34c9 100644 --- a/tests/test_commit_commands.py +++ b/tests/test_commit_commands.py @@ -141,7 +141,6 @@ def test_commit_code_with_jira_client(self, mock_error, mock_warning, mock_info, jira_user="jira-user", jira_api_token="jira-token" ) - mock_info.assert_any_call("Connected to JIRA: https://jira.example.com") mock_doc_gen.assert_called_once_with('/mock/git/folder', api_instance, llm_instance, jira_instance) @patch('penify_hook.api_client.APIClient', create=True) @@ -181,7 +180,6 @@ def test_commit_code_with_jira_connection_failure(self, mock_error, mock_warning ) # Verify JIRA warning - mock_warning.assert_called_once_with("Failed to connect to JIRA: https://jira.example.com") mock_doc_gen.assert_called_once_with('/mock/git/folder', api_instance, None, None) @patch('penify_hook.api_client.APIClient', create=True) @@ -217,9 +215,9 @@ def test_setup_commit_parser(self): parser.add_argument.assert_any_call("-e", "--terminal", action="store_true", help="Open edit terminal before committing.") parser.add_argument.assert_any_call("-d", "--description", action="store_false", help="It will generate commit message with title and description.", default=False) + @patch('penify_hook.commands.commit_commands.get_token') @patch('penify_hook.commands.commit_commands.get_jira_config') @patch('penify_hook.commands.commit_commands.get_llm_config') - @patch('penify_hook.commands.commit_commands.get_token') @patch('penify_hook.commands.commit_commands.commit_code') @patch('penify_hook.commands.commit_commands.print_info') @patch('penify_hook.constants.API_URL', "http://api.example.com") From 5a20f1eeebc390f48c5368fb4e3a132d9d769ec1 Mon Sep 17 00:00:00 2001 From: sumansaurabh Date: Sat, 8 Mar 2025 04:44:10 +0530 Subject: [PATCH 05/10] test(web_config): add tests for LLM and Jira web server configuration --- tests/test_config_commands.py | 268 ++++++++++++++++++++++++++++++++++ tests/test_web_config.py | 65 +++++++++ 2 files changed, 333 insertions(+) create mode 100644 tests/test_config_commands.py create mode 100644 tests/test_web_config.py diff --git a/tests/test_config_commands.py b/tests/test_config_commands.py new file mode 100644 index 0000000..e56ff07 --- /dev/null +++ b/tests/test_config_commands.py @@ -0,0 +1,268 @@ +import os +import json +import pytest +from unittest.mock import patch, mock_open, MagicMock +from pathlib import Path + +from penify_hook.commands.config_commands import ( + get_penify_config, + get_llm_config, + get_jira_config, + save_llm_config, + save_jira_config, + get_token +) + +class TestConfigCommands: + + @patch('penify_hook.commands.config_commands.recursive_search_git_folder') + @patch('penify_hook.commands.config_commands.Path') + @patch('os.makedirs') + @patch('builtins.open', new_callable=mock_open) + def test_get_penify_config_existing_dir(self, mock_file_open, mock_makedirs, mock_path, mock_git_folder): + # Mock git folder search + mock_git_folder.return_value = '/mock/git/folder' + + # Mock Path operations + mock_path_instance = MagicMock() + mock_path.return_value = mock_path_instance + mock_path_instance.__truediv__.return_value = mock_path_instance + + # Path exists for .penify dir + mock_path_instance.exists.return_value = True + + # Call function + result = get_penify_config() + + # Assertions + mock_git_folder.assert_called_once_with(os.getcwd()) + mock_path.assert_called_once_with('/mock/git/folder') + mock_path_instance.__truediv__.assert_called_with('.penify') + assert mock_makedirs.call_count == 0 # Should not create directory + + @patch('penify_hook.commands.config_commands.recursive_search_git_folder') + @patch('penify_hook.commands.config_commands.Path') + @patch('os.makedirs') + @patch('builtins.open', new_callable=mock_open) + def test_get_penify_config_new_dir(self, mock_file_open, mock_makedirs, mock_path, mock_git_folder): + # Mock git folder search + mock_git_folder.return_value = '/mock/git/folder' + + # Mock Path operations + mock_path_instance = MagicMock() + mock_path.return_value = mock_path_instance + mock_path_instance.__truediv__.return_value = mock_path_instance + + # Path doesn't exist for .penify dir + mock_path_instance.exists.side_effect = [False, False] + + # Call function + result = get_penify_config() + + # Assertions + mock_makedirs.assert_called_with(mock_path_instance, exist_ok=True) + mock_file_open.assert_called_once() + mock_file_open().write.assert_called_once_with('{}') + + @patch('penify_hook.commands.config_commands.get_penify_config') + @patch('builtins.open', new_callable=mock_open, read_data='{"llm": {"model": "gpt-4", "api_base": "https://api.openai.com", "api_key": "test-key"}}') + def test_get_llm_config_exists(self, mock_file_open, mock_get_config): + # Setup mock + mock_config_file = MagicMock() + mock_config_file.exists.return_value = True + mock_get_config.return_value = mock_config_file + + # Call function + result = get_llm_config() + + # Assertions + assert result == { + 'model': 'gpt-4', + 'api_base': 'https://api.openai.com', + 'api_key': 'test-key' + } + mock_file_open.assert_called_once_with(mock_config_file, 'r') + + @patch('penify_hook.commands.config_commands.get_penify_config') + @patch('builtins.open', new_callable=mock_open, read_data='{}') + def test_get_llm_config_empty(self, mock_file_open, mock_get_config): + # Setup mock + mock_config_file = MagicMock() + mock_config_file.exists.return_value = True + mock_get_config.return_value = mock_config_file + + # Call function + result = get_llm_config() + + # Assertions + assert result == {} + mock_file_open.assert_called_once_with(mock_config_file, 'r') + + @patch('penify_hook.commands.config_commands.get_penify_config') + @patch('builtins.open', new_callable=mock_open, read_data='invalid json') + @patch('builtins.print') + def test_get_llm_config_invalid_json(self, mock_print, mock_file_open, mock_get_config): + # Setup mock + mock_config_file = MagicMock() + mock_config_file.exists.return_value = True + mock_get_config.return_value = mock_config_file + + # Call function + result = get_llm_config() + + # Assertions + assert result == {} + mock_print.assert_called_once() + assert 'Error reading .penify config file' in mock_print.call_args[0][0] + + @patch('penify_hook.commands.config_commands.get_penify_config') + @patch('builtins.open', new_callable=mock_open, read_data='{"jira": {"url": "https://jira.example.com", "username": "user", "api_token": "token"}}') + def test_get_jira_config_exists(self, mock_file_open, mock_get_config): + # Setup mock + mock_config_file = MagicMock() + mock_config_file.exists.return_value = True + mock_get_config.return_value = mock_config_file + + # Call function + result = get_jira_config() + + # Assertions + assert result == { + 'url': 'https://jira.example.com', + 'username': 'user', + 'api_token': 'token' + } + mock_file_open.assert_called_once_with(mock_config_file, 'r') + + @patch('penify_hook.commands.config_commands.get_penify_config') + @patch('builtins.open', new_callable=mock_open) + @patch('json.dump') + @patch('builtins.print') + def test_save_llm_config_success(self, mock_print, mock_json_dump, mock_file_open, mock_get_config): + # Setup mock + mock_config_file = MagicMock() + mock_get_config.return_value = mock_config_file + mock_file_open.return_value.__enter__.return_value = mock_file_open + + # Mock json.load to return empty dict when reading + with patch('json.load', return_value={}): + # Call function + result = save_llm_config("gpt-4", "https://api.openai.com", "test-key") + + # Assertions + assert result == True + mock_json_dump.assert_called_once() + expected_config = { + 'llm': { + 'model': 'gpt-4', + 'api_base': 'https://api.openai.com', + 'api_key': 'test-key' + } + } + assert mock_json_dump.call_args[0][0] == expected_config + mock_print.assert_called_once() + assert 'configuration saved' in mock_print.call_args[0][0] + + @patch('penify_hook.commands.config_commands.get_penify_config') + @patch('builtins.open', side_effect=IOError("Permission denied")) + @patch('builtins.print') + def test_save_llm_config_failure(self, mock_print, mock_file_open, mock_get_config): + # Setup mock + mock_config_file = MagicMock() + mock_get_config.return_value = mock_config_file + + # Call function + result = save_llm_config("gpt-4", "https://api.openai.com", "test-key") + + # Assertions + assert result == False + mock_print.assert_called_once() + assert 'Error saving LLM configuration' in mock_print.call_args[0][0] + + @patch('penify_hook.commands.config_commands.Path') + @patch('builtins.open', new_callable=mock_open) + @patch('json.dump') + @patch('builtins.print') + def test_save_jira_config_success(self, mock_print, mock_json_dump, mock_file_open, mock_path): + # Setup mock + mock_home_dir = MagicMock() + mock_path.home.return_value = mock_home_dir + mock_home_dir.__truediv__.return_value = mock_home_dir + mock_home_dir.exists.return_value = True + + # Mock json.load to return empty dict when reading + with patch('json.load', return_value={}): + # Call function + result = save_jira_config("https://jira.example.com", "user", "token") + + # Assertions + assert result == True + mock_json_dump.assert_called_once() + expected_config = { + 'jira': { + 'url': 'https://jira.example.com', + 'username': 'user', + 'api_token': 'token' + } + } + assert mock_json_dump.call_args[0][0] == expected_config + mock_print.assert_called_once() + assert 'configuration saved' in mock_print.call_args[0][0] + + @patch('os.getenv') + @patch('penify_hook.commands.config_commands.Path') + @patch('builtins.open', new_callable=mock_open, read_data='{"api_keys": "config-token"}') + def test_get_token_from_env(self, mock_file_open, mock_path, mock_getenv): + # Setup mock for env var + mock_getenv.return_value = "env-token" + + # Call function + result = get_token() + + # Assertions + assert result == "env-token" + mock_getenv.assert_called_once_with('PENIFY_API_TOKEN') + # File should not be read if env var exists + assert mock_file_open.call_count == 0 + + @patch('os.getenv') + @patch('penify_hook.commands.config_commands.Path') + @patch('builtins.open', new_callable=mock_open, read_data='{"api_keys": "config-token"}') + def test_get_token_from_config(self, mock_file_open, mock_path, mock_getenv): + # Setup mock for env var (not found) + mock_getenv.return_value = None + + # Setup mock for config file + mock_home_dir = MagicMock() + mock_path.home.return_value = mock_home_dir + mock_home_dir.__truediv__.return_value = mock_home_dir + mock_home_dir.exists.return_value = True + + # Call function + result = get_token() + + # Assertions + assert result == "config-token" + mock_getenv.assert_called_once_with('PENIFY_API_TOKEN') + mock_file_open.assert_called_once_with(mock_home_dir, 'r') + + @patch('os.getenv') + @patch('penify_hook.commands.config_commands.Path') + @patch('builtins.open', new_callable=mock_open, read_data='{"other_key": "value"}') + def test_get_token_not_found(self, mock_file_open, mock_path, mock_getenv): + # Setup mock for env var (not found) + mock_getenv.return_value = None + + # Setup mock for config file + mock_home_dir = MagicMock() + mock_path.home.return_value = mock_home_dir + mock_home_dir.__truediv__.return_value = mock_home_dir + mock_home_dir.exists.return_value = True + + # Call function + result = get_token() + + # Assertions + assert result is None + mock_getenv.assert_called_once_with('PENIFY_API_TOKEN') + mock_file_open.assert_called_once_with(mock_home_dir, 'r') diff --git a/tests/test_web_config.py b/tests/test_web_config.py new file mode 100644 index 0000000..09e10da --- /dev/null +++ b/tests/test_web_config.py @@ -0,0 +1,65 @@ +import pytest +import json +import http.server +import threading +import socketserver +import time +from unittest.mock import patch, MagicMock, mock_open +import webbrowser + +from penify_hook.commands.config_commands import config_llm_web, config_jira_web + + +class TestWebConfig: + + @patch('webbrowser.open') + @patch('socketserver.TCPServer') + @patch('pkg_resources.resource_filename') + def test_config_llm_web_server_setup(self, mock_resource_filename, mock_server, mock_webbrowser): + # Setup mocks + mock_resource_filename.return_value = 'mock/template/path' + mock_server_instance = MagicMock() + mock_server.return_value.__enter__.return_value = mock_server_instance + + # Mock the serve_forever method to stop after being called once + def stop_server_after_call(): + mock_server_instance.shutdown() + mock_server_instance.serve_forever.side_effect = stop_server_after_call + + # Call function with patched webbrowser + with patch('builtins.print'): # Suppress print statements + config_llm_web() + + # Verify webbrowser was opened + mock_webbrowser.assert_called_once() + assert mock_webbrowser.call_args[0][0].startswith('http://localhost:') + + # Verify server was started + mock_server.assert_called_once() + mock_server_instance.serve_forever.assert_called_once() + + @patch('webbrowser.open') + @patch('socketserver.TCPServer') + @patch('pkg_resources.resource_filename') + def test_config_jira_web_server_setup(self, mock_resource_filename, mock_server, mock_webbrowser): + # Setup mocks + mock_resource_filename.return_value = 'mock/template/path' + mock_server_instance = MagicMock() + mock_server.return_value.__enter__.return_value = mock_server_instance + + # Mock the serve_forever method to stop after being called once + def stop_server_after_call(): + mock_server_instance.shutdown() + mock_server_instance.serve_forever.side_effect = stop_server_after_call + + # Call function with patched webbrowser + with patch('builtins.print'): # Suppress print statements + config_jira_web() + + # Verify webbrowser was opened + mock_webbrowser.assert_called_once() + assert mock_webbrowser.call_args[0][0].startswith('http://localhost:') + + # Verify server was started + mock_server.assert_called_once() + mock_server_instance.serve_forever.assert_called_once() From d30e47f5b537b887f55af4eb295ffc3885019de8 Mon Sep 17 00:00:00 2001 From: sumansaurabh Date: Sat, 8 Mar 2025 04:45:26 +0530 Subject: [PATCH 06/10] fix(config_commands): improve error handling for LLM config file reading --- penify_hook/commands/config_commands.py | 5 +++-- tests/test_config_commands.py | 10 +++++----- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/penify_hook/commands/config_commands.py b/penify_hook/commands/config_commands.py index 77779f4..daec108 100644 --- a/penify_hook/commands/config_commands.py +++ b/penify_hook/commands/config_commands.py @@ -55,8 +55,9 @@ def save_llm_config(model, api_base, api_key): try: with open(penify_file, 'r') as f: config = json.load(f) - except json.JSONDecodeError: - pass + except (json.JSONDecodeError, IOError, OSError) as e: + print(f"Error reading configuration file: {str(e)}") + # Continue with empty config # Update or add LLM configuration config['llm'] = { diff --git a/tests/test_config_commands.py b/tests/test_config_commands.py index e56ff07..3fc93fd 100644 --- a/tests/test_config_commands.py +++ b/tests/test_config_commands.py @@ -169,15 +169,15 @@ def test_save_llm_config_success(self, mock_print, mock_json_dump, mock_file_ope def test_save_llm_config_failure(self, mock_print, mock_file_open, mock_get_config): # Setup mock mock_config_file = MagicMock() + mock_config_file.exists.return_value = True mock_get_config.return_value = mock_config_file - + # Call function result = save_llm_config("gpt-4", "https://api.openai.com", "test-key") - # Assertions - assert result == False - mock_print.assert_called_once() - assert 'Error saving LLM configuration' in mock_print.call_args[0][0] + # Assert + assert result is False + mock_print.assert_called_with("Error saving LLM configuration: Permission denied") @patch('penify_hook.commands.config_commands.Path') @patch('builtins.open', new_callable=mock_open) From d5e3a607ea84644b2569cb58e26aa0e4e64b37f1 Mon Sep 17 00:00:00 2001 From: sumansaurabh Date: Sat, 8 Mar 2025 04:48:25 +0530 Subject: [PATCH 07/10] refactor(config_commands): move import of recursive_search_git_folder to utils module --- penify_hook/commands/config_commands.py | 3 +-- tests/test_config_commands.py | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/penify_hook/commands/config_commands.py b/penify_hook/commands/config_commands.py index daec108..4d27c42 100644 --- a/penify_hook/commands/config_commands.py +++ b/penify_hook/commands/config_commands.py @@ -8,6 +8,7 @@ from pathlib import Path from threading import Thread import logging +from penify_hook.utils import recursive_search_git_folder def get_penify_config() -> Path: @@ -16,8 +17,6 @@ def get_penify_config() -> Path: This function searches for the .penify file in the current directory and its parent directories until it finds it or reaches the home directory. """ - from penify_hook.utils import recursive_search_git_folder - current_dir = os.getcwd() home_dir = recursive_search_git_folder(current_dir) diff --git a/tests/test_config_commands.py b/tests/test_config_commands.py index 3fc93fd..5093505 100644 --- a/tests/test_config_commands.py +++ b/tests/test_config_commands.py @@ -15,7 +15,7 @@ class TestConfigCommands: - @patch('penify_hook.commands.config_commands.recursive_search_git_folder') + @patch('penify_hook.utils.recursive_search_git_folder') @patch('penify_hook.commands.config_commands.Path') @patch('os.makedirs') @patch('builtins.open', new_callable=mock_open) @@ -40,7 +40,7 @@ def test_get_penify_config_existing_dir(self, mock_file_open, mock_makedirs, moc mock_path_instance.__truediv__.assert_called_with('.penify') assert mock_makedirs.call_count == 0 # Should not create directory - @patch('penify_hook.commands.config_commands.recursive_search_git_folder') + @patch('penify_hook.utils.recursive_search_git_folder') @patch('penify_hook.commands.config_commands.Path') @patch('os.makedirs') @patch('builtins.open', new_callable=mock_open) From 8cbb5558ccd8c72279ae8cd539662ffc0cf1824c Mon Sep 17 00:00:00 2001 From: sumansaurabh Date: Sat, 8 Mar 2025 04:49:51 +0530 Subject: [PATCH 08/10] fix(tests): update patch path for recursive_search_git_folder in config_commands tests --- tests/test_config_commands.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_config_commands.py b/tests/test_config_commands.py index 5093505..ba84c10 100644 --- a/tests/test_config_commands.py +++ b/tests/test_config_commands.py @@ -15,7 +15,7 @@ class TestConfigCommands: - @patch('penify_hook.utils.recursive_search_git_folder') + @patch('penify_hook.commands.config_commands.recursive_search_git_folder') # Updated patch path here @patch('penify_hook.commands.config_commands.Path') @patch('os.makedirs') @patch('builtins.open', new_callable=mock_open) From f45ed0372143dc790e5943d172e6fb07004862e3 Mon Sep 17 00:00:00 2001 From: sumansaurabh Date: Sat, 8 Mar 2025 04:55:41 +0530 Subject: [PATCH 09/10] test(doc_commands): add unit tests for document generation commands and error handling --- tests/test_doc_commands.py | 182 +++++++++++++++++++++++++++++++++++++ 1 file changed, 182 insertions(+) create mode 100644 tests/test_doc_commands.py diff --git a/tests/test_doc_commands.py b/tests/test_doc_commands.py new file mode 100644 index 0000000..5d3eb81 --- /dev/null +++ b/tests/test_doc_commands.py @@ -0,0 +1,182 @@ +import unittest +from unittest.mock import patch, MagicMock, call +import sys +import os +from argparse import ArgumentParser +import tempfile + +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) + +from penify_hook.commands.doc_commands import ( + generate_doc, + setup_docgen_parser, + handle_docgen +) + + +class TestDocCommands(unittest.TestCase): + + @patch('penify_hook.commands.doc_commands.GitDocGenHook') + @patch('penify_hook.commands.doc_commands.FileAnalyzerGenHook') + @patch('penify_hook.commands.doc_commands.FolderAnalyzerGenHook') + @patch('penify_hook.commands.doc_commands.APIClient') + @patch('penify_hook.commands.doc_commands.os.getcwd') + def test_generate_doc_no_location(self, mock_getcwd, mock_api_client, + mock_folder_analyzer, mock_file_analyzer, + mock_git_analyzer): + # Setup + mock_api_instance = MagicMock() + mock_api_client.return_value = mock_api_instance + mock_getcwd.return_value = '/fake/current/dir' + mock_git_instance = MagicMock() + mock_git_analyzer.return_value = mock_git_instance + + # Call function + generate_doc('http://api.example.com', 'fake-token', None) + + # Assertions + mock_api_client.assert_called_once_with('http://api.example.com', 'fake-token') + mock_git_analyzer.assert_called_once_with('/fake/current/dir', mock_api_instance) + mock_git_instance.run.assert_called_once() + mock_file_analyzer.assert_not_called() + mock_folder_analyzer.assert_not_called() + + @patch('penify_hook.commands.doc_commands.GitDocGenHook') + @patch('penify_hook.commands.doc_commands.FileAnalyzerGenHook') + @patch('penify_hook.commands.doc_commands.FolderAnalyzerGenHook') + @patch('penify_hook.commands.doc_commands.APIClient') + def test_generate_doc_file_location(self, mock_api_client, mock_folder_analyzer, + mock_file_analyzer, mock_git_analyzer): + # Setup + mock_api_instance = MagicMock() + mock_api_client.return_value = mock_api_instance + mock_file_instance = MagicMock() + mock_file_analyzer.return_value = mock_file_instance + + # Call function + generate_doc('http://api.example.com', 'fake-token', 'example.py') + + # Assertions + mock_api_client.assert_called_once_with('http://api.example.com', 'fake-token') + mock_file_analyzer.assert_called_once_with('example.py', mock_api_instance) + mock_file_instance.run.assert_called_once() + mock_git_analyzer.assert_not_called() + mock_folder_analyzer.assert_not_called() + + @patch('penify_hook.commands.doc_commands.GitDocGenHook') + @patch('penify_hook.commands.doc_commands.FileAnalyzerGenHook') + @patch('penify_hook.commands.doc_commands.FolderAnalyzerGenHook') + @patch('penify_hook.commands.doc_commands.APIClient') + def test_generate_doc_folder_location(self, mock_api_client, mock_folder_analyzer, + mock_file_analyzer, mock_git_analyzer): + # Setup + mock_api_instance = MagicMock() + mock_api_client.return_value = mock_api_instance + mock_folder_instance = MagicMock() + mock_folder_analyzer.return_value = mock_folder_instance + + # Call function + generate_doc('http://api.example.com', 'fake-token', 'src') + + # Assertions + mock_api_client.assert_called_once_with('http://api.example.com', 'fake-token') + mock_folder_analyzer.assert_called_once_with('src', mock_api_instance) + mock_folder_instance.run.assert_called_once() + mock_git_analyzer.assert_not_called() + mock_file_analyzer.assert_not_called() + + @patch('sys.exit') + @patch('penify_hook.commands.doc_commands.GitDocGenHook') + @patch('penify_hook.commands.doc_commands.APIClient') + def test_generate_doc_error_handling(self, mock_api_client, mock_git_analyzer, mock_exit): + # Setup + mock_api_instance = MagicMock() + mock_api_client.return_value = mock_api_instance + mock_git_analyzer.side_effect = Exception("Test error") + + # Call function + generate_doc('http://api.example.com', 'fake-token', None) + + # Assertions + mock_exit.assert_called_once_with(1) + + def test_setup_docgen_parser(self): + parser = ArgumentParser() + setup_docgen_parser(parser) + + # Check that docgen options are properly set up + args = parser.parse_args(['-l', 'test_location']) + self.assertEqual(args.location, 'test_location') + + # Check install-hook subcommand + args = parser.parse_args(['install-hook', '-l', 'hook_location']) + self.assertEqual(args.docgen_subcommand, 'install-hook') + self.assertEqual(args.location, 'hook_location') + + # Check uninstall-hook subcommand + args = parser.parse_args(['uninstall-hook', '-l', 'hook_location']) + self.assertEqual(args.docgen_subcommand, 'uninstall-hook') + self.assertEqual(args.location, 'hook_location') + + @patch('penify_hook.commands.doc_commands.install_git_hook') + @patch('penify_hook.commands.doc_commands.uninstall_git_hook') + @patch('penify_hook.commands.doc_commands.generate_doc') + @patch('penify_hook.commands.doc_commands.get_token') + @patch('sys.exit') + def test_handle_docgen(self, mock_exit, mock_get_token, mock_generate_doc, + mock_uninstall_hook, mock_install_hook): + # Setup + mock_get_token.return_value = 'fake-token' + + # Test install-hook subcommand + args = MagicMock(docgen_subcommand='install-hook', location='hook_location') + handle_docgen(args) + mock_install_hook.assert_called_once_with('hook_location', 'fake-token') + mock_generate_doc.assert_not_called() + mock_uninstall_hook.assert_not_called() + + # Reset mocks + mock_install_hook.reset_mock() + mock_generate_doc.reset_mock() + + # Test uninstall-hook subcommand + args = MagicMock(docgen_subcommand='uninstall-hook', location='hook_location') + handle_docgen(args) + mock_uninstall_hook.assert_called_once_with('hook_location') + mock_generate_doc.assert_not_called() + mock_install_hook.assert_not_called() + + # Reset mocks + mock_uninstall_hook.reset_mock() + mock_generate_doc.reset_mock() + + # Test direct documentation generation + args = MagicMock(docgen_subcommand=None, location='doc_location') + handle_docgen(args) + mock_generate_doc.assert_called_once() + mock_install_hook.assert_not_called() + mock_uninstall_hook.assert_not_called() + + # Test with no token + mock_get_token.return_value = None + handle_docgen(args) + mock_exit.assert_called_once_with(1) + + @patch('penify_hook.commands.doc_commands.os.getcwd') + @patch('penify_hook.commands.doc_commands.APIClient') + def test_generate_doc_with_exception(self, mock_api_client, mock_getcwd): + # Setup + mock_api_client.side_effect = Exception("API error") + mock_getcwd.return_value = '/fake/current/dir' + + # Test file location with exception + with self.assertRaises(SystemExit): + generate_doc('http://api.example.com', 'fake-token', 'example.py') + + # Test folder location with exception + with self.assertRaises(SystemExit): + generate_doc('http://api.example.com', 'fake-token', 'src_folder') + + +if __name__ == '__main__': + unittest.main() From 116c1b72e1487f0a909870381fe9a95bc0aeaffb Mon Sep 17 00:00:00 2001 From: sumansaurabh Date: Sat, 8 Mar 2025 05:01:00 +0530 Subject: [PATCH 10/10] --- tests/test_doc_commands.py | 361 ++++++++++++++++++++----------------- 1 file changed, 193 insertions(+), 168 deletions(-) diff --git a/tests/test_doc_commands.py b/tests/test_doc_commands.py index 5d3eb81..06ad41c 100644 --- a/tests/test_doc_commands.py +++ b/tests/test_doc_commands.py @@ -1,9 +1,8 @@ -import unittest -from unittest.mock import patch, MagicMock, call +import pytest import sys import os from argparse import ArgumentParser -import tempfile +from unittest.mock import patch, MagicMock sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) @@ -14,169 +13,195 @@ ) -class TestDocCommands(unittest.TestCase): - - @patch('penify_hook.commands.doc_commands.GitDocGenHook') - @patch('penify_hook.commands.doc_commands.FileAnalyzerGenHook') - @patch('penify_hook.commands.doc_commands.FolderAnalyzerGenHook') - @patch('penify_hook.commands.doc_commands.APIClient') - @patch('penify_hook.commands.doc_commands.os.getcwd') - def test_generate_doc_no_location(self, mock_getcwd, mock_api_client, - mock_folder_analyzer, mock_file_analyzer, - mock_git_analyzer): - # Setup - mock_api_instance = MagicMock() - mock_api_client.return_value = mock_api_instance - mock_getcwd.return_value = '/fake/current/dir' - mock_git_instance = MagicMock() - mock_git_analyzer.return_value = mock_git_instance - - # Call function - generate_doc('http://api.example.com', 'fake-token', None) - - # Assertions - mock_api_client.assert_called_once_with('http://api.example.com', 'fake-token') - mock_git_analyzer.assert_called_once_with('/fake/current/dir', mock_api_instance) - mock_git_instance.run.assert_called_once() - mock_file_analyzer.assert_not_called() - mock_folder_analyzer.assert_not_called() - - @patch('penify_hook.commands.doc_commands.GitDocGenHook') - @patch('penify_hook.commands.doc_commands.FileAnalyzerGenHook') - @patch('penify_hook.commands.doc_commands.FolderAnalyzerGenHook') - @patch('penify_hook.commands.doc_commands.APIClient') - def test_generate_doc_file_location(self, mock_api_client, mock_folder_analyzer, - mock_file_analyzer, mock_git_analyzer): - # Setup - mock_api_instance = MagicMock() - mock_api_client.return_value = mock_api_instance - mock_file_instance = MagicMock() - mock_file_analyzer.return_value = mock_file_instance - - # Call function +@patch('penify_hook.commands.doc_commands.GitDocGenHook') +@patch('penify_hook.commands.doc_commands.FileAnalyzerGenHook') +@patch('penify_hook.commands.doc_commands.FolderAnalyzerGenHook') +@patch('penify_hook.commands.doc_commands.APIClient') +@patch('penify_hook.commands.doc_commands.os.getcwd') +def test_generate_doc_no_location(mock_getcwd, mock_api_client, + mock_folder_analyzer, mock_file_analyzer, + mock_git_analyzer): + # Setup + mock_api_instance = MagicMock() + mock_api_client.return_value = mock_api_instance + mock_getcwd.return_value = '/fake/current/dir' + mock_git_instance = MagicMock() + mock_git_analyzer.return_value = mock_git_instance + + # Call function + generate_doc('http://api.example.com', 'fake-token', None) + + # Assertions + mock_api_client.assert_called_once_with('http://api.example.com', 'fake-token') + mock_git_analyzer.assert_called_once_with('/fake/current/dir', mock_api_instance) + mock_git_instance.run.assert_called_once() + mock_file_analyzer.assert_not_called() + mock_folder_analyzer.assert_not_called() + + +@patch('penify_hook.commands.doc_commands.GitDocGenHook') +@patch('penify_hook.commands.doc_commands.FileAnalyzerGenHook') +@patch('penify_hook.commands.doc_commands.FolderAnalyzerGenHook') +@patch('penify_hook.commands.doc_commands.APIClient') +def test_generate_doc_file_location(mock_api_client, mock_folder_analyzer, + mock_file_analyzer, mock_git_analyzer): + # Setup + mock_api_instance = MagicMock() + mock_api_client.return_value = mock_api_instance + mock_file_instance = MagicMock() + mock_file_analyzer.return_value = mock_file_instance + + # Call function + generate_doc('http://api.example.com', 'fake-token', 'example.py') + + # Assertions + mock_api_client.assert_called_once_with('http://api.example.com', 'fake-token') + mock_file_analyzer.assert_called_once_with('example.py', mock_api_instance) + mock_file_instance.run.assert_called_once() + mock_git_analyzer.assert_not_called() + mock_folder_analyzer.assert_not_called() + + +@patch('penify_hook.commands.doc_commands.GitDocGenHook') +@patch('penify_hook.commands.doc_commands.FileAnalyzerGenHook') +@patch('penify_hook.commands.doc_commands.FolderAnalyzerGenHook') +@patch('penify_hook.commands.doc_commands.APIClient') +def test_generate_doc_folder_location(mock_api_client, mock_folder_analyzer, + mock_file_analyzer, mock_git_analyzer): + # Setup + mock_api_instance = MagicMock() + mock_api_client.return_value = mock_api_instance + mock_folder_instance = MagicMock() + mock_folder_analyzer.return_value = mock_folder_instance + + # Call function + generate_doc('http://api.example.com', 'fake-token', 'src') + + # Assertions + mock_api_client.assert_called_once_with('http://api.example.com', 'fake-token') + mock_folder_analyzer.assert_called_once_with('src', mock_api_instance) + mock_folder_instance.run.assert_called_once() + mock_git_analyzer.assert_not_called() + mock_file_analyzer.assert_not_called() + + +@patch('sys.exit') +@patch('penify_hook.commands.doc_commands.GitDocGenHook') +@patch('penify_hook.commands.doc_commands.APIClient') +def test_generate_doc_error_handling(mock_api_client, mock_git_analyzer, mock_exit): + # Setup + mock_api_instance = MagicMock() + mock_api_client.return_value = mock_api_instance + mock_git_analyzer.side_effect = Exception("Test error") + + # Call function + generate_doc('http://api.example.com', 'fake-token', None) + + # Assertions + mock_exit.assert_called_once_with(1) + + +def test_setup_docgen_parser(): + parser = ArgumentParser() + setup_docgen_parser(parser) + + # Check that docgen options are properly set up + args = parser.parse_args(['-l', 'test_location']) + assert args.location == 'test_location' + + # Check install-hook subcommand + args = parser.parse_args(['install-hook', '-l', 'hook_location']) + assert args.docgen_subcommand == 'install-hook' + assert args.location == 'hook_location' + + # Check uninstall-hook subcommand + args = parser.parse_args(['uninstall-hook', '-l', 'hook_location']) + assert args.docgen_subcommand == 'uninstall-hook' + assert args.location == 'hook_location' + + +@patch('penify_hook.commands.doc_commands.install_git_hook') +@patch('penify_hook.commands.doc_commands.uninstall_git_hook') +@patch('penify_hook.commands.doc_commands.generate_doc') +@patch('penify_hook.commands.doc_commands.get_token') +@patch('sys.exit') +def test_handle_docgen_install_hook(mock_exit, mock_get_token, mock_generate_doc, + mock_uninstall_hook, mock_install_hook): + # Setup + mock_get_token.return_value = 'fake-token' + + # Test install-hook subcommand + args = MagicMock(docgen_subcommand='install-hook', location='hook_location') + handle_docgen(args) + mock_install_hook.assert_called_once_with('hook_location', 'fake-token') + mock_generate_doc.assert_not_called() + mock_uninstall_hook.assert_not_called() + + +@patch('penify_hook.commands.doc_commands.install_git_hook') +@patch('penify_hook.commands.doc_commands.uninstall_git_hook') +@patch('penify_hook.commands.doc_commands.generate_doc') +@patch('penify_hook.commands.doc_commands.get_token') +@patch('sys.exit') +def test_handle_docgen_uninstall_hook(mock_exit, mock_get_token, mock_generate_doc, + mock_uninstall_hook, mock_install_hook): + # Setup + mock_get_token.return_value = 'fake-token' + + # Test uninstall-hook subcommand + args = MagicMock(docgen_subcommand='uninstall-hook', location='hook_location') + handle_docgen(args) + mock_uninstall_hook.assert_called_once_with('hook_location') + mock_generate_doc.assert_not_called() + mock_install_hook.assert_not_called() + + +@patch('penify_hook.commands.doc_commands.install_git_hook') +@patch('penify_hook.commands.doc_commands.uninstall_git_hook') +@patch('penify_hook.commands.doc_commands.generate_doc') +@patch('penify_hook.commands.doc_commands.get_token') +def test_handle_docgen_generate(mock_get_token, mock_generate_doc, + mock_uninstall_hook, mock_install_hook): + # Setup + mock_get_token.return_value = 'fake-token' + + # Test direct documentation generation + args = MagicMock(docgen_subcommand=None, location='doc_location') + handle_docgen(args) + mock_generate_doc.assert_called_once() + mock_install_hook.assert_not_called() + mock_uninstall_hook.assert_not_called() + + +@patch('penify_hook.commands.doc_commands.get_token') +@patch('sys.exit') +def test_handle_docgen_no_token(mock_exit, mock_get_token): + # Test with no token + mock_get_token.return_value = None + args = MagicMock(docgen_subcommand=None, location='doc_location') + handle_docgen(args) + mock_exit.assert_called_once_with(1) + + +@patch('penify_hook.commands.doc_commands.os.getcwd') +@patch('penify_hook.commands.doc_commands.APIClient') +def test_generate_doc_with_file_exception(mock_api_client, mock_getcwd): + # Setup + mock_api_client.side_effect = Exception("API error") + mock_getcwd.return_value = '/fake/current/dir' + + # Test file location with exception + with pytest.raises(SystemExit): generate_doc('http://api.example.com', 'fake-token', 'example.py') - - # Assertions - mock_api_client.assert_called_once_with('http://api.example.com', 'fake-token') - mock_file_analyzer.assert_called_once_with('example.py', mock_api_instance) - mock_file_instance.run.assert_called_once() - mock_git_analyzer.assert_not_called() - mock_folder_analyzer.assert_not_called() - - @patch('penify_hook.commands.doc_commands.GitDocGenHook') - @patch('penify_hook.commands.doc_commands.FileAnalyzerGenHook') - @patch('penify_hook.commands.doc_commands.FolderAnalyzerGenHook') - @patch('penify_hook.commands.doc_commands.APIClient') - def test_generate_doc_folder_location(self, mock_api_client, mock_folder_analyzer, - mock_file_analyzer, mock_git_analyzer): - # Setup - mock_api_instance = MagicMock() - mock_api_client.return_value = mock_api_instance - mock_folder_instance = MagicMock() - mock_folder_analyzer.return_value = mock_folder_instance - - # Call function - generate_doc('http://api.example.com', 'fake-token', 'src') - - # Assertions - mock_api_client.assert_called_once_with('http://api.example.com', 'fake-token') - mock_folder_analyzer.assert_called_once_with('src', mock_api_instance) - mock_folder_instance.run.assert_called_once() - mock_git_analyzer.assert_not_called() - mock_file_analyzer.assert_not_called() - - @patch('sys.exit') - @patch('penify_hook.commands.doc_commands.GitDocGenHook') - @patch('penify_hook.commands.doc_commands.APIClient') - def test_generate_doc_error_handling(self, mock_api_client, mock_git_analyzer, mock_exit): - # Setup - mock_api_instance = MagicMock() - mock_api_client.return_value = mock_api_instance - mock_git_analyzer.side_effect = Exception("Test error") - - # Call function - generate_doc('http://api.example.com', 'fake-token', None) - - # Assertions - mock_exit.assert_called_once_with(1) - - def test_setup_docgen_parser(self): - parser = ArgumentParser() - setup_docgen_parser(parser) - - # Check that docgen options are properly set up - args = parser.parse_args(['-l', 'test_location']) - self.assertEqual(args.location, 'test_location') - - # Check install-hook subcommand - args = parser.parse_args(['install-hook', '-l', 'hook_location']) - self.assertEqual(args.docgen_subcommand, 'install-hook') - self.assertEqual(args.location, 'hook_location') - - # Check uninstall-hook subcommand - args = parser.parse_args(['uninstall-hook', '-l', 'hook_location']) - self.assertEqual(args.docgen_subcommand, 'uninstall-hook') - self.assertEqual(args.location, 'hook_location') - - @patch('penify_hook.commands.doc_commands.install_git_hook') - @patch('penify_hook.commands.doc_commands.uninstall_git_hook') - @patch('penify_hook.commands.doc_commands.generate_doc') - @patch('penify_hook.commands.doc_commands.get_token') - @patch('sys.exit') - def test_handle_docgen(self, mock_exit, mock_get_token, mock_generate_doc, - mock_uninstall_hook, mock_install_hook): - # Setup - mock_get_token.return_value = 'fake-token' - - # Test install-hook subcommand - args = MagicMock(docgen_subcommand='install-hook', location='hook_location') - handle_docgen(args) - mock_install_hook.assert_called_once_with('hook_location', 'fake-token') - mock_generate_doc.assert_not_called() - mock_uninstall_hook.assert_not_called() - - # Reset mocks - mock_install_hook.reset_mock() - mock_generate_doc.reset_mock() - - # Test uninstall-hook subcommand - args = MagicMock(docgen_subcommand='uninstall-hook', location='hook_location') - handle_docgen(args) - mock_uninstall_hook.assert_called_once_with('hook_location') - mock_generate_doc.assert_not_called() - mock_install_hook.assert_not_called() - - # Reset mocks - mock_uninstall_hook.reset_mock() - mock_generate_doc.reset_mock() - - # Test direct documentation generation - args = MagicMock(docgen_subcommand=None, location='doc_location') - handle_docgen(args) - mock_generate_doc.assert_called_once() - mock_install_hook.assert_not_called() - mock_uninstall_hook.assert_not_called() - - # Test with no token - mock_get_token.return_value = None - handle_docgen(args) - mock_exit.assert_called_once_with(1) - - @patch('penify_hook.commands.doc_commands.os.getcwd') - @patch('penify_hook.commands.doc_commands.APIClient') - def test_generate_doc_with_exception(self, mock_api_client, mock_getcwd): - # Setup - mock_api_client.side_effect = Exception("API error") - mock_getcwd.return_value = '/fake/current/dir' - - # Test file location with exception - with self.assertRaises(SystemExit): - generate_doc('http://api.example.com', 'fake-token', 'example.py') - - # Test folder location with exception - with self.assertRaises(SystemExit): - generate_doc('http://api.example.com', 'fake-token', 'src_folder') - - -if __name__ == '__main__': - unittest.main() + + +@patch('penify_hook.commands.doc_commands.os.getcwd') +@patch('penify_hook.commands.doc_commands.APIClient') +def test_generate_doc_with_folder_exception(mock_api_client, mock_getcwd): + # Setup + mock_api_client.side_effect = Exception("API error") + mock_getcwd.return_value = '/fake/current/dir' + + # Test folder location with exception + with pytest.raises(SystemExit): + generate_doc('http://api.example.com', 'fake-token', 'src_folder')