diff --git a/unittest/run_tests.sh b/unittest/run_tests.sh index 4fa9e57384d0826ab3a5208708d275dcd4f84e70..85fe2e3aeee4acff398bbf0b8d40d28ec299bdb6 100755 --- a/unittest/run_tests.sh +++ b/unittest/run_tests.sh @@ -4,9 +4,10 @@ SCRIPT_PATH=$(readlink -f "$0") SCRIPT_DIR=$(dirname "$SCRIPT_PATH") PROJECT_ROOT="$SCRIPT_DIR/../oedp" +MCP_ROOT="$SCRIPT_DIR/../oedp-mcp" # 设置Python环境变量 -export PYTHONPATH="$PROJECT_ROOT:$PYTHONPATH" +export PYTHONPATH="$PROJECT_ROOT:$MCP_ROOT:$PYTHONPATH" # 执行测试用例 python3 -m coverage run -m pytest diff --git a/unittest/test_check_cmd.py b/unittest/test_check_cmd.py index afd0f90b037ad0b2740f418cfc0e6af2ea215a03..c99486724de3410f4b36a4359cd8fdb5911b0c97 100644 --- a/unittest/test_check_cmd.py +++ b/unittest/test_check_cmd.py @@ -3,26 +3,186 @@ # oeDeploy is licensed under the Mulan PSL v2. import unittest +import os +import tempfile +import shutil from unittest.mock import patch, MagicMock +import yaml + from src.commands.check.check_cmd import CheckCmd class TestCheckCmd(unittest.TestCase): def setUp(self): + """测试前准备""" self.project_path = "/fake/project" self.action = "install" - + self.temp_dir = tempfile.mkdtemp() + + # 创建测试项目结构 + self.test_project_dir = os.path.join(self.temp_dir, "test_project") + os.makedirs(self.test_project_dir) + os.makedirs(os.path.join(self.test_project_dir, "workspace")) + + # 创建config.yaml文件 + self.config_content = { + "name": "test_plugin", + "version": "1.0.0", + "description": "Test plugin for unit testing", + "all": { + "vars": { + "test_var": "test_value" + }, + "hosts": { + "localhost": { + "ansible_connection": "local" + } + } + } + } + with open(os.path.join(self.test_project_dir, "config.yaml"), 'w') as f: + yaml.dump(self.config_content, f) + + # 创建main.yaml文件 + self.main_content = { + "name": "test_plugin", + "version": "1.0.0", + "description": "Test plugin for unit testing", + "action": { + "install": { + "description": "Install the plugin", + "tasks": [ + { + "name": "test_task", + "playbook": "install.yml" + } + ] + } + } + } + with open(os.path.join(self.test_project_dir, "main.yaml"), 'w') as f: + yaml.dump(self.main_content, f) + + # 创建playbook文件 + playbook_content = { + "name": "Test playbook", + "hosts": "localhost", + "tasks": [ + { + "name": "Test task", + "debug": { + "msg": "Hello World" + } + } + ] + } + with open(os.path.join(self.test_project_dir, "workspace", "install.yml"), 'w') as f: + yaml.dump(playbook_content, f) + + def tearDown(self): + """测试后清理""" + shutil.rmtree(self.temp_dir) + def test_init(self): """测试CheckCmd初始化""" check_cmd = CheckCmd(self.project_path, self.action) self.assertEqual(check_cmd.project, self.project_path) self.assertEqual(check_cmd.action, self.action) self.assertIsNotNone(check_cmd.log) - - def test_run(self): - """测试run方法(当前为空实现)""" - check_cmd = CheckCmd(self.project_path, self.action) + + def test_run_basic(self): + """测试run方法基本功能""" + check_cmd = CheckCmd(self.test_project_dir, "install") result = check_cmd.run() # 当前实现为pass,返回None self.assertIsNone(result) + def test_run_with_invalid_project(self): + """测试run方法(无效的项目路径)""" + check_cmd = CheckCmd("/invalid/path", "install") + result = check_cmd.run() + self.assertIsNone(result) + + def test_run_with_missing_config(self): + """测试run方法(缺少配置文件)""" + # 删除config.yaml文件 + os.remove(os.path.join(self.test_project_dir, "config.yaml")) + + check_cmd = CheckCmd(self.test_project_dir, "install") + result = check_cmd.run() + self.assertIsNone(result) + + def test_run_with_missing_main(self): + """测试run方法(缺少main.yaml文件)""" + # 删除main.yaml文件 + os.remove(os.path.join(self.test_project_dir, "main.yaml")) + + check_cmd = CheckCmd(self.test_project_dir, "install") + result = check_cmd.run() + self.assertIsNone(result) + + def test_run_with_missing_workspace(self): + """测试run方法(缺少workspace目录)""" + # 删除workspace目录 + shutil.rmtree(os.path.join(self.test_project_dir, "workspace")) + + check_cmd = CheckCmd(self.test_project_dir, "install") + result = check_cmd.run() + self.assertIsNone(result) + + def test_run_with_invalid_action(self): + """测试run方法(无效的action)""" + check_cmd = CheckCmd(self.test_project_dir, "invalid_action") + result = check_cmd.run() + self.assertIsNone(result) + + @patch('src.commands.check.check_cmd.LoggerGenerator') + def test_logger_initialization(self, mock_logger_generator): + """测试日志生成器初始化""" + mock_logger = MagicMock() + mock_logger_generator.return_value.get_logger.return_value = mock_logger + + check_cmd = CheckCmd(self.project_path, self.action) + + mock_logger_generator.return_value.get_logger.assert_called_once_with('check_cmd') + self.assertEqual(check_cmd.log, mock_logger) + + def test_different_actions(self): + """测试不同的action参数""" + actions = ["install", "uninstall", "setup", "info"] + + for action in actions: + with self.subTest(action=action): + check_cmd = CheckCmd(self.test_project_dir, action) + result = check_cmd.run() + self.assertIsNone(result) + + def test_project_path_normalization(self): + """测试项目路径标准化""" + # 测试相对路径 + relative_path = "." + check_cmd = CheckCmd(relative_path, self.action) + self.assertEqual(check_cmd.project, relative_path) + + # 测试绝对路径 + absolute_path = os.path.abspath(self.test_project_dir) + check_cmd = CheckCmd(absolute_path, self.action) + self.assertEqual(check_cmd.project, absolute_path) + + def test_empty_project_path(self): + """测试空项目路径""" + check_cmd = CheckCmd("", self.action) + self.assertEqual(check_cmd.project, "") + result = check_cmd.run() + self.assertIsNone(result) + + def test_none_project_path(self): + """测试None项目路径""" + check_cmd = CheckCmd(None, self.action) + self.assertEqual(check_cmd.project, None) + result = check_cmd.run() + self.assertIsNone(result) + + +if __name__ == '__main__': + unittest.main() diff --git a/unittest/test_info_cmd.py b/unittest/test_info_cmd.py index 4ef1a7a0d8ba39f06b5e5facf9800b62dc14bee8..c16e6fa658185d2e5740a6f43eeac2029a00daa0 100644 --- a/unittest/test_info_cmd.py +++ b/unittest/test_info_cmd.py @@ -6,151 +6,299 @@ import unittest import os import tempfile import shutil -import yaml from unittest.mock import patch, MagicMock +import yaml + from src.commands.info.info_cmd import InfoCmd from src.exceptions.config_exception import ConfigException class TestInfoCmd(unittest.TestCase): def setUp(self): - # 创建临时项目目录 - self.test_dir = tempfile.mkdtemp() - self.project_path = os.path.join(self.test_dir, "test_project") - os.makedirs(self.project_path) - - # 创建有效的main.yaml - self.main_yaml = os.path.join(self.project_path, "main.yaml") - with open(self.main_yaml, 'w') as f: - yaml.dump({ - "name": "test_project", - "version": "1.0.0", - "description": "Test project description", - "action": { - "install": { - "description": "Install the application", - "tasks": [ - {"name": "setup", "playbook": "setup.yml"} - ] - }, - "uninstall": { - "description": "Uninstall the application", - "tasks": [ - {"name": "cleanup", "playbook": "cleanup.yml"} - ] - } + """测试前准备""" + self.temp_dir = tempfile.mkdtemp() + + # 创建测试项目结构 + self.test_project_dir = os.path.join(self.temp_dir, "test_project") + os.makedirs(self.test_project_dir) + + # 创建main.yaml文件 + self.main_content = { + "name": "test_plugin", + "version": "1.0.0", + "description": "Test plugin for unit testing", + "action": { + "install": { + "description": "Install the plugin" + }, + "uninstall": { + "description": "Uninstall the plugin" + }, + "setup": { + "description": "Setup the plugin configuration" } - }, f) - + } + } + with open(os.path.join(self.test_project_dir, "main.yaml"), 'w') as f: + yaml.dump(self.main_content, f) + def tearDown(self): - # 清理临时目录 - shutil.rmtree(self.test_dir) - + """测试后清理""" + shutil.rmtree(self.temp_dir) + def test_init(self): """测试InfoCmd初始化""" - info_cmd = InfoCmd(self.project_path) - self.assertEqual(info_cmd.project, self.project_path) + info_cmd = InfoCmd(self.test_project_dir) + self.assertEqual(info_cmd.project, self.test_project_dir) self.assertIsNotNone(info_cmd.log) - + def test_run_success(self): - """测试成功获取项目信息""" - info_cmd = InfoCmd(self.project_path) + """测试run方法(成功)""" + info_cmd = InfoCmd(self.test_project_dir) result = info_cmd.run() + self.assertTrue(result) - - def test_run_with_invalid_project(self): - """测试无效项目路径""" - info_cmd = InfoCmd("/nonexistent/project") + + def test_run_missing_main_file(self): + """测试run方法(缺少main.yaml文件)""" + # 删除main.yaml文件 + os.remove(os.path.join(self.test_project_dir, "main.yaml")) + + info_cmd = InfoCmd(self.test_project_dir) result = info_cmd.run() + self.assertFalse(result) - - def test_run_with_missing_main_yaml(self): - """测试缺少main.yaml文件""" - os.remove(self.main_yaml) - info_cmd = InfoCmd(self.project_path) + + def test_run_invalid_main_yaml(self): + """测试run方法(无效的main.yaml内容)""" + # 写入无效的YAML内容 + with open(os.path.join(self.test_project_dir, "main.yaml"), 'w') as f: + f.write("invalid yaml content: [") + + info_cmd = InfoCmd(self.test_project_dir) + with self.assertRaises(Exception): + info_cmd.run() + + def test_run_missing_project_directory(self): + """测试run方法(项目目录不存在)""" + info_cmd = InfoCmd("/invalid/path") result = info_cmd.run() + self.assertFalse(result) - - def test_run_with_invalid_main_yaml(self): - """测试无效的main.yaml文件""" - with open(self.main_yaml, 'w') as f: - f.write("invalid yaml content: [unclosed") - - info_cmd = InfoCmd(self.project_path) - # yaml解析错误会抛出异常,被MainReader捕获并记录日志 - # 但实际会在MainReader初始化时抛出异常而不是返回False - with self.assertRaises(Exception): + + def test_get_name_success(self): + """测试获取名称(成功)""" + info_cmd = InfoCmd(self.test_project_dir) + + with patch('src.commands.info.info_cmd.MainReader') as mock_main_reader: + mock_main = MagicMock() + mock_main.get_name.return_value = "test_plugin" + mock_main.get_version.return_value = "1.0.0" + mock_main.get_description.return_value = "Test description" + mock_main.get_action.return_value = { + "install": {"description": "Install"}, + "uninstall": {"description": "Uninstall"} + } + mock_main_reader.return_value = mock_main + result = info_cmd.run() - - def test_run_with_missing_fields(self): - """测试main.yaml缺少必要字段(实际代码能容错处理)""" - with open(self.main_yaml, 'w') as f: - yaml.dump({ - "name": "test_project" - # 缺少version, description, action等字段 - }, f) - - info_cmd = InfoCmd(self.project_path) - # 实际代码能够容错处理缺少的字段,返回空值 + + self.assertTrue(result) + mock_main.get_name.assert_called_once() + mock_main.get_version.assert_called_once() + mock_main.get_description.assert_called_once() + mock_main.get_action.assert_called_once() + + def test_get_name_missing(self): + """测试获取名称(名称缺失)""" + # 创建缺少name字段的main.yaml + main_content = { + "version": "1.0.0", + "description": "Test plugin", + "action": { + "install": {"description": "Install"} + } + } + with open(os.path.join(self.test_project_dir, "main.yaml"), 'w') as f: + yaml.dump(main_content, f) + + info_cmd = InfoCmd(self.test_project_dir) result = info_cmd.run() - self.assertTrue(result) - - def test_run_with_empty_action(self): - """测试action字段为空""" - with open(self.main_yaml, 'w') as f: - yaml.dump({ - "name": "test_project", - "version": "1.0.0", - "description": "Test project", - "action": {} - }, f) - - info_cmd = InfoCmd(self.project_path) + + self.assertFalse(result) + + def test_get_version_missing(self): + """测试获取版本(版本缺失)""" + # 创建缺少version字段的main.yaml + main_content = { + "name": "test_plugin", + "description": "Test plugin", + "action": { + "install": {"description": "Install"} + } + } + with open(os.path.join(self.test_project_dir, "main.yaml"), 'w') as f: + yaml.dump(main_content, f) + + info_cmd = InfoCmd(self.test_project_dir) result = info_cmd.run() - self.assertTrue(result) # 空action也应该能正常处理 - - def test_run_with_multiple_actions(self): - """测试多个action""" - with open(self.main_yaml, 'w') as f: - yaml.dump({ - "name": "test_project", - "version": "2.0.0", - "description": "Test project with multiple actions", - "action": { - "install": {"description": "Install"}, - "uninstall": {"description": "Uninstall"}, - "update": {"description": "Update"}, - "configure": {"description": "Configure"} - } - }, f) - info_cmd = InfoCmd(self.project_path) + self.assertTrue(result) # version是可选的,应该成功 + + def test_get_description_missing(self): + """测试获取描述(描述缺失)""" + # 创建缺少description字段的main.yaml + main_content = { + "name": "test_plugin", + "version": "1.0.0", + "action": { + "install": {"description": "Install"} + } + } + with open(os.path.join(self.test_project_dir, "main.yaml"), 'w') as f: + yaml.dump(main_content, f) + + info_cmd = InfoCmd(self.test_project_dir) result = info_cmd.run() - self.assertTrue(result) - - def test_run_with_action_no_description(self): - """测试action没有description字段""" - with open(self.main_yaml, 'w') as f: - yaml.dump({ - "name": "test_project", - "version": "1.0.0", - "description": "Test project", - "action": { - "install": { - "tasks": [{"name": "setup", "playbook": "setup.yml"}] - } - } - }, f) - info_cmd = InfoCmd(self.project_path) + self.assertTrue(result) # description是可选的,应该成功 + + def test_get_action_missing(self): + """测试获取操作(操作缺失)""" + # 创建缺少action字段的main.yaml + main_content = { + "name": "test_plugin", + "version": "1.0.0", + "description": "Test plugin" + } + with open(os.path.join(self.test_project_dir, "main.yaml"), 'w') as f: + yaml.dump(main_content, f) + + info_cmd = InfoCmd(self.test_project_dir) + result = info_cmd.run() + + self.assertTrue(result) # action是可选的,应该成功 + + def test_action_list_generation(self): + """测试操作列表生成""" + info_cmd = InfoCmd(self.test_project_dir) + + with patch('src.commands.info.info_cmd.MainReader') as mock_main_reader: + mock_main = MagicMock() + mock_main.get_name.return_value = "test_plugin" + mock_main.get_version.return_value = "1.0.0" + mock_main.get_description.return_value = "Test description" + mock_main.get_action.return_value = { + "install": {"description": "Install the plugin"}, + "uninstall": {"description": "Uninstall the plugin"}, + "setup": {"description": "Setup configuration"} + } + mock_main_reader.return_value = mock_main + + result = info_cmd.run() + + self.assertTrue(result) + + def test_empty_action_list(self): + """测试空操作列表""" + # 创建空action的main.yaml + main_content = { + "name": "test_plugin", + "version": "1.0.0", + "description": "Test plugin", + "action": {} + } + with open(os.path.join(self.test_project_dir, "main.yaml"), 'w') as f: + yaml.dump(main_content, f) + + info_cmd = InfoCmd(self.test_project_dir) result = info_cmd.run() + self.assertTrue(result) - - @patch('src.commands.info.info_cmd.MainReader') - def test_run_with_config_exception(self, mock_reader): - """测试MainReader抛出ConfigException""" - mock_reader.side_effect = ConfigException("Config error") - info_cmd = InfoCmd(self.project_path) + + def test_action_without_description(self): + """测试没有描述的操作""" + # 创建缺少操作描述的main.yaml + main_content = { + "name": "test_plugin", + "version": "1.0.0", + "description": "Test plugin", + "action": { + "install": {}, + "uninstall": {"description": "Uninstall"} + } + } + with open(os.path.join(self.test_project_dir, "main.yaml"), 'w') as f: + yaml.dump(main_content, f) + + info_cmd = InfoCmd(self.test_project_dir) result = info_cmd.run() - self.assertFalse(result) + + self.assertTrue(result) + + def test_config_exception_handling(self): + """测试配置异常处理""" + info_cmd = InfoCmd(self.test_project_dir) + + with patch('src.commands.info.info_cmd.MainReader') as mock_main_reader: + mock_main_reader.side_effect = ConfigException("Test exception") + + result = info_cmd.run() + + self.assertFalse(result) + + def test_general_exception_handling(self): + """测试通用异常处理""" + info_cmd = InfoCmd(self.test_project_dir) + + with patch('src.commands.info.info_cmd.MainReader') as mock_main_reader: + mock_main_reader.side_effect = Exception("Unexpected error") + + with self.assertRaises(Exception): + info_cmd.run() + + def test_table_formatting(self): + """测试表格格式化""" + info_cmd = InfoCmd(self.test_project_dir) + + with patch('src.commands.info.info_cmd.MainReader') as mock_main_reader: + mock_main = MagicMock() + mock_main.get_name.return_value = "test_plugin" + mock_main.get_version.return_value = "1.0.0" + mock_main.get_description.return_value = "Test description" + mock_main.get_action.return_value = { + "install": {"description": "Install the plugin with a very long description that might wrap in the table"}, + "uninstall": {"description": "Uninstall"} + } + mock_main_reader.return_value = mock_main + + result = info_cmd.run() + + self.assertTrue(result) + + def test_project_path_normalization(self): + """测试项目路径标准化""" + # 测试相对路径 + relative_path = "." + info_cmd = InfoCmd(relative_path) + self.assertEqual(info_cmd.project, relative_path) + + # 测试绝对路径 + absolute_path = os.path.abspath(self.test_project_dir) + info_cmd = InfoCmd(absolute_path) + self.assertEqual(info_cmd.project, absolute_path) + + @patch('src.commands.info.info_cmd.LoggerGenerator') + def test_logger_initialization(self, mock_logger_generator): + """测试日志生成器初始化""" + mock_logger = MagicMock() + mock_logger_generator.return_value.get_logger.return_value = mock_logger + + info_cmd = InfoCmd(self.test_project_dir) + + mock_logger_generator.return_value.get_logger.assert_called_once_with('info_cmd') + self.assertEqual(info_cmd.log, mock_logger) + +if __name__ == '__main__': + unittest.main() diff --git a/unittest/test_init_cmd.py b/unittest/test_init_cmd.py index 1df16eddbf0714ccf12e566d500639eaa1dd65a5..cdcd9fbb246e64587bad90da11e50d62aafd2530 100644 --- a/unittest/test_init_cmd.py +++ b/unittest/test_init_cmd.py @@ -6,277 +6,396 @@ import unittest import os import tempfile import shutil -import hashlib +import tarfile from unittest.mock import patch, MagicMock -import src.constants.paths +import yaml + from src.commands.init.init_cmd import InitCmd from src.constants.paths import PLUGIN_DIR, REPO_CACHE_DIR, REPO_CONFIG_PATH + class TestInitCmd(unittest.TestCase): def setUp(self): - # 创建临时目录 - self.test_dir = tempfile.mkdtemp() - self.plugin_dir = os.path.join(self.test_dir, "plugin") - os.makedirs(self.plugin_dir, exist_ok=True) - - # 创建测试插件压缩包 - self.test_plugin = os.path.join(self.plugin_dir, "test_plugin.tar.gz") - with open(self.test_plugin, 'wb') as f: - f.write(b'test data') - - # 备份原始路径常量 - self.original_plugin_dir = src.constants.paths.PLUGIN_DIR + """测试前准备""" + self.temp_dir = tempfile.mkdtemp() + + # 创建测试插件目录结构 + self.test_plugin_dir = os.path.join(self.temp_dir, "test_plugin") + os.makedirs(self.test_plugin_dir) + os.makedirs(os.path.join(self.test_plugin_dir, "workspace")) + + # 创建config.yaml文件 + self.config_content = { + "name": "test_plugin", + "version": "1.0.0", + "description": "Test plugin for unit testing" + } + with open(os.path.join(self.test_plugin_dir, "config.yaml"), 'w') as f: + yaml.dump(self.config_content, f) + + # 创建main.yaml文件 + self.main_content = { + "name": "test_plugin", + "version": "1.0.0", + "description": "Test plugin for unit testing", + "action": { + "install": {"description": "Install the plugin"}, + "uninstall": {"description": "Uninstall the plugin"} + } + } + with open(os.path.join(self.test_plugin_dir, "main.yaml"), 'w') as f: + yaml.dump(self.main_content, f) - # 使用patch模拟路径常量 - self.plugin_patcher = patch('src.constants.paths.PLUGIN_DIR', self.plugin_dir) - self.plugin_patcher.start() + # 创建压缩包 + self.tar_path = os.path.join(self.temp_dir, "test_plugin.tar.gz") + with tarfile.open(self.tar_path, 'w:gz') as tar: + tar.add(self.test_plugin_dir, arcname=os.path.basename(self.test_plugin_dir)) + # 创建目标目录 + self.target_dir = os.path.join(self.temp_dir, "target_project") + def tearDown(self): - # 清理临时目录 - shutil.rmtree(self.test_dir) - # 停止patch恢复原始路径 - self.plugin_patcher.stop() - - @patch('src.commands.init.init_cmd.CommandExecutor.run_single_cmd') - def test_init_with_local_archive(self, mock_cmd): - """测试使用本地压缩包初始化项目""" - mock_cmd.return_value = ("", "", 0) - - target_path = os.path.join(self.test_dir, "new_project") - init_cmd = InitCmd(self.test_plugin, target_path) - result = init_cmd.run() - - self.assertTrue(result) - self.assertTrue(os.path.exists(target_path)) - mock_cmd.assert_called_once() - - @patch('src.commands.init.init_cmd.InitCmd._download_with_retry') - @patch('src.commands.init.init_cmd.CommandExecutor.run_single_cmd') - def test_init_with_remote_archive(self, mock_extract, mock_download): - """测试使用远程URL初始化项目""" - mock_download.return_value = True - mock_extract.return_value = ("", "", 0) - - target_path = os.path.join(self.test_dir, "remote_project") - init_cmd = InitCmd("http://example.com/plugin.tar.gz", target_path) - result = init_cmd.run() - + """测试后清理""" + shutil.rmtree(self.temp_dir) + + def test_init(self): + """测试InitCmd初始化""" + init_cmd = InitCmd("test_plugin", self.target_dir) + self.assertEqual(init_cmd.plugin, "test_plugin") + self.assertEqual(init_cmd.project, self.target_dir) + self.assertEqual(init_cmd.parent_dir, "") + self.assertFalse(init_cmd.force) + self.assertIsNotNone(init_cmd.log) + + def test_init_with_parent_dir(self): + """测试InitCmd初始化(带父目录)""" + parent_dir = "/parent/dir" + init_cmd = InitCmd("test_plugin", "", parent_dir=parent_dir) + self.assertEqual(init_cmd.plugin, "test_plugin") + self.assertEqual(init_cmd.project, "") + self.assertEqual(init_cmd.parent_dir, parent_dir) + self.assertFalse(init_cmd.force) + + def test_init_with_force(self): + """测试InitCmd初始化(强制模式)""" + init_cmd = InitCmd("test_plugin", self.target_dir, force=True) + self.assertTrue(init_cmd.force) + + def test_determine_target_path_with_project(self): + """测试目标路径确定(指定项目路径)""" + init_cmd = InitCmd("test_plugin", self.target_dir) + result = init_cmd._determine_target_path() + self.assertEqual(result, os.path.abspath(self.target_dir)) + + def test_determine_target_path_with_parent_dir(self): + """测试目标路径确定(指定父目录)""" + parent_dir = self.temp_dir + init_cmd = InitCmd("test_plugin", "", parent_dir=parent_dir) + result = init_cmd._determine_target_path() + expected = os.path.abspath(os.path.join(parent_dir, "test_plugin")) + self.assertEqual(result, expected) + + def test_determine_target_path_with_tar_and_parent_dir(self): + """测试目标路径确定(压缩包和父目录)""" + parent_dir = self.temp_dir + init_cmd = InitCmd(self.tar_path, "", parent_dir=parent_dir) + result = init_cmd._determine_target_path() + expected = os.path.abspath(os.path.join(parent_dir, "test_plugin")) + self.assertEqual(result, expected) + + def test_determine_target_path_default(self): + """测试目标路径确定(默认情况)""" + init_cmd = InitCmd("test_plugin", "") + result = init_cmd._determine_target_path() + expected = os.path.abspath(os.path.join(os.getcwd(), "test_plugin")) + self.assertEqual(result, expected) + + def test_validate_target_path_valid(self): + """测试目标路径验证(有效路径)""" + init_cmd = InitCmd("test_plugin", self.target_dir) + result = init_cmd._validate_target_path("/valid/path/to/project") self.assertTrue(result) - self.assertTrue(os.path.exists(target_path)) - mock_download.assert_called_once() - - @patch('src.commands.init.init_cmd.InitCmd._find_plugin_in_repos') - def test_init_with_plugin_name(self, mock_find): - """测试使用插件名称初始化项目""" - mock_find.return_value = { - 'urls': ['http://example.com/plugin.tar.gz'], - 'sha256sum': '123456', - 'version': '1.0.0' - } - - target_path = os.path.join(self.test_dir, "named_project") - init_cmd = InitCmd("test_plugin", target_path) + + def test_validate_target_path_invalid(self): + """测试目标路径验证(无效路径)""" + init_cmd = InitCmd("test_plugin", self.target_dir) + result = init_cmd._validate_target_path("/") + self.assertFalse(result) + + def test_handle_existing_target_empty(self): + """测试处理已存在目标(空目录)""" + os.makedirs(self.target_dir) - with patch('src.commands.init.init_cmd.InitCmd._download_with_retry', return_value=True), \ - patch('src.commands.init.init_cmd.InitCmd._verify_checksum', return_value=True), \ - patch('src.commands.init.init_cmd.InitCmd._extract_archive', return_value=True): - result = init_cmd.run() + init_cmd = InitCmd("test_plugin", self.target_dir) + result = init_cmd._handle_existing_target(self.target_dir) self.assertTrue(result) - mock_find.assert_called_once() - - def test_init_with_invalid_path(self): - """测试使用无效路径初始化项目(路径层级小于2)""" - init_cmd = InitCmd(self.test_plugin, "/tmp") - result = init_cmd.run() - - self.assertFalse(result) - - @patch('src.commands.init.init_cmd.CommandExecutor.run_single_cmd') - def test_init_with_existing_dir(self, mock_cmd): - """测试目标目录已存在的情况""" - mock_cmd.return_value = ("", "", 0) - - target_path = os.path.join(self.test_dir, "existing_project") - os.makedirs(target_path) + + def test_handle_existing_target_non_empty_with_force(self): + """测试处理已存在目标(非空目录,强制模式)""" + os.makedirs(self.target_dir) + with open(os.path.join(self.target_dir, "test.txt"), 'w') as f: + f.write("test content") + + init_cmd = InitCmd("test_plugin", self.target_dir, force=True) + + with patch('src.commands.init.init_cmd.CommandExecutor') as mock_executor: + mock_executor.run_single_cmd.return_value = ("", "", 0) + result = init_cmd._handle_existing_target(self.target_dir) + + self.assertTrue(result) + + def test_handle_existing_target_non_empty_without_force(self): + """测试处理已存在目标(非空目录,非强制模式)""" + os.makedirs(self.target_dir) + with open(os.path.join(self.target_dir, "test.txt"), 'w') as f: + f.write("test content") - # 创建测试文件使目录非空 - with open(os.path.join(target_path, "testfile"), 'w') as f: - f.write("test") + init_cmd = InitCmd("test_plugin", self.target_dir, force=False) + result = init_cmd._handle_existing_target(self.target_dir) - # 测试不强制覆盖 - init_cmd = InitCmd(self.test_plugin, target_path, force=False) - result = init_cmd.run() self.assertFalse(result) + + def test_extract_archive_success(self): + """测试解压压缩包(成功)""" + init_cmd = InitCmd("test_plugin", self.target_dir) + + with patch('src.commands.init.init_cmd.CommandExecutor') as mock_executor: + mock_executor.run_single_cmd.return_value = ("", "", 0) + + result = init_cmd._extract_archive(self.tar_path, self.target_dir) + + self.assertTrue(result) + mock_executor.run_single_cmd.assert_called_once() + + def test_extract_archive_failure(self): + """测试解压压缩包(失败)""" + init_cmd = InitCmd("test_plugin", self.target_dir) + + with patch('src.commands.init.init_cmd.CommandExecutor') as mock_executor: + mock_executor.run_single_cmd.return_value = ("", "extraction error", 1) + + result = init_cmd._extract_archive(self.tar_path, self.target_dir) + + self.assertFalse(result) + + def test_extract_archive_exception(self): + """测试解压压缩包(异常)""" + init_cmd = InitCmd("test_plugin", self.target_dir) + + with patch('src.commands.init.init_cmd.CommandExecutor') as mock_executor: + mock_executor.run_single_cmd.side_effect = Exception("Unexpected error") + + result = init_cmd._extract_archive(self.tar_path, self.target_dir) + + self.assertFalse(result) + + @patch('src.commands.init.init_cmd.CommandExecutor') + def test_run_with_local_tar(self, mock_executor): + """测试运行(本地压缩包)""" + mock_executor.run_single_cmd.return_value = ("", "", 0) - # 测试强制覆盖 - init_cmd = InitCmd(self.test_plugin, target_path, force=True) - result = init_cmd.run() - self.assertTrue(result) - - @patch('src.commands.init.init_cmd.InitCmd._find_plugin_in_repos') - def test_init_with_missing_plugin(self, mock_find): - """测试插件不存在的情况""" - mock_find.return_value = None - - target_path = os.path.join(self.test_dir, "missing_plugin") - init_cmd = InitCmd("nonexistent_plugin", target_path) + init_cmd = InitCmd(self.tar_path, self.target_dir) result = init_cmd.run() - self.assertFalse(result) + self.assertTrue(result) - @patch('src.commands.init.init_cmd.InitCmd._verify_checksum') - def test_init_with_checksum_failure(self, mock_verify): - """测试校验和验证失败的情况""" - mock_verify.return_value = False - - target_path = os.path.join(self.test_dir, "checksum_fail") - init_cmd = InitCmd("test_plugin", target_path) + @patch('src.commands.init.init_cmd.CommandExecutor') + def test_run_with_url_tar(self, mock_executor): + """测试运行(URL压缩包)""" + mock_executor.run_single_cmd.return_value = ("", "", 0) - with patch('src.commands.init.init_cmd.InitCmd._find_plugin_in_repos') as mock_find: - mock_find.return_value = { - 'urls': ['http://example.com/plugin.tar.gz'], - 'sha256sum': '123456', - 'version': '1.0.0' - } - with patch('src.commands.init.init_cmd.InitCmd._download_with_retry', return_value=True): - result = init_cmd.run() + init_cmd = InitCmd("http://example.com/test.tar.gz", self.target_dir) + result = init_cmd.run() - self.assertFalse(result) + self.assertTrue(result) - @patch('src.commands.init.init_cmd.CommandExecutor.run_single_cmd') - def test_extract_failure(self, mock_cmd): - """测试解压失败处理""" - mock_cmd.return_value = ("", "extract error", 1) + @patch('src.commands.init.init_cmd.CommandExecutor') + def test_run_with_plugin_name(self, mock_executor): + """测试运行(插件名称)""" + # 创建仓库配置和缓存 + os.makedirs(REPO_CACHE_DIR, exist_ok=True) + repo_config = { + 'plugins': [ + { + 'test_plugin': [ + { + 'version': '1.0.0', + 'urls': ['http://example.com/test_plugin.tar.gz'], + 'sha256sum': 'test_checksum' + } + ] + } + ] + } + with open(os.path.join(REPO_CACHE_DIR, 'test_repo.yaml'), 'w') as f: + yaml.dump(repo_config, f) - target_path = os.path.join(self.test_dir, "extract_fail") - init_cmd = InitCmd(self.test_plugin, target_path) - result = init_cmd.run() + # 创建插件缓存目录 + os.makedirs(PLUGIN_DIR, exist_ok=True) - self.assertFalse(result) - - @patch('src.commands.init.init_cmd.os.makedirs') - def test_permission_denied(self, mock_makedirs): - """测试文件权限不足""" - mock_makedirs.side_effect = PermissionError("Permission denied") + mock_executor.run_single_cmd.return_value = ("", "", 0) - target_path = os.path.join(self.test_dir, "permission_test") - init_cmd = InitCmd("http://example.com/plugin.tar.gz", target_path) - result = init_cmd.run() + init_cmd = InitCmd("test_plugin", self.target_dir) - self.assertFalse(result) + with patch.object(init_cmd, '_verify_checksum', return_value=True): + result = init_cmd.run() + + self.assertTrue(result) - def test_determine_target_path_with_project(self): - """测试确定目标路径(指定project)""" - init_cmd = InitCmd("test_plugin.tar.gz", "/opt/my_project") - target = init_cmd._determine_target_path() - self.assertEqual(target, os.path.abspath("/opt/my_project")) - - def test_determine_target_path_with_parent_dir(self): - """测试确定目标路径(指定parent_dir)""" - init_cmd = InitCmd("test_plugin.tar.gz", "", parent_dir="/opt") - target = init_cmd._determine_target_path() - self.assertEqual(target, os.path.abspath("/opt/test_plugin")) - - def test_determine_target_path_default(self): - """测试确定目标路径(使用当前目录)""" - init_cmd = InitCmd("test_plugin.tar.gz", "") - target = init_cmd._determine_target_path() - expected = os.path.abspath(os.path.join(os.getcwd(), "test_plugin")) - self.assertEqual(target, expected) - def test_verify_checksum_success(self): - """测试校验和验证成功""" - test_file = os.path.join(self.test_dir, "test.txt") - with open(test_file, 'wb') as f: - f.write(b"test content") + """测试校验和验证(成功)""" + init_cmd = InitCmd("test_plugin", self.target_dir) + + # 创建测试文件 + test_file = os.path.join(self.temp_dir, "test.txt") + with open(test_file, 'w') as f: + f.write("test content") - # 计算实际的checksum + # 计算正确的校验和 + import hashlib sha256_hash = hashlib.sha256() with open(test_file, 'rb') as f: - sha256_hash.update(f.read()) + for byte_block in iter(lambda: f.read(4096), b""): + sha256_hash.update(byte_block) expected_checksum = sha256_hash.hexdigest() - init_cmd = InitCmd("test", "/opt/test") result = init_cmd._verify_checksum(test_file, expected_checksum) self.assertTrue(result) - + def test_verify_checksum_failure(self): - """测试校验和验证失败""" - test_file = os.path.join(self.test_dir, "test.txt") - with open(test_file, 'wb') as f: - f.write(b"test content") + """测试校验和验证(失败)""" + init_cmd = InitCmd("test_plugin", self.target_dir) - init_cmd = InitCmd("test", "/opt/test") - result = init_cmd._verify_checksum(test_file, "wrong_checksum") - self.assertFalse(result) - - @patch('src.commands.init.init_cmd.CommandExecutor.run_single_cmd') - def test_download_with_retry_success(self, mock_cmd): - """测试下载重试成功""" - mock_cmd.return_value = ("", "", 0) + # 创建测试文件 + test_file = os.path.join(self.temp_dir, "test.txt") + with open(test_file, 'w') as f: + f.write("test content") - init_cmd = InitCmd("test", "/opt/test") - output_file = os.path.join(self.test_dir, "download.tar.gz") - result = init_cmd._download_with_retry("http://example.com/test.tar.gz", output_file, "test") + result = init_cmd._verify_checksum(test_file, "invalid_checksum") + self.assertFalse(result) + + def test_verify_checksum_exception(self): + """测试校验和验证(异常)""" + init_cmd = InitCmd("test_plugin", self.target_dir) - self.assertTrue(result) - mock_cmd.assert_called_once() - - @patch('src.commands.init.init_cmd.CommandExecutor.run_single_cmd') - def test_download_with_retry_failure(self, mock_cmd): - """测试下载重试失败""" - mock_cmd.return_value = ("", "error", 1) + result = init_cmd._verify_checksum("/invalid/path", "checksum") + self.assertFalse(result) + + @patch('src.commands.init.init_cmd.CommandExecutor') + def test_download_with_retry_success(self, mock_executor): + """测试带重试下载(成功)""" + mock_executor.run_single_cmd.return_value = ("", "", 0) - init_cmd = InitCmd("test", "/opt/test") - output_file = os.path.join(self.test_dir, "download.tar.gz") - result = init_cmd._download_with_retry("http://example.com/test.tar.gz", output_file, "test", max_retries=2) + init_cmd = InitCmd("test_plugin", self.target_dir) - self.assertFalse(result) - self.assertEqual(mock_cmd.call_count, 2) - - @patch('src.commands.init.init_cmd.CommandExecutor.run_single_cmd') - def test_update_repo_success(self, mock_cmd): - """测试更新仓库成功""" - mock_cmd.return_value = ("", "", 0) + url = "http://example.com/file.tar.gz" + output_file = os.path.join(self.temp_dir, "downloaded.tar.gz") - init_cmd = InitCmd("test", "/opt/test") - result = init_cmd._update_repo() + result = init_cmd._download_with_retry(url, output_file, "test_repo") self.assertTrue(result) - - @patch('src.commands.init.init_cmd.CommandExecutor.run_single_cmd') - def test_update_repo_failure(self, mock_cmd): - """测试更新仓库失败""" - mock_cmd.return_value = ("", "error", 1) + + @patch('src.commands.init.init_cmd.CommandExecutor') + def test_download_with_retry_failure(self, mock_executor): + """测试带重试下载(失败)""" + mock_executor.run_single_cmd.return_value = ("", "download error", 1) - init_cmd = InitCmd("test", "/opt/test") - result = init_cmd._update_repo() + init_cmd = InitCmd("test_plugin", self.target_dir) + + url = "http://example.com/file.tar.gz" + output_file = os.path.join(self.temp_dir, "downloaded.tar.gz") + + result = init_cmd._download_with_retry(url, output_file, "test_repo") self.assertFalse(result) - - @patch('src.commands.init.init_cmd.InitCmd._update_repo') - def test_find_plugin_update_failed(self, mock_update): - """测试查找插件时更新仓库失败""" - mock_update.return_value = False + + def test_find_plugin_in_repos_success(self): + """测试在仓库中查找插件(成功)""" + # 创建仓库缓存 + os.makedirs(REPO_CACHE_DIR, exist_ok=True) + repo_config = { + 'plugins': [ + { + 'test_plugin': [ + { + 'version': '1.0.0', + 'urls': ['http://example.com/test_plugin.tar.gz'], + 'sha256sum': 'test_checksum' + } + ] + } + ] + } + with open(os.path.join(REPO_CACHE_DIR, 'test_repo.yaml'), 'w') as f: + yaml.dump(repo_config, f) - init_cmd = InitCmd("test_plugin", "/opt/test") - result = init_cmd._find_plugin_in_repos() + init_cmd = InitCmd("test_plugin", self.target_dir) - self.assertIsNone(result) - - @patch('src.commands.init.init_cmd.CommandExecutor.run_single_cmd') - def test_extract_with_strip_components(self, mock_cmd): - """测试解压时使用--strip-components=1参数""" - mock_cmd.return_value = ("", "", 0) + with patch.object(init_cmd, '_update_repo', return_value=True): + result = init_cmd._find_plugin_in_repos() + + self.assertIsNotNone(result) + self.assertEqual(result['version'], '1.0.0') + + def test_find_plugin_in_repos_not_found(self): + """测试在仓库中查找插件(未找到)""" + # 创建仓库缓存(不包含目标插件) + os.makedirs(REPO_CACHE_DIR, exist_ok=True) + repo_config = { + 'plugins': [ + { + 'other_plugin': [ + { + 'version': '1.0.0', + 'urls': ['http://example.com/other_plugin.tar.gz'], + 'sha256sum': 'test_checksum' + } + ] + } + ] + } + with open(os.path.join(REPO_CACHE_DIR, 'test_repo.yaml'), 'w') as f: + yaml.dump(repo_config, f) - init_cmd = InitCmd("test", "/opt/test") - target_path = os.path.join(self.test_dir, "extract_test") - os.makedirs(target_path) + init_cmd = InitCmd("test_plugin", self.target_dir) - result = init_cmd._extract_archive(self.test_plugin, target_path) + with patch.object(init_cmd, '_update_repo', return_value=True): + result = init_cmd._find_plugin_in_repos() + + self.assertIsNone(result) + + def test_find_plugin_in_repos_update_failed(self): + """测试在仓库中查找插件(更新失败)""" + init_cmd = InitCmd("test_plugin", self.target_dir) + + with patch.object(init_cmd, '_update_repo', return_value=False): + result = init_cmd._find_plugin_in_repos() + + self.assertIsNone(result) + + def test_exception_handling(self): + """测试异常处理""" + init_cmd = InitCmd("test_plugin", self.target_dir) - self.assertTrue(result) - args, _ = mock_cmd.call_args - cmd = args[0] - self.assertIn('--strip-components=1', cmd) + with patch.object(init_cmd, '_determine_target_path') as mock_method: + mock_method.side_effect = Exception("Test exception") + + result = init_cmd.run() + + self.assertFalse(result) + + @patch('src.commands.init.init_cmd.LoggerGenerator') + def test_logger_initialization(self, mock_logger_generator): + """测试日志生成器初始化""" + mock_logger = MagicMock() + mock_logger_generator.return_value.get_logger.return_value = mock_logger + + init_cmd = InitCmd("test_plugin", self.target_dir) + + mock_logger_generator.return_value.get_logger.assert_called_once_with('init_cmd') + self.assertEqual(init_cmd.log, mock_logger) + + +if __name__ == '__main__': + unittest.main()