Files
workphone-sdk/tools/build_real_device_acceptance_matrix.py

159 lines
7.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""生成真机110功能验收矩阵。
该脚本只分析本地项目代码,不执行任何微信外发动作。输出用于指导后续无线 Frida 真机验证:
1. 每项功能是否需要真机画面证据。
2. 每项功能是否需要后端日志证据。
3. 每项功能是否属于高风险,只能在测试对象或空数据下验证。
"""
from __future__ import annotations
import json
import re
from pathlib import Path
from datetime import datetime
ROOT = Path(__file__).resolve().parents[1]
OUT = ROOT / "开发文档" / "6、测试" / "wireless_frida_real_device_acceptance_20260518"
OUT.mkdir(parents=True, exist_ok=True)
CANDIDATE_FILES = [
ROOT / "sdk/app/routers/wechat_full.py",
ROOT / "sdk/app/routers/frida_wireless.py",
ROOT / "sdk/app/routers/adb.py",
ROOT / "sdk/app/routers/devices.py",
ROOT / "sdk/app/services/wechat_adb_engine.py",
ROOT / "sdk/app/agent/hook/wechat_hook_v2.js",
ROOT / "tools/wireless_frida_wechat_batch_verify.py",
]
HIGH_RISK_RE = re.compile(r"add|accept|delete|remove|post|send|forward|revoke|register|login|logout|switch|invite|quit|set|clear|follow|unfollow|like|comment|share|batchExecute|getSimPhone|moments|friend|group|official|phone|sim|feedback|report|unblock|手机号|添加好友|朋友圈|群发|解封|支付|转账|红包|举报|反馈|外发|删除|清空|资料改写", re.I)
READ_ONLY_RE = re.compile(r"^(get|list|status|info|ping|check|probe|current|health|ready|devices|projects|logs|screenshot|dump|globalSearch|find)", re.I)
UI_SAFE_RE = re.compile(r"tap|swipe|key|input|launch|open|back|scroll|click|navigate|tab", re.I)
CN_MAP = {
"ping": "心跳检测",
"processinfo": "进程信息读取",
"javainfo": "Java上下文读取",
"currentactivity": "当前Activity读取",
"classprobe": "微信关键类探测",
"getContacts": "联系人列表读取",
"getMessages": "消息记录读取",
"sendMessage": "发送消息",
"addFriend": "添加好友",
"postMoments": "发朋友圈",
"getSimPhone": "获取手机号",
"checkLoginState": "登录状态检查",
"getDeviceInfo": "设备信息读取",
"getNetworkInfo": "网络信息读取",
"getStorageInfo": "存储信息读取",
}
def classify(name: str) -> tuple[str, str, str]:
if HIGH_RISK_RE.search(name):
return "高风险受控", "仅允许测试对象/空数据/安全闸验证,不触发真实外发或真实改写", "接口存在、参数校验、后端拦截日志、安全闸截图"
if READ_ONLY_RE.search(name):
return "只读可真机执行", "允许在真机直接执行读取或状态检查", "后端返回数据、手机当前画面、Frida/ADB日志"
if UI_SAFE_RE.search(name):
return "UI安全操作", "允许在真机执行导航、点击、滑动、输入但不提交", "前后截图、当前Activity、命令日志"
return "待人工确认", "默认只做空数据/只读验证,避免误触发外部动作", "接口契约、后端日志、失败原因"
def _line_in_rpc_exports(text: str, pos: int) -> bool:
start = text.rfind("rpc.exports", 0, pos)
if start < 0:
return False
end = text.find("function _", start)
if end < 0:
end = len(text)
return start <= pos < end
def extract() -> list[dict]:
rows: list[dict] = []
seen = set()
for p in CANDIDATE_FILES:
if not p.exists():
continue
text = p.read_text(encoding="utf-8", errors="ignore")
patterns = [
("fastapi_route", r"@router\.(get|post|put|delete|patch)\(['\"]([^'\"]+)['\"]"),
("python_function", r"^(?:async\s+def|def)\s+([a-zA-Z_][\w]*)\s*\(", re.M),
("frida_rpc", r"^\s*([a-zA-Z_$][\w$]*)\s*:\s*function\s*\(", re.M),
]
for pat in patterns:
kind = pat[0]
flags = pat[2] if len(pat) > 2 else 0
for m in re.finditer(pat[1], text, flags):
if kind == "fastapi_route":
method, route = m.group(1).upper(), m.group(2)
name = f"{method} {route}"
else:
name = m.group(1)
# 只保留 rpc.exports 中暴露给 Python/后端调用的函数,避免把内部 helper 当作110接口。
if kind == "frida_rpc" and not _line_in_rpc_exports(text, m.start()):
continue
if name.startswith("_"):
continue
key = (kind, name, str(p.relative_to(ROOT)))
if key in seen:
continue
seen.add(key)
risk, execute_rule, evidence = classify(name)
rows.append({
"id": len(rows) + 1,
"name": name,
"display_name": CN_MAP.get(name, name),
"source_type": kind,
"source_file": str(p.relative_to(ROOT)),
"risk_level": risk,
"real_device_execution": execute_rule,
"required_evidence": evidence,
"backend_effect": "必须记录调用时间、通道、入参摘要、返回摘要、错误栈与耗时",
"phone_final_effect": "必须采集执行前后手机画面或当前Activity只读项可采集当前画面与返回值",
"acceptance_assertion": "通过=真机无线在线+命令/RPC成功+证据齐全+未触发禁止动作",
})
# 优先保留能覆盖110项的功能按风险与来源排序。
rows.sort(key=lambda r: (0 if r["risk_level"] != "高风险受控" else 1, r["source_file"], r["name"]))
for i, r in enumerate(rows[:160], 1):
r["id"] = i
return rows[:160]
def write(rows: list[dict]) -> None:
(OUT / "真机110功能验收矩阵.json").write_text(json.dumps(rows, ensure_ascii=False, indent=2), encoding="utf-8")
selected = rows[:110]
md = [
"# 真机 110 功能验收矩阵",
"",
f"生成时间:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}",
"",
"本矩阵把验证口径从“接口跑通”升级为“**真机执行、后端记录、最终画面、结果断言**”。其中,高风险受控项不在真实对象上外发或改写,只允许通过测试对象、空数据或安全总闸验证。",
"",
"| 序号 | 功能 | 来源 | 风险层级 | 真机执行口径 | 必要证据 | 验收断言 |",
"|---:|---|---|---|---|---|---|",
]
for r in selected:
md.append(
f"| {r['id']} | `{r['display_name']}` | `{r['source_file']}` | {r['risk_level']} | {r['real_device_execution']} | {r['required_evidence']} | {r['acceptance_assertion']} |"
)
md += [
"",
"## 执行原则",
"",
"真机验证必须在已拔掉 Type-C 的无线链路上进行手机端结果以当前页面、前后截图、Activity 状态、返回值和后端日志共同判定。对于添加好友、获取手机号、群发、朋友圈、反馈/举报、解封等功能,若没有明确测试账号、测试手机号、测试群或用户二次确认,不进入真实提交环节。",
"",
"## 后续脚本改造要求",
"",
"脚本需要为每个功能写入 `before_screenshot`、`backend_log`、`after_screenshot`、`assertion`、`risk_gate` 五类字段。失败项不能只标记失败还要写明是哪一层失败无线连接、Frida RPC、后端调用、手机页面、最终结果或安全闸。",
]
(OUT / "真机110功能验收矩阵.md").write_text("\n".join(md) + "\n", encoding="utf-8")
print(json.dumps({"total_extracted": len(rows), "selected_for_110": len(selected), "out": str(OUT)}, ensure_ascii=False, indent=2))
if __name__ == "__main__":
rows = extract()
write(rows)