Skip to main content
Glama
Roland0511

MCP Feishu Project Manager

by Roland0511
test_server.py13.7 kB
#!/usr/bin/env python # -*- coding: utf-8 -*- """ 飞书项目MCP服务器测试 """ import unittest # 使用绝对导入而不是相对导入,以便在 unittest discover 时正确导入 from src.mcp_server.server import get_view_list, get_view_detail, get_view_detail_by_name, get_work_item_detail, get_work_item_type_meta, get_flow_roles class TestFSProjMCPServer(unittest.TestCase): """测试飞书项目MCP服务器工具函数""" def test_get_view_list(self): """测试获取视图列表功能""" print("\n===== 测试 get_view_list =====") try: view_list = get_view_list("story") self.assertIsNotNone(view_list, "视图列表不应为None") self.assertTrue(len(view_list) > 0, "视图列表不应为空") print(f"获取到视图列表,共 {len(view_list)} 个视图") except Exception as e: self.fail(f"测试 get_view_list 失败: {str(e)}") def test_get_view_detail(self): """测试获取视图详情功能""" print("\n===== 测试 get_view_detail =====") try: # 首先获取视图列表,然后使用第一个视图的ID进行测试 view_list = get_view_list("story") self.assertIsNotNone(view_list, "视图列表不应为None") self.assertTrue(len(view_list) > 0, "视图列表不应为空") first_view = view_list[0] self.assertIn("view_id", first_view, "视图对象中应包含view_id字段") first_view_id = first_view["view_id"] print(f"使用视图ID: {first_view_id}") view_detail = get_view_detail(first_view_id) self.assertIsNotNone(view_detail, "视图详情不应为None") self.assertIn("view_id", view_detail, "视图详情中应包含view_id字段") self.assertEqual(view_detail["view_id"], first_view_id, "返回的视图ID应与请求的一致") print(f"获取到视图详情: {view_detail}") except Exception as e: self.fail(f"测试 get_view_detail 失败: {str(e)}") def test_get_work_item_detail(self): """测试获取工作项详情功能""" print("\n===== 测试 get_work_item_detail =====") try: # 首先获取视图列表 view_list = get_view_list("story") self.assertIsNotNone(view_list, "视图列表不应为None") self.assertTrue(len(view_list) > 0, "视图列表不应为空") # 遍历视图列表,寻找包含工作项的视图 work_item_ids = [] view_detail = None view_id = None for view in view_list: view_id = view["view_id"] print(f"尝试使用视图ID: {view_id}") view_detail = get_view_detail(view_id) self.assertIsNotNone(view_detail, "视图详情不应为None") # 从视图详情中获取工作项ID work_item_ids = view_detail.get("work_item_id_list", []) if work_item_ids: print(f"找到包含工作项的视图: {view_id}") break else: print(f"视图 {view_id} 中没有工作项,尝试下一个视图") # 如果所有视图都没有工作项,则跳过测试 if not work_item_ids: print("所有视图中都没有工作项,跳过测试") return # 获取前两个工作项的ID(如果有的话) work_item_ids = [str(id) for id in work_item_ids[:2]] if not work_item_ids: print("无法获取工作项ID,跳过测试") return work_item_ids_str = ",".join(work_item_ids) print(f"使用工作项ID: {work_item_ids_str}") # 调用get_work_item_detail函数 work_item_details = get_work_item_detail("story", work_item_ids_str) self.assertIsNotNone(work_item_details, "工作项详情不应为None") # 验证返回的工作项数量与请求的ID数量一致 self.assertEqual(len(work_item_details), len(work_item_ids), f"返回的工作项数量({len(work_item_details)})应与请求的ID数量({len(work_item_ids)})一致") # 验证每个工作项的ID与请求的ID一致 for detail in work_item_details: self.assertIn("id", detail, "工作项详情中应包含id字段") self.assertIn(str(detail["id"]), work_item_ids, f"返回的工作项ID {detail['id']} 应在请求的ID列表中") print(f"获取到工作项详情,共 {len(work_item_details)} 个工作项") except Exception as e: self.fail(f"测试 get_work_item_detail 失败: {str(e)}") def test_get_view_detail_by_name(self): """测试通过视图名称获取视图详情功能""" print("\n===== 测试 get_view_detail_by_name =====") try: # 首先获取视图列表 work_item_type = "story" view_list = get_view_list(work_item_type) self.assertIsNotNone(view_list, "视图列表不应为None") self.assertTrue(len(view_list) > 0, "视图列表不应为空") # 获取第一个视图的名称 first_view = view_list[0] self.assertIn("name", first_view, "视图对象中应包含name字段") first_view_name = first_view["name"] first_view_id = first_view["view_id"] print(f"使用视图名称: {first_view_name}, 对应ID: {first_view_id}") # 使用视图名称获取视图详情 view_detail_by_name = get_view_detail_by_name(first_view_name, work_item_type) self.assertIsNotNone(view_detail_by_name, "通过名称获取的视图详情不应为None") # 直接使用视图ID获取视图详情,用于比较 view_detail_by_id = get_view_detail(first_view_id) self.assertIsNotNone(view_detail_by_id, "通过ID获取的视图详情不应为None") # 验证两种方式获取的视图详情是否一致 self.assertEqual(view_detail_by_name.get("view_id"), view_detail_by_id.get("view_id"), "通过名称和ID获取的视图ID应一致") print(f"通过名称获取到视图详情: {view_detail_by_name}") # 测试不存在的视图名称 non_existent_view_name = "不存在的视图名称" + str(hash(first_view_name)) empty_view_detail = get_view_detail_by_name(non_existent_view_name, work_item_type) self.assertEqual(empty_view_detail, {}, "对于不存在的视图名称,应返回空字典") print(f"测试不存在的视图名称: {non_existent_view_name}, 返回: {empty_view_detail}") # 测试分页参数 page_num = 1 page_size = 5 paged_view_detail = get_view_detail_by_name(first_view_name, work_item_type, page_num, page_size) self.assertIsNotNone(paged_view_detail, "带分页参数的视图详情不应为None") print(f"使用分页参数 (page_num={page_num}, page_size={page_size}) 获取视图详情成功") except Exception as e: self.fail(f"测试 get_view_detail_by_name 失败: {str(e)}") def test_get_work_item_type_meta(self): """测试获取工作项类型元数据功能""" print("\n===== 测试 get_work_item_type_meta =====") try: # 测试获取需求类型的元数据 work_item_type_meta = get_work_item_type_meta("story") self.assertIsNotNone(work_item_type_meta, "工作项类型元数据不应为None") # 验证元数据是一个列表 self.assertIsInstance(work_item_type_meta, list, "工作项类型元数据应为列表类型") self.assertTrue(len(work_item_type_meta) > 0, "工作项类型元数据列表不应为空") # 验证列表中的元素包含必要的字段 first_field = work_item_type_meta[0] self.assertIsInstance(first_field, dict, "字段元素应为字典类型") self.assertIn("field_name", first_field, "字段元素中应包含field_name") self.assertIn("field_key", first_field, "字段元素中应包含field_key") print(f"获取到工作项类型元数据,包含 {len(work_item_type_meta)} 个字段") # 测试获取缺陷类型的元数据 issue_meta = get_work_item_type_meta("issue") self.assertIsNotNone(issue_meta, "缺陷类型元数据不应为None") self.assertIsInstance(issue_meta, list, "缺陷类型元数据应为列表类型") self.assertTrue(len(issue_meta) > 0, "缺陷类型元数据列表不应为空") print(f"获取到缺陷类型元数据,包含 {len(issue_meta)} 个字段") # 测试获取版本类型的元数据 version_meta = get_work_item_type_meta("version") self.assertIsNotNone(version_meta, "版本类型元数据不应为None") self.assertIsInstance(version_meta, list, "版本类型元数据应为列表类型") self.assertTrue(len(version_meta) > 0, "版本类型元数据列表不应为空") print(f"获取到版本类型元数据,包含 {len(version_meta)} 个字段") except Exception as e: self.fail(f"测试 get_work_item_type_meta 失败: {str(e)}") def test_get_flow_roles(self): """测试获取流程角色配置详情功能""" print("\n===== 测试 get_flow_roles =====") try: # 测试获取需求类型的流程角色配置 flow_roles = get_flow_roles("story") self.assertIsNotNone(flow_roles, "流程角色配置详情不应为None") # 验证返回的数据结构 if isinstance(flow_roles, list): # 如果返回的是列表,验证列表不为空 self.assertTrue(len(flow_roles) > 0, "流程角色配置列表不应为空") # 验证列表中的元素包含必要的字段 first_role = flow_roles[0] self.assertIsInstance(first_role, dict, "角色元素应为字典类型") self.assertIn("id", first_role, "角色元素中应包含id字段") self.assertIn("name", first_role, "角色元素中应包含name字段") # 打印角色信息 print(f"获取到流程角色配置,共 {len(flow_roles)} 个角色") for role in flow_roles: role_name = role.get("name", "未知角色") role_id = role.get("id", "未知ID") print(f"角色: {role_name}, ID: {role_id}") # 检查一些常见的角色ID是否存在 role_ids = [role.get("id") for role in flow_roles] expected_ids = ["plan", "QA", "frontend", "backend"] for expected_id in expected_ids: if expected_id in role_ids: print(f"找到预期的角色ID: {expected_id}") else: print(f"未找到预期的角色ID: {expected_id}") elif isinstance(flow_roles, dict): # 如果返回的是字典,打印字典的键 print(f"获取到流程角色配置,包含以下键: {', '.join(flow_roles.keys())}") self.fail("流程角色配置应为列表类型,而不是字典类型") else: print(f"获取到流程角色配置,类型: {type(flow_roles)}") self.fail(f"流程角色配置应为列表类型,而不是 {type(flow_roles)}") # 测试获取缺陷类型的流程角色配置 issue_flow_roles = get_flow_roles("issue") self.assertIsNotNone(issue_flow_roles, "缺陷类型流程角色配置不应为None") self.assertIsInstance(issue_flow_roles, list, "缺陷类型流程角色配置应为列表类型") self.assertTrue(len(issue_flow_roles) > 0, "缺陷类型流程角色配置列表不应为空") print(f"获取到缺陷类型流程角色配置成功,共 {len(issue_flow_roles)} 个角色") # 测试获取版本类型的流程角色配置 version_flow_roles = get_flow_roles("version") self.assertIsNotNone(version_flow_roles, "版本类型流程角色配置不应为None") self.assertIsInstance(version_flow_roles, list, "版本类型流程角色配置应为列表类型") self.assertTrue(len(version_flow_roles) > 0, "版本类型流程角色配置列表不应为空") print(f"获取到版本类型流程角色配置成功,共 {len(version_flow_roles)} 个角色") except Exception as e: self.fail(f"测试 get_flow_roles 失败: {str(e)}") if __name__ == "__main__": unittest.main()

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/Roland0511/mcp-feishu-proj'

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