You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

83 lines
3.6 KiB

# -*- 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)}'))