# -*- coding: utf-8 -*- """r2 票 02 静态对账:扫描 e2e-incr.py 全部 api()/api_raw() 调用,对照 dump_api_out.txt 基准。 与票 01 scan_calls.py 同款正则;只扫增量脚本、落 calls-incr.json——票 01 的 calls-r2.json 为冻结产物不动。 产出(stdout + calls-incr.json): - 增量调用 × 基准命中(零未命中 = PASS) - 票 02 范围 10 端点逐一核对(calls-expected 清单全覆盖) """ import json import re import io import sys sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8') SCRIPT = r'e:\code\crm-backend-matt\.scratch\customer-e2e\e2e-incr.py' DUMP = r'e:\code\crm-backend-matt\.scratch\customer-rework\dump_api_out.txt' OUTJSON = r'e:\code\crm-backend-matt\.scratch\customer-e2e-r2\calls-incr.json' # 票 02 范围(票 01 对账锁定:客户域未覆盖 19 中恰此 10 个) EXPECTED = { ('POST', '/api/customer/agreement/create'), ('POST', '/api/customer/agreement/edit'), ('POST', '/api/customer/agreement/delete'), ('GET', '/api/customer/agreement/detail'), ('GET', '/api/customer/agreement/list'), ('GET', '/api/rule/customer/dedup'), ('POST', '/api/rule/customer/dedup/save'), ('GET', '/api/preference/view-form/get'), ('POST', '/api/preference/view-form/save'), ('POST', '/api/customer/company-lookup/batch-update'), } endpoints = set() for line in open(DUMP, encoding='utf-8'): m = re.match(r'^(GET|POST) (\S+)', line.strip()) if m: endpoints.add((m.group(1), m.group(2))) print(f'基准端点(dump_api_out.txt 全模块):{len(endpoints)}') src = open(SCRIPT, encoding='utf-8').read() # 预解析模块级路径常量(NAME = '/api/...')——dedup/view-form 调用走常量而非字面量 consts = dict(re.findall(r"^([A-Z_]+)\s*=\s*'(/api/[^']+)'", src, re.M)) def _resolve(path): return consts.get(path, path) calls = set() for m in re.finditer(r"api(?:_raw)?\(\s*\w+,\s*'(GET|POST)',\s*(?:f?)'([^']+)'", src): calls.add((m.group(1), _resolve(m.group(2)))) # 字面量路径(含 f-string 内无插值的形态) for m in re.finditer(r"api(?:_raw)?\(\s*\w+,\s*'(GET|POST)',\s*([A-Z_]{3,})\b", src): p = consts.get(m.group(2)) if p: calls.add((m.group(1), p)) # 模块常量路径(DEDUP_G/VF_G 等) print(f'路径常量:{consts}') print(f'\n增量脚本调用端点:{len(calls)}') unknown = sorted(c for c in calls if c not in endpoints) print('\n== 命中情况 ==') for m, p in sorted(calls): tag = 'OK' if (m, p) in endpoints else '!!未命中' print(f' [{tag}] {m} {p}') missing_expected = sorted(EXPECTED - calls) extra = sorted(calls - EXPECTED - endpoints) print(f'\n== 票 02 范围核对 == 期望 10,实调 {len(calls & EXPECTED)},缺 {len(missing_expected)}') for m, p in missing_expected: print(f' - 缺 {m} {p}') for m, p in sorted(calls - EXPECTED): in_base = (m, p) in endpoints print(f' · 范围外{"(基准内,复用读端点等)" if in_base else "!!"} {m} {p}') json.dump({'incr_calls': sorted(f'{m} {p}' for m, p in calls), 'unknown': [f'{m} {p}' for m, p in unknown], 'expected_scope': sorted(f'{m} {p}' for m, p in EXPECTED), 'missing_expected': [f'{m} {p}' for m, p in missing_expected]}, open(OUTJSON, 'w', encoding='utf-8'), ensure_ascii=False, indent=1) print(f'\n✔ 落盘 calls-incr.json') print('静态对账结论:' + ('PASS(零未命中 + 10 端点全覆盖)' if not unknown and not missing_expected else f'FAIL(未命中 {len(unknown)} / 范围缺 {len(missing_expected)})'))