test_external_api.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415
  1. #!/usr/bin/env python3
  2. """
  3. 外部API测试脚本
  4. 功能:
  5. 1. 测试5种标注类型的项目创建
  6. 2. 测试进度查询接口
  7. 3. 测试数据导出接口
  8. 4. 验证标签传入功能
  9. 使用方式:
  10. python scripts/test_external_api.py --base-url http://localhost:8003 --token <admin_token>
  11. 或使用环境变量:
  12. export API_BASE_URL=http://localhost:8003
  13. export ADMIN_TOKEN=your_token_here
  14. python scripts/test_external_api.py
  15. """
  16. import argparse
  17. import os
  18. import sys
  19. import json
  20. import requests
  21. from typing import Optional, Dict, Any, List
  22. from dataclasses import dataclass
  23. from datetime import datetime
  24. @dataclass
  25. class TestResult:
  26. """测试结果"""
  27. name: str
  28. success: bool
  29. message: str
  30. details: Optional[Dict] = None
  31. class ExternalAPITester:
  32. """外部API测试器"""
  33. def __init__(self, base_url: str, token: str):
  34. self.base_url = base_url.rstrip('/')
  35. self.token = token
  36. self.headers = {
  37. "Authorization": f"Bearer {token}",
  38. "Content-Type": "application/json"
  39. }
  40. self.results: List[TestResult] = []
  41. self.created_projects: List[str] = []
  42. def log(self, message: str):
  43. """打印日志"""
  44. print(f"[{datetime.now().strftime('%H:%M:%S')}] {message}")
  45. def add_result(self, name: str, success: bool, message: str, details: Optional[Dict] = None):
  46. """添加测试结果"""
  47. result = TestResult(name, success, message, details)
  48. self.results.append(result)
  49. status = "✓" if success else "✗"
  50. self.log(f"{status} {name}: {message}")
  51. if details and not success:
  52. self.log(f" 详情: {json.dumps(details, ensure_ascii=False, indent=2)}")
  53. def test_create_project(
  54. self,
  55. name: str,
  56. task_type: str,
  57. data: List[Dict],
  58. tags: Optional[List[Dict]] = None,
  59. description: str = ""
  60. ) -> Optional[str]:
  61. """
  62. 测试创建项目
  63. Returns:
  64. str: 项目ID,失败返回None
  65. """
  66. test_name = f"创建{task_type}项目"
  67. payload = {
  68. "name": name,
  69. "description": description,
  70. "task_type": task_type,
  71. "data": data,
  72. "external_id": f"test_{task_type}_{datetime.now().strftime('%Y%m%d%H%M%S')}"
  73. }
  74. if tags:
  75. payload["tags"] = tags
  76. try:
  77. response = requests.post(
  78. f"{self.base_url}/api/external/projects/init",
  79. json=payload,
  80. headers=self.headers,
  81. timeout=30
  82. )
  83. if response.status_code == 201:
  84. result = response.json()
  85. project_id = result.get("project_id")
  86. self.created_projects.append(project_id)
  87. # 验证标签是否正确应用
  88. if tags:
  89. config = result.get("config", "")
  90. tags_found = all(tag["tag"] in config for tag in tags)
  91. if tags_found:
  92. self.add_result(
  93. test_name,
  94. True,
  95. f"项目创建成功,ID: {project_id},标签已正确应用",
  96. {"project_id": project_id, "task_count": result.get("task_count")}
  97. )
  98. else:
  99. self.add_result(
  100. test_name,
  101. False,
  102. f"项目创建成功但标签未正确应用",
  103. {"project_id": project_id, "config": config[:200]}
  104. )
  105. else:
  106. self.add_result(
  107. test_name,
  108. True,
  109. f"项目创建成功,ID: {project_id}",
  110. {"project_id": project_id, "task_count": result.get("task_count")}
  111. )
  112. return project_id
  113. else:
  114. self.add_result(
  115. test_name,
  116. False,
  117. f"HTTP {response.status_code}",
  118. {"response": response.text[:500]}
  119. )
  120. return None
  121. except Exception as e:
  122. self.add_result(test_name, False, f"请求异常: {str(e)}")
  123. return None
  124. def test_get_progress(self, project_id: str) -> bool:
  125. """测试获取项目进度"""
  126. test_name = f"查询项目进度 ({project_id[:20]}...)"
  127. try:
  128. response = requests.get(
  129. f"{self.base_url}/api/external/projects/{project_id}/progress",
  130. headers=self.headers,
  131. timeout=30
  132. )
  133. if response.status_code == 200:
  134. result = response.json()
  135. self.add_result(
  136. test_name,
  137. True,
  138. f"进度查询成功,完成率: {result.get('completion_percentage', 0)}%",
  139. {
  140. "total_tasks": result.get("total_tasks"),
  141. "completed_tasks": result.get("completed_tasks"),
  142. "status": result.get("status")
  143. }
  144. )
  145. return True
  146. else:
  147. self.add_result(
  148. test_name,
  149. False,
  150. f"HTTP {response.status_code}",
  151. {"response": response.text[:500]}
  152. )
  153. return False
  154. except Exception as e:
  155. self.add_result(test_name, False, f"请求异常: {str(e)}")
  156. return False
  157. def test_export_project(self, project_id: str, format: str = "json") -> bool:
  158. """测试导出项目数据"""
  159. test_name = f"导出项目数据 ({project_id[:20]}..., {format}格式)"
  160. try:
  161. response = requests.post(
  162. f"{self.base_url}/api/external/projects/{project_id}/export",
  163. json={"format": format, "completed_only": False},
  164. headers=self.headers,
  165. timeout=60
  166. )
  167. if response.status_code == 200:
  168. result = response.json()
  169. self.add_result(
  170. test_name,
  171. True,
  172. f"导出成功,共{result.get('total_exported', 0)}条数据",
  173. {
  174. "file_url": result.get("file_url"),
  175. "file_name": result.get("file_name"),
  176. "file_size": result.get("file_size")
  177. }
  178. )
  179. return True
  180. else:
  181. self.add_result(
  182. test_name,
  183. False,
  184. f"HTTP {response.status_code}",
  185. {"response": response.text[:500]}
  186. )
  187. return False
  188. except Exception as e:
  189. self.add_result(test_name, False, f"请求异常: {str(e)}")
  190. return False
  191. def run_all_tests(self):
  192. """运行所有测试"""
  193. self.log("=" * 60)
  194. self.log("开始外部API测试")
  195. self.log("=" * 60)
  196. self.log(f"API地址: {self.base_url}")
  197. self.log("")
  198. # 测试数据
  199. text_data = [
  200. {"id": "text_1", "content": "这个产品非常好用,推荐购买!"},
  201. {"id": "text_2", "content": "质量太差了,不值这个价格"},
  202. {"id": "text_3", "content": "一般般,没什么特别的"}
  203. ]
  204. image_data = [
  205. {"id": "img_1", "content": "https://picsum.photos/id/40/800/600"},
  206. {"id": "img_2", "content": "https://picsum.photos/id/40/800/600"},
  207. {"id": "img_3", "content": "https://picsum.photos/id/40/800/600"}
  208. ]
  209. # 标签定义
  210. sentiment_tags = [
  211. {"tag": "正面", "color": "#4CAF50"},
  212. {"tag": "负面", "color": "#F44336"},
  213. {"tag": "中性", "color": "#9E9E9E"}
  214. ]
  215. animal_tags = [
  216. {"tag": "猫猫", "color": "#FF9800"},
  217. {"tag": "狗狗", "color": "#2196F3"},
  218. {"tag": "其他"} # 不指定颜色,测试自动生成
  219. ]
  220. object_tags = [
  221. {"tag": "人物", "color": "#E91E63"},
  222. {"tag": "车辆", "color": "#00BCD4"},
  223. {"tag": "建筑", "color": "#795548"}
  224. ]
  225. ner_tags = [
  226. {"tag": "人名", "color": "#9C27B0"},
  227. {"tag": "地名", "color": "#3F51B5"},
  228. {"tag": "组织", "color": "#009688"}
  229. ]
  230. polygon_tags = [
  231. {"tag": "区域A", "color": "#FF5722"},
  232. {"tag": "区域B", "color": "#607D8B"},
  233. {"tag": "区域C"} # 不指定颜色
  234. ]
  235. # 1. 测试文本分类项目
  236. self.log("\n--- 测试1: 文本分类项目 ---")
  237. project_id = self.test_create_project(
  238. name="测试-文本分类项目",
  239. task_type="text_classification",
  240. data=text_data,
  241. tags=sentiment_tags,
  242. description="情感分析测试项目"
  243. )
  244. if project_id:
  245. self.test_get_progress(project_id)
  246. self.test_export_project(project_id, "json")
  247. # 2. 测试图像分类项目
  248. self.log("\n--- 测试2: 图像分类项目 ---")
  249. project_id = self.test_create_project(
  250. name="测试-图像分类项目",
  251. task_type="image_classification",
  252. data=image_data,
  253. tags=animal_tags,
  254. description="动物分类测试项目"
  255. )
  256. if project_id:
  257. self.test_get_progress(project_id)
  258. self.test_export_project(project_id, "csv")
  259. # 3. 测试目标检测项目
  260. self.log("\n--- 测试3: 目标检测项目 ---")
  261. project_id = self.test_create_project(
  262. name="测试-目标检测项目",
  263. task_type="object_detection",
  264. data=image_data,
  265. tags=object_tags,
  266. description="目标检测测试项目"
  267. )
  268. if project_id:
  269. self.test_get_progress(project_id)
  270. self.test_export_project(project_id, "yolo")
  271. self.test_export_project(project_id, "coco")
  272. # 4. 测试NER项目
  273. self.log("\n--- 测试4: 命名实体识别项目 ---")
  274. ner_data = [
  275. {"id": "ner_1", "content": "张三在北京的阿里巴巴公司工作"},
  276. {"id": "ner_2", "content": "李四去了上海参加腾讯的面试"},
  277. {"id": "ner_3", "content": "王五是华为深圳总部的工程师"}
  278. ]
  279. project_id = self.test_create_project(
  280. name="测试-NER项目",
  281. task_type="ner",
  282. data=ner_data,
  283. tags=ner_tags,
  284. description="命名实体识别测试项目"
  285. )
  286. if project_id:
  287. self.test_get_progress(project_id)
  288. self.test_export_project(project_id, "json")
  289. # 5. 测试多边形标注项目
  290. self.log("\n--- 测试5: 多边形标注项目 ---")
  291. project_id = self.test_create_project(
  292. name="测试-多边形标注项目",
  293. task_type="polygon",
  294. data=image_data,
  295. tags=polygon_tags,
  296. description="多边形区域标注测试项目"
  297. )
  298. if project_id:
  299. self.test_get_progress(project_id)
  300. self.test_export_project(project_id, "coco")
  301. # 6. 测试不带标签的项目(验证默认行为)
  302. self.log("\n--- 测试6: 不带标签的项目 ---")
  303. project_id = self.test_create_project(
  304. name="测试-无标签项目",
  305. task_type="text_classification",
  306. data=text_data,
  307. tags=None, # 不传标签
  308. description="测试默认配置"
  309. )
  310. if project_id:
  311. self.test_get_progress(project_id)
  312. # 输出测试结果汇总
  313. self.print_summary()
  314. def print_summary(self):
  315. """打印测试结果汇总"""
  316. self.log("\n" + "=" * 60)
  317. self.log("测试结果汇总")
  318. self.log("=" * 60)
  319. total = len(self.results)
  320. passed = sum(1 for r in self.results if r.success)
  321. failed = total - passed
  322. self.log(f"总计: {total} 个测试")
  323. self.log(f"通过: {passed} 个")
  324. self.log(f"失败: {failed} 个")
  325. self.log(f"通过率: {(passed/total*100):.1f}%" if total > 0 else "N/A")
  326. if failed > 0:
  327. self.log("\n失败的测试:")
  328. for r in self.results:
  329. if not r.success:
  330. self.log(f" ✗ {r.name}: {r.message}")
  331. self.log("\n创建的项目ID:")
  332. for pid in self.created_projects:
  333. self.log(f" - {pid}")
  334. self.log("\n" + "=" * 60)
  335. # 返回退出码
  336. return 0 if failed == 0 else 1
  337. def main():
  338. parser = argparse.ArgumentParser(description="外部API测试脚本")
  339. parser.add_argument(
  340. "--base-url",
  341. default=os.environ.get("API_BASE_URL", "http://localhost:8003"),
  342. help="API基础URL (默认: http://localhost:8003)"
  343. )
  344. parser.add_argument(
  345. "--token",
  346. default=os.environ.get("ADMIN_TOKEN", ""),
  347. help="管理员Token"
  348. )
  349. args = parser.parse_args()
  350. if not args.token:
  351. print("错误: 请提供管理员Token")
  352. print("使用方式:")
  353. print(" python scripts/test_external_api.py --token <your_token>")
  354. print(" 或设置环境变量 ADMIN_TOKEN")
  355. sys.exit(1)
  356. tester = ExternalAPITester(args.base_url, args.token)
  357. exit_code = tester.run_all_tests()
  358. sys.exit(exit_code)
  359. if __name__ == "__main__":
  360. main()