scan_frontend_endpoints.py 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172
  1. # -*- coding: utf-8 -*-
  2. """
  3. 扫描 cfc-frontend,提取所有 request() 调用的 method+path,用于检测
  4. 「新增 / 变更需求」——与基线对比即可知道前端新增了哪些接口,从而自动补全用例。
  5. 输出(默认 endpoints 模式):每行 `METHOD /api/path`(排序去重),便于 comm 对比。
  6. python scan_frontend_endpoints.py --out endpoints.txt
  7. python scan_frontend_endpoints.py --mode cases --out extra.tsv # 直接产出冒烟用例 TSV
  8. """
  9. import os, re, sys, argparse
  10. HERE = os.path.dirname(os.path.abspath(__file__))
  11. FRONTEND = os.path.join(HERE, 'cfc-frontend')
  12. # 两种 request 调用风格:
  13. # 1) request('/api/x', 'POST', data)
  14. # 2) request({ url: BASE_URL + '/api/x', method: 'POST' })
  15. path_re_pos = re.compile(r"request\(\s*['\"](/api/[^'\"]+)['\"]\s*,\s*['\"](GET|POST|PUT|DELETE|PATCH)['\"]", re.I)
  16. path_re_obj = re.compile(r"url\s*:\s*(?:BASE_URL\s*\+\s*)?['\"](/api/[^'\"]+)['\"]")
  17. method_re_obj = re.compile(r"method\s*:\s*['\"](GET|POST|PUT|DELETE|PATCH)['\"]", re.I)
  18. SKIP_DIRS = ('node_modules', 'unpackage', '.git', 'dist', 'assets')
  19. def scan_file(path):
  20. out = set()
  21. try:
  22. with open(path, 'r', encoding='utf-8', errors='ignore') as f:
  23. text = f.read()
  24. except Exception:
  25. return out
  26. for m in path_re_pos.finditer(text):
  27. out.add((m.group(2).upper(), m.group(1)))
  28. for um in path_re_obj.finditer(text):
  29. p = um.group(1)
  30. window = text[um.end(): um.end() + 200]
  31. mm = method_re_obj.search(window)
  32. method = mm.group(1).upper() if mm else 'POST'
  33. out.add((method, p))
  34. return out
  35. def main():
  36. ap = argparse.ArgumentParser()
  37. ap.add_argument('--frontend', default=FRONTEND)
  38. ap.add_argument('--out')
  39. ap.add_argument('--mode', choices=['endpoints', 'cases'], default='endpoints')
  40. args = ap.parse_args()
  41. results = set()
  42. for root, dirs, files in os.walk(args.frontend):
  43. dirs[:] = [d for d in dirs if d not in SKIP_DIRS]
  44. for fn in files:
  45. if fn.endswith(('.js', '.vue')):
  46. results |= scan_file(os.path.join(root, fn))
  47. if args.mode == 'cases':
  48. # page fn method path auth param —— 自动补全的用例默认匿名、无路径参数
  49. lines = sorted(f"auto\tauto\t{m}\t{p}\tfalse\tfalse" for m, p in results)
  50. else:
  51. lines = sorted(f"{m} {p}" for m, p in results)
  52. text = "\n".join(lines) + ("\n" if lines else "")
  53. if args.out:
  54. with open(args.out, 'w', encoding='utf-8') as f:
  55. f.write(text)
  56. else:
  57. sys.stdout.write(text)
  58. if __name__ == '__main__':
  59. main()