diff --git a/ChangeLog b/ChangeLog index 2bff13e..dc2f91a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,9 @@ +* 2.0.1 +- Added unit tests for custom_config.py hook script. +- Added unit tests for cleanup_config.py hook script. +- Verified project tests pass by isolating from client_libs collection errors. +- Fixed linting errors in new test files. + * 2.0.0 - Hierachical context file for conversions troubleshooting. - Added Conversion Troubleshooting & Diagnostics functionality (api_examples/collect_conversions_troubleshooting_data.py). @@ -18,7 +24,6 @@ - Changed name of setup files to install and provided an uninstall procedure. - Added additional rules for GAQL edge cases to GEMINI.md. - Added command conversions_support_data. ->>>>>>> v1.6.0 * 1.5.0 - Added rigorous GAQL validation rules to GEMINI.md diff --git a/tests/test_cleanup_config.py b/tests/test_cleanup_config.py new file mode 100644 index 0000000..b55285f --- /dev/null +++ b/tests/test_cleanup_config.py @@ -0,0 +1,61 @@ +import sys +import os +import unittest +from unittest.mock import patch + +# Add the project root to sys.path +script_dir = os.path.dirname(os.path.abspath(__file__)) +project_root = os.path.abspath(os.path.join(script_dir, "..")) +hooks_dir = os.path.join(project_root, ".gemini/hooks") +sys.path.append(hooks_dir) + +import cleanup_config # noqa: E402 + +class TestCleanupConfig(unittest.TestCase): + + @patch("os.path.exists") + @patch("os.listdir") + @patch("os.path.isfile") + @patch("os.path.isdir") + @patch("os.unlink") + @patch("shutil.rmtree") + def test_cleanup_success(self, mock_rmtree, mock_unlink, mock_isdir, mock_isfile, mock_listdir, mock_exists): + # Setup mocks + mock_exists.return_value = True + mock_listdir.return_value = ["file1.txt", "dir1", ".gitkeep"] + + # Define side effects for isfile and isdir + def is_file_side_effect(path): + return "file1.txt" in path + def is_dir_side_effect(path): + return "dir1" in path + + mock_isfile.side_effect = is_file_side_effect + mock_isdir.side_effect = is_dir_side_effect + + cleanup_config.cleanup() + + # Verify calls + mock_unlink.assert_called_once() + self.assertIn("file1.txt", mock_unlink.call_args[0][0]) + + mock_rmtree.assert_called_once() + self.assertIn("dir1", mock_rmtree.call_args[0][0]) + + # Verify .gitkeep was NOT touched + for call in mock_unlink.call_args_list: + self.assertNotIn(".gitkeep", call[0][0]) + + @patch("os.path.exists") + def test_cleanup_no_config_dir(self, mock_exists): + mock_exists.return_value = False + with patch("sys.stderr") as mock_stderr: + cleanup_config.cleanup() + mock_stderr.write.assert_called() + # Should not call listdir if it doesn't exist + with patch("os.listdir") as mock_listdir: + cleanup_config.cleanup() + mock_listdir.assert_not_called() + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_custom_config.py b/tests/test_custom_config.py new file mode 100644 index 0000000..6ded6bf --- /dev/null +++ b/tests/test_custom_config.py @@ -0,0 +1,97 @@ +import sys +import os +import unittest +from unittest.mock import patch, MagicMock, mock_open + +# Add the project root to sys.path so we can import the hook scripts +script_dir = os.path.dirname(os.path.abspath(__file__)) +project_root = os.path.abspath(os.path.join(script_dir, "..")) +hooks_dir = os.path.join(project_root, ".gemini/hooks") +sys.path.append(hooks_dir) + +import custom_config # noqa: E402 + +class TestCustomConfig(unittest.TestCase): + + def test_get_version_success(self): + with patch("subprocess.run") as mocked_run: + mocked_run.return_value = MagicMock(stdout="2.1.0\n", check=True) + version = custom_config.get_version("dummy_script.py") + self.assertEqual(version, "2.1.0") + mocked_run.assert_called_once() + + def test_get_version_failure(self): + with patch("subprocess.run") as mocked_run: + mocked_run.side_effect = Exception("failed") + version = custom_config.get_version("dummy_script.py") + self.assertEqual(version, "2.0.0") # Fallback + + def test_parse_ruby_config(self): + content = """ + c.developer_token = 'token123' + c.client_id = "id456" + c.client_secret = 'secret789' + """ + with patch("builtins.open", mock_open(read_data=content)): + data = custom_config.parse_ruby_config("dummy.rb") + self.assertEqual(data["developer_token"], "token123") + self.assertEqual(data["client_id"], "id456") + self.assertEqual(data["client_secret"], "secret789") + + def test_parse_ini_config(self): + content = "[DEFAULT]\ndeveloper_token = token123\nclient_id = 'id456'\n" + with patch("builtins.open", mock_open(read_data=content)): + data = custom_config.parse_ini_config("dummy.ini") + self.assertEqual(data["developer_token"], "token123") + self.assertEqual(data["client_id"], "id456") + + def test_parse_properties_config(self): + content = "api.googleads.developerToken=token123\napi.googleads.clientId=id456\n" + with patch("builtins.open", mock_open(read_data=content)): + data = custom_config.parse_properties_config("dummy.properties") + self.assertEqual(data["developer_token"], "token123") + self.assertEqual(data["client_id"], "id456") + + def test_write_yaml_config_oauth2(self): + data = { + "developer_token": "token123", + "client_id": "id456", + "client_secret": "secret789", + "refresh_token": "refresh000" + } + with patch("builtins.open", mock_open()) as mocked_file: + success = custom_config.write_yaml_config(data, "dummy.yaml", "2.1.0") + self.assertTrue(success) + handle = mocked_file() + handle.write.assert_any_call("developer_token: token123\n") + handle.write.assert_any_call("client_id: id456\n") + handle.write.assert_any_call("gaada: \"2.1.0\"\n") + + def test_write_yaml_config_service_account(self): + data = { + "developer_token": "token123", + "json_key_file_path": "/path/to/key.json", + "impersonated_email": "user@example.com" + } + with patch("builtins.open", mock_open()) as mocked_file: + success = custom_config.write_yaml_config(data, "dummy.yaml", "2.1.0") + self.assertTrue(success) + handle = mocked_file() + handle.write.assert_any_call("json_key_file_path: /path/to/key.json\n") + handle.write.assert_any_call("impersonated_email: user@example.com\n") + # Verify client_id is NOT written + for call in handle.write.call_args_list: + self.assertNotIn("client_id:", call[0][0]) + + def test_configure_language(self): + with patch("os.path.exists", return_value=True), \ + patch("shutil.copy2") as mocked_copy, \ + patch("builtins.open", mock_open()) as mocked_file: + success = custom_config.configure_language("Python", "home.yaml", "target.yaml", "2.1.0", is_python=True) + self.assertTrue(success) + mocked_copy.assert_called_once_with("home.yaml", "target.yaml") + handle = mocked_file() + handle.write.assert_called_with('\ngaada: "2.1.0"\n') + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_service_account_config.py b/tests/test_service_account_config.py new file mode 100644 index 0000000..3da2779 --- /dev/null +++ b/tests/test_service_account_config.py @@ -0,0 +1,68 @@ +import sys +import os +import unittest +from unittest.mock import patch, MagicMock + +# Add .gemini/hooks to sys.path +script_dir = os.path.dirname(os.path.abspath(__file__)) +hooks_dir = os.path.join(script_dir, "../.gemini/hooks") +sys.path.append(hooks_dir) + +import custom_config + +class TestCustomConfig(unittest.TestCase): + + def test_parse_ruby_config_service_account(self): + content = """ + GoogleAds::Config.new do |c| + c.developer_token = 'TEST_TOKEN' + c.json_key_file_path = '/path/to/key.json' + c.impersonated_email = 'user@example.com' + end + """ + with patch("builtins.open", unittest.mock.mock_open(read_data=content)): + data = custom_config.parse_ruby_config("dummy.rb") + self.assertEqual(data["json_key_file_path"], "/path/to/key.json") + self.assertEqual(data["impersonated_email"], "user@example.com") + + def test_parse_ini_config_service_account(self): + content = """ +[GOOGLE_ADS] +developer_token = "TEST_TOKEN" +json_key_file_path = "/path/to/key.json" +impersonated_email = "user@example.com" + """ + with patch("builtins.open", unittest.mock.mock_open(read_data=content)): + data = custom_config.parse_ini_config("dummy.ini") + self.assertEqual(data["json_key_file_path"], "/path/to/key.json") + self.assertEqual(data["impersonated_email"], "user@example.com") + + def test_parse_properties_config_service_account(self): + content = """ +api.googleads.developerToken=TEST_TOKEN +api.googleads.oAuth2SecretsJsonPath=/path/to/key.json +api.googleads.oAuth2PrnEmail=user@example.com + """ + with patch("builtins.open", unittest.mock.mock_open(read_data=content)): + data = custom_config.parse_properties_config("dummy.properties") + self.assertEqual(data["json_key_file_path"], "/path/to/key.json") + self.assertEqual(data["impersonated_email"], "user@example.com") + + def test_write_yaml_config_service_account(self): + data = { + "developer_token": "TEST_TOKEN", + "json_key_file_path": "/path/to/key.json", + "impersonated_email": "user@example.com" + } + with patch("builtins.open", unittest.mock.mock_open()) as mocked_file: + custom_config.write_yaml_config(data, "dummy.yaml", "2.0.0") + mocked_file.assert_called_once_with("dummy.yaml", "w") + handle = mocked_file() + # Verify json_key_file_path is written + handle.write.assert_any_call("json_key_file_path: /path/to/key.json\n") + # Verify client_id is NOT written + for call in handle.write.call_args_list: + self.assertNotIn("client_id:", call[0][0]) + +if __name__ == "__main__": + unittest.main()