|
@@ -1172,6 +1172,7 @@ def main():
|
|
|
ap.add_argument('--report', default='', help='结果报告输出文件')
|
|
ap.add_argument('--report', default='', help='结果报告输出文件')
|
|
|
ap.add_argument('--extra', default='', help='额外用例文件 TSV(page fn method path auth param),追加到 CASES,用于新需求/变更需求补全测试')
|
|
ap.add_argument('--extra', default='', help='额外用例文件 TSV(page fn method path auth param),追加到 CASES,用于新需求/变更需求补全测试')
|
|
|
ap.add_argument('--only-extra', action='store_true', help='仅运行 --extra 用例(跳过内置全量用例),用于聚焦业务级重测')
|
|
ap.add_argument('--only-extra', action='store_true', help='仅运行 --extra 用例(跳过内置全量用例),用于聚焦业务级重测')
|
|
|
|
|
+ ap.add_argument('--body-map', default='', help='真实请求体映射 TSV(method<TAB>path<TAB>json),命中则覆盖用例默认空体,用于破除空体冒烟天花板')
|
|
|
args = ap.parse_args()
|
|
args = ap.parse_args()
|
|
|
|
|
|
|
|
# 加载额外用例(新需求/变更需求补全):追加到 CASES 后再跑
|
|
# 加载额外用例(新需求/变更需求补全):追加到 CASES 后再跑
|
|
@@ -1196,6 +1197,29 @@ def main():
|
|
|
CASES[:] = extra_cases
|
|
CASES[:] = extra_cases
|
|
|
print(f'[only-extra] 仅运行 {len(CASES)} 条额外用例')
|
|
print(f'[only-extra] 仅运行 {len(CASES)} 条额外用例')
|
|
|
|
|
|
|
|
|
|
+ # 加载真实请求体映射(用于破除空体冒烟的天花板)
|
|
|
|
|
+ body_map = None
|
|
|
|
|
+ if args.body_map and os.path.exists(args.body_map):
|
|
|
|
|
+ body_map = {}
|
|
|
|
|
+ _bm = 0
|
|
|
|
|
+ with open(args.body_map, 'r', encoding='utf-8') as _bf:
|
|
|
|
|
+ for _line in _bf:
|
|
|
|
|
+ _line = _line.rstrip('\n')
|
|
|
|
|
+ if not _line or _line.startswith('method'):
|
|
|
|
|
+ continue
|
|
|
|
|
+ _p = _line.split('\t')
|
|
|
|
|
+ if len(_p) < 3:
|
|
|
|
|
+ continue
|
|
|
|
|
+ _m, _pt, _j = _p[0], _p[1], '\t'.join(_p[2:])
|
|
|
|
|
+ try:
|
|
|
|
|
+ json.loads(_j) # 校验合法 JSON,避免注入坏体
|
|
|
|
|
+ except Exception:
|
|
|
|
|
+ print(f'[body-map] 跳过非法 JSON: {_m} {_pt}')
|
|
|
|
|
+ continue
|
|
|
|
|
+ body_map[(_m.upper(), _pt)] = _j.encode('utf-8')
|
|
|
|
|
+ _bm += 1
|
|
|
|
|
+ print(f'已加载请求体映射: {_bm} 条(来自 {args.body_map})')
|
|
|
|
|
+
|
|
|
tokens = {'parent': None, 'teacher': None}
|
|
tokens = {'parent': None, 'teacher': None}
|
|
|
token = os.getenv('CFC_TOKEN')
|
|
token = os.getenv('CFC_TOKEN')
|
|
|
if args.auto_login and not token:
|
|
if args.auto_login and not token:
|
|
@@ -1207,7 +1231,8 @@ def main():
|
|
|
stats = {}
|
|
stats = {}
|
|
|
rows = []
|
|
rows = []
|
|
|
pages = set()
|
|
pages = set()
|
|
|
- for page, method, path, auth, param, fn in CASES:
|
|
|
|
|
|
|
+ for case in CASES:
|
|
|
|
|
+ page, method, path, auth, param, fn, *rest = case
|
|
|
if args.only_page and args.only_page not in page:
|
|
if args.only_page and args.only_page not in page:
|
|
|
continue
|
|
continue
|
|
|
if not token and auth:
|
|
if not token and auth:
|
|
@@ -1215,7 +1240,13 @@ def main():
|
|
|
real = path.replace(':var', args.param_val)
|
|
real = path.replace(':var', args.param_val)
|
|
|
while '//' in real:
|
|
while '//' in real:
|
|
|
real = real.replace('//', '/')
|
|
real = real.replace('//', '/')
|
|
|
- r = _call(args.base, method, real, token=token, timeout=args.timeout, host=args.host)
|
|
|
|
|
|
|
+ # 允许 --body-map 覆盖真实请求体(破除空体冒烟天花板)
|
|
|
|
|
+ body = rest[0] if rest else b'{}'
|
|
|
|
|
+ if body_map is not None:
|
|
|
|
|
+ _m = body_map.get((method, real)) or body_map.get((method, path))
|
|
|
|
|
+ if _m is not None:
|
|
|
|
|
+ body = _m
|
|
|
|
|
+ r = _call(args.base, method, real, token=token, timeout=args.timeout, host=args.host, body=body)
|
|
|
http_code, biz, cat = _classify(r)
|
|
http_code, biz, cat = _classify(r)
|
|
|
stats[cat] = stats.get(cat, 0) + 1
|
|
stats[cat] = stats.get(cat, 0) + 1
|
|
|
pages.add(page)
|
|
pages.add(page)
|