From bca19655079b27356263861ace127b7cdeb2093a Mon Sep 17 00:00:00 2001 From: VIRTUALGUARD Date: Sat, 12 Jul 2025 14:01:18 +0800 Subject: [PATCH] [Feat] Add the function which can remove existed service from services.json --- README.md | 4 +- main.py | 13 +++++ src/manager.py | 75 +++++++++++++++++++++++++++- test/test_manager.py | 113 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 202 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 4512afc..f32c042 100644 --- a/README.md +++ b/README.md @@ -49,4 +49,6 @@ ## TODO -- [ ] Add the function that can remove services +- [x] Add the function that can remove services + +- [ ] Package src as a module diff --git a/main.py b/main.py index b0ff1c1..ccb9da4 100755 --- a/main.py +++ b/main.py @@ -23,6 +23,10 @@ def main(): operate_parser = subparsers.add_parser("operate", help="Service operations to carry out") operate_parser.add_argument("index", type=int, help="Index of the service, which is a integer") + # 移除服务命令 + remove_parser = subparsers.add_parser("remove", help="Remove a service") + remove_parser.add_argument("index", type=int, help="Index of the service to remove") + args = parser.parse_args() # 初始化仓库和管理器 @@ -51,6 +55,15 @@ def main(): print("Error: invalid index") except Exception as e: print(f"Operation failed: {str(e)}") + + elif args.command == "remove": + try: + manager.remove_service(args.index) + print(f"Service at index {args.index} removed successfully") + except IndexError: + print("Error: invalid index") + except Exception as e: + print(f"Remove failed: {str(e)}") if __name__ == "__main__": main() diff --git a/src/manager.py b/src/manager.py index f843b2b..7a9948c 100755 --- a/src/manager.py +++ b/src/manager.py @@ -63,7 +63,7 @@ class ServiceRepository: def __init__(self, file_path: str = 'data/services.json'): self.file_path = file_path - # 确保目录存在 + # Make sure the path has existed dir_path = os.path.dirname(file_path) if dir_path: os.makedirs(dir_path, exist_ok=True) @@ -100,6 +100,47 @@ class ServiceRepository: return [] logger.info("Service repository file not found, starting fresh") return [] + + def remove(self, service_name: str) -> None: + """Remove a service by name. + + Args: + service_name: Name of the service to remove + + Raises: + ValueError: If service not found + """ + try: + # Load all servives + services = self.load_all() + + # Search for matching services (case insensitive) + original_count = len(services) + services = [s for s in services + if s['name'].strip().lower() != service_name.strip().lower()] + + # Check and remove + if len(services) == original_count: + raise ValueError(f"Service '{service_name}' not found") + + # Update services,json + with open(self.file_path, 'w') as file: + json.dump(services, file, indent=4) + + logger.info(f"Removed service: {service_name}") + + except FileNotFoundError: + logger.warning("Services file not found, nothing to remove") + raise ValueError("Services file does not exist") + except json.JSONDecodeError: + logger.error("Invalid JSON format in services file") + raise ValueError("Invalid services data format") + except PermissionError: + logger.error("Permission denied when writing services file") + raise + except Exception as e: + logger.error(f"Error removing service: {str(e)}") + raise class Manager: @@ -151,7 +192,16 @@ class Manager: logger.error(f"Service registration failed: {str(e)}") raise - # TODO: Add the function that can remove services + def remove_service(self, service_name: str) -> None: + """Remove a service by name. + + Args: + service_name: Name of the service to remove + + Raises: + ValueError: If service not found + """ + self.repository.remove(service_name) def list_services(self) -> List[Service]: """List all registered services. @@ -192,3 +242,24 @@ class Manager: service = services[index] service.service_operation() + +if __name__ == "__main__": + # 测试服务移除功能 + manager = Manager() + try: + # 注册测试服务 + test_service = manager.register_service("sys", "test-service") + print("Registered test service") + + # 移除测试服务 + manager.remove_service("test-service") + print("Successfully removed test service") + + # 尝试移除不存在的服务 + try: + manager.remove_service("non-existent") + except ValueError as e: + print(f"Expected error: {str(e)}") + + except Exception as e: + print(f"Test failed: {str(e)}") diff --git a/test/test_manager.py b/test/test_manager.py index 4d2ff62..1cb9afa 100644 --- a/test/test_manager.py +++ b/test/test_manager.py @@ -2,6 +2,7 @@ import unittest from unittest.mock import patch, MagicMock from src.manager import ServiceFactory, ServiceRepository, Manager import os +import json import tempfile class TestServiceFactory(unittest.TestCase): @@ -59,6 +60,65 @@ class TestServiceRepository(unittest.TestCase): services = self.repo.load_all() self.assertEqual(len(services), 0) + + def test_remove_service_success(self): + """测试正常移除服务""" + # 添加测试服务 + mock_service = MagicMock() + mock_service.to_dict.return_value = { + "tag": "sys", + "name": "nginx", + "path": None + } + self.repo.save(mock_service) + + # 移除服务 + self.repo.remove("nginx") + + # 验证服务已被移除 + services = self.repo.load_all() + self.assertEqual(len(services), 0) + + def test_remove_service_case_insensitive(self): + """测试大小写不敏感移除""" + # 添加测试服务 + mock_service = MagicMock() + mock_service.to_dict.return_value = { + "tag": "sys", + "name": "Nginx", + "path": None + } + self.repo.save(mock_service) + + # 使用小写名称移除 + self.repo.remove("nginx") + + # 验证服务已被移除 + services = self.repo.load_all() + self.assertEqual(len(services), 0) + + def test_remove_service_whitespace(self): + """测试移除带空格的服务名""" + # 添加测试服务 + mock_service = MagicMock() + mock_service.to_dict.return_value = { + "tag": "sys", + "name": " nginx ", + "path": None + } + self.repo.save(mock_service) + + # 使用带空格名称移除 + self.repo.remove(" nginx ") + + # 验证服务已被移除 + services = self.repo.load_all() + self.assertEqual(len(services), 0) + + def test_remove_nonexistent_service(self): + """测试移除不存在的服务""" + with self.assertRaises(ValueError): + self.repo.remove("nonexistent") class TestManager(unittest.TestCase): @patch("src.manager.ServiceRepository") @@ -130,6 +190,59 @@ class TestManager(unittest.TestCase): with self.assertRaises(ValueError): manager.register_service("invalid", "invalid_service") + @patch("src.manager.ServiceRepository") + def test_remove_service_success(self, mock_repo): + """测试正常移除服务""" + manager = Manager(mock_repo.return_value) + service_name = "nginx" + + # 调用remove_service + manager.remove_service(service_name) + + # 验证ServiceRepository的remove方法被正确调用 + mock_repo.return_value.remove.assert_called_once_with(service_name) + + @patch("src.manager.ServiceRepository") + def test_remove_service_case_insensitive(self, mock_repo): + """测试大小写不敏感移除""" + manager = Manager(mock_repo.return_value) + + # 使用混合大小写 + manager.remove_service("NgInX") + + # 验证调用时使用原始大小写 + mock_repo.return_value.remove.assert_called_once_with("NgInX") + + @patch("src.manager.ServiceRepository") + def test_remove_service_nonexistent(self, mock_repo): + """测试移除不存在的服务""" + # 设置remove方法抛出ValueError + mock_repo.return_value.remove.side_effect = ValueError("Service not found") + manager = Manager(mock_repo.return_value) + + with self.assertRaises(ValueError): + manager.remove_service("nonexistent") + + @patch("src.manager.ServiceRepository") + def test_remove_service_file_not_found(self, mock_repo): + """测试文件不存在时的错误处理""" + # 设置remove方法抛出FileNotFoundError + mock_repo.return_value.remove.side_effect = FileNotFoundError + manager = Manager(mock_repo.return_value) + + with self.assertRaises(FileNotFoundError): + manager.remove_service("nginx") + + @patch("src.manager.ServiceRepository") + def test_remove_service_invalid_json(self, mock_repo): + """测试无效JSON文件时的异常处理""" + # 设置remove方法抛出JSONDecodeError + mock_repo.return_value.remove.side_effect = json.JSONDecodeError("Expecting value", "", 0) + manager = Manager(mock_repo.return_value) + + with self.assertRaises(json.JSONDecodeError): + manager.remove_service("nginx") + @patch("src.manager.ServiceRepository") @patch("src.manager.logger") def test_execute_service_operation_invalid_index(self, mock_logger, mock_repo):