Skip to main content
Glama
ferocknew

Fusion360 LLM Assistant

by ferocknew
test_base.py12.6 kB
""" 测试基类和公共测试工具 """ import unittest import asyncio import logging import time import sys import os from typing import Dict, Any, Optional # 添加项目根目录到路径,以便导入 src 代码 project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) sys.path.insert(0, project_root) # 导入真实的代码 from src.fusion360_mcp import tools # from addin.client import MCPClient # 已删除,不再需要 # 设置测试日志 logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) class AsyncTestCase(unittest.TestCase): """异步测试基类""" def setUp(self): """测试初始化""" self.loop = asyncio.new_event_loop() asyncio.set_event_loop(self.loop) def tearDown(self): """测试清理""" self.loop.close() def async_test(self, coro): """运行异步测试""" return self.loop.run_until_complete(coro) class Fusion360TestBase(AsyncTestCase): """Fusion 360 测试基类 - 使用真实的 src 代码""" def setUp(self): super().setUp() self.fusion360_url = "http://localhost:9000" # Fusion 360 插件HTTP服务地址 self.test_results = [] # 直接使用 src 中的工具模块 self.tools = tools # 注意:MCP 服务器本身没有端口,它是被动被调用的 # 我们只需要测试工具模块是否能正确调用 Fusion 360 插件 # MCPClient 已删除,不再需要 def create_mock_api_response(self, success: bool = True, data: Optional[Dict[str, Any]] = None) -> Dict[str, Any]: """创建模拟 API 响应""" return { "success": success, "result": data or {}, "error": None if success else "测试错误" } def assert_api_call_success(self, result: Dict[str, Any], expected_keys: list = None): """断言 API 调用成功""" self.assertTrue(result.get("success"), f"API 调用失败: {result.get('error')}") if expected_keys: for key in expected_keys: self.assertIn(key, result.get("result", {}), f"缺少期望的键: {key}") def assert_api_call_failure(self, result: Dict[str, Any], expected_error: str = None): """断言 API 调用失败""" self.assertFalse(result.get("success"), "期望 API 调用失败但实际成功") if expected_error: self.assertIn(expected_error, str(result.get("error", ""))) def log_test_result(self, test_name: str, result: Dict[str, Any]): """记录测试结果""" status = "✅ 成功" if result.get("success") else "❌ 失败" logger.info(f"{test_name}: {status}") self.test_results.append({ "test": test_name, "success": result.get("success"), "result": result }) async def check_fusion360_connection(self) -> bool: """检查 Fusion 360 插件HTTP服务连接""" try: # 直接检查 Fusion 360 插件的 HTTP 服务 api = self.tools.get_api() response = await api._request("GET", "/api/health") return response.get("status") == "healthy" except Exception as e: logger.warning(f"Fusion 360 插件连接检查失败: {e}") return False async def call_fusion360_api(self, method: str, endpoint: str, data: Optional[Dict[str, Any]] = None) -> Dict[str, Any]: """直接调用 Fusion 360 插件 API""" try: client = await self.get_client() url = f"{self.fusion360_url}{endpoint}" if method.upper() == "GET": response = await client.get(url) elif method.upper() == "POST": response = await client.post(url, json=data) elif method.upper() == "PUT": response = await client.put(url, json=data) elif method.upper() == "DELETE": response = await client.delete(url) else: raise ValueError(f"不支持的 HTTP 方法: {method}") response.raise_for_status() return response.json() except httpx.RequestError as e: logger.error(f"请求 Fusion 360 API 失败: {e}") return {"success": False, "error": f"网络错误: {e}"} except httpx.HTTPStatusError as e: logger.error(f"Fusion 360 API 返回错误: {e.response.status_code}") return {"success": False, "error": f"HTTP {e.response.status_code}"} except Exception as e: logger.error(f"调用 Fusion 360 API 异常: {e}") return {"success": False, "error": str(e)} async def call_real_tool(self, tool_name: str, **kwargs) -> Dict[str, Any]: """直接调用 src 中的真实工具函数""" try: # 根据工具名称调用对应的函数 if tool_name == "create_document": result = await self.tools.create_document( name=kwargs.get("name"), template=kwargs.get("template"), units=kwargs.get("units", "mm") ) elif tool_name == "create_object": result = await self.tools.create_object( object_type=kwargs.get("object_type"), parameters=kwargs.get("parameters", {}), position=kwargs.get("position"), rotation=kwargs.get("rotation") ) elif tool_name == "edit_object": result = await self.tools.edit_object( kwargs.get("object_id"), kwargs.get("parameters", {}) ) elif tool_name == "delete_object": result = await self.tools.delete_object(kwargs.get("object_id")) elif tool_name == "execute_code": result = await self.tools.execute_code( kwargs.get("code"), kwargs.get("context") ) elif tool_name == "insert_part_from_library": result = await self.tools.insert_part_from_library( kwargs.get("library_name"), kwargs.get("part_name"), kwargs.get("position") ) elif tool_name == "get_view": result = await self.tools.get_view( camera_position=kwargs.get("camera_position"), target_position=kwargs.get("target_position"), format=kwargs.get("format", "png"), width=kwargs.get("width", 1920), height=kwargs.get("height", 1080) ) elif tool_name == "get_objects": result = await self.tools.get_objects() elif tool_name == "get_object": result = await self.tools.get_object(kwargs.get("object_id")) elif tool_name == "get_parts_list": result = await self.tools.get_parts_list() else: return {"success": False, "error": f"未知的工具: {tool_name}"} return result except Exception as e: logger.error(f"调用真实工具 {tool_name} 失败: {e}") return {"success": False, "error": str(e)} # MCPClient 相关方法已删除 def print_test_summary(self): """打印测试摘要""" total = len(self.test_results) passed = sum(1 for r in self.test_results if r["success"]) failed = total - passed logger.info("=" * 50) logger.info(f"测试摘要: 总计 {total}, 通过 {passed}, 失败 {failed}") if failed > 0: logger.info("失败的测试:") for result in self.test_results: if not result["success"]: logger.info(f" ❌ {result['test']}: {result['result'].get('error')}") def tearDown(self): """测试清理""" # 这里可以添加其他清理逻辑 super().tearDown() class MockFusion360API: """模拟 Fusion 360 API""" def __init__(self): self.documents = [] self.objects = [] self.parts = [ {"library": "标准件", "name": "螺栓M6x20", "category": "紧固件"}, {"library": "标准件", "name": "螺母M6", "category": "紧固件"}, {"library": "标准件", "name": "垫圈6", "category": "紧固件"} ] self.call_history = [] async def create_document(self, name: str = None, template: str = None, units: str = "mm") -> Dict[str, Any]: """模拟创建文档""" self.call_history.append(("create_document", {"name": name, "template": template, "units": units})) doc_id = f"doc_{len(self.documents) + 1}" document = { "id": doc_id, "name": name or "新建文档", "template": template, "units": units } self.documents.append(document) return { "success": True, "result": { "document_id": doc_id, "name": document["name"], "units": units } } async def create_object(self, object_type: str, parameters: Dict[str, Any], position: list = None, rotation: list = None) -> Dict[str, Any]: """模拟创建对象""" self.call_history.append(("create_object", { "object_type": object_type, "parameters": parameters, "position": position, "rotation": rotation })) obj_id = f"obj_{len(self.objects) + 1}" obj = { "id": obj_id, "type": object_type, "parameters": parameters, "position": position or [0, 0, 0], "rotation": rotation or [0, 0, 0] } self.objects.append(obj) return { "success": True, "result": { "object_id": obj_id, "type": object_type, "parameters": parameters } } async def get_objects(self) -> Dict[str, Any]: """模拟获取对象列表""" self.call_history.append(("get_objects", {})) return { "success": True, "result": { "objects": [ { "id": obj["id"], "name": f"对象_{obj['id']}", "type": obj["type"], "visible": True } for obj in self.objects ] } } async def get_object(self, object_id: str) -> Dict[str, Any]: """模拟获取特定对象""" self.call_history.append(("get_object", {"object_id": object_id})) obj = next((o for o in self.objects if o["id"] == object_id), None) if not obj: return { "success": False, "error": f"对象 {object_id} 不存在" } return { "success": True, "result": { "object": { "id": obj["id"], "name": f"对象_{obj['id']}", "type": obj["type"], "parameters": obj["parameters"] } } } async def delete_object(self, object_id: str) -> Dict[str, Any]: """模拟删除对象""" self.call_history.append(("delete_object", {"object_id": object_id})) obj_index = next((i for i, o in enumerate(self.objects) if o["id"] == object_id), None) if obj_index is None: return { "success": False, "error": f"对象 {object_id} 不存在" } deleted_obj = self.objects.pop(obj_index) return { "success": True, "result": { "deleted_object_id": object_id, "message": f"对象 {object_id} 已删除" } } async def get_parts_list(self) -> Dict[str, Any]: """模拟获取零件列表""" self.call_history.append(("get_parts_list", {})) return { "success": True, "result": { "parts": self.parts } } def reset(self): """重置模拟 API 状态""" self.documents.clear() self.objects.clear() self.call_history.clear()

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/ferocknew/fusion360_mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server