2b20a611d7
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
345 lines
32 KiB
HTML
345 lines
32 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Pangolin 功能 × 测试覆盖清单</title>
|
||
<style>
|
||
:root{
|
||
--bg:#0f1117; --panel:#171a22; --panel2:#1d2129; --fg:#e6e8ee; --fg2:#a8afbd;
|
||
--accent:#e0884f; --accent2:#5fb0c9; --ok:#5ec27a; --bad:#e06a6a; --warn:#e0b84f;
|
||
--border:#272c36; --mono:"SF Mono",ui-monospace,Menlo,Consolas,monospace;
|
||
--sans:-apple-system,"PingFang SC","Helvetica Neue",Arial,sans-serif;
|
||
}
|
||
*{box-sizing:border-box}
|
||
body{margin:0;background:var(--bg);color:var(--fg);font-family:var(--sans);line-height:1.7;font-size:15px}
|
||
.wrap{max-width:1040px;margin:0 auto;padding:48px 24px 96px}
|
||
h1{font-size:30px;line-height:1.3;margin:0 0 8px;letter-spacing:-.01em}
|
||
.sub{color:var(--fg2);font-size:15px;margin:0 0 28px}
|
||
h2{font-size:21px;margin:46px 0 6px;padding-bottom:8px;border-bottom:1px solid var(--border)}
|
||
h3{font-size:16.5px;margin:26px 0 6px;color:var(--accent)}
|
||
p{margin:10px 0}
|
||
code{font-family:var(--mono);font-size:.86em;background:var(--panel2);padding:1px 6px;border-radius:5px;color:#f0d9c4}
|
||
a{color:var(--accent2);text-decoration:none}
|
||
a:hover{text-decoration:underline}
|
||
ul{margin:8px 0;padding-left:22px}
|
||
li{margin:5px 0}
|
||
.lead{background:linear-gradient(180deg,rgba(224,136,79,.10),transparent);border:1px solid var(--border);border-radius:12px;padding:18px 20px;margin:0 0 8px}
|
||
.small{color:var(--fg2);font-size:13px}
|
||
.note{color:var(--fg2);font-size:13px;border-left:3px solid var(--border);padding-left:12px;margin:10px 0}
|
||
.domain{color:var(--accent2);font-size:13px;font-weight:700;letter-spacing:.04em;text-transform:uppercase;margin:30px 0 2px}
|
||
table{width:100%;border-collapse:collapse;margin:10px 0 18px;font-size:13px}
|
||
th,td{border:1px solid var(--border);padding:8px 10px;text-align:left;vertical-align:top}
|
||
th{background:var(--panel2);color:var(--fg);font-weight:600}
|
||
td{color:var(--fg2)}
|
||
td b,td strong{color:var(--fg)}
|
||
td.feat{color:var(--fg);font-weight:600;width:15%}
|
||
.tag{display:inline-block;font-size:10.5px;font-weight:700;padding:1px 7px;border-radius:999px;white-space:nowrap}
|
||
.t-ok{background:rgba(94,194,122,.16);color:var(--ok)}
|
||
.t-warn{background:rgba(224,184,79,.16);color:var(--warn)}
|
||
.t-bad{background:rgba(224,106,106,.16);color:var(--bad)}
|
||
.t-man{background:rgba(168,175,189,.16);color:var(--fg2)}
|
||
.kbd{font-family:var(--mono);font-size:.84em;color:#f0d9c4}
|
||
.legend{display:flex;flex-wrap:wrap;gap:14px;margin:14px 0 4px;font-size:13px}
|
||
.legend span{display:flex;align-items:center;gap:6px}
|
||
.ck{background:var(--panel);border:1px solid var(--border);border-radius:10px;padding:14px 18px;margin:12px 0}
|
||
.ck li{margin:7px 0;list-style:none}
|
||
.ck ul{padding-left:4px}
|
||
.ck .box{color:var(--accent);font-family:var(--mono);margin-right:8px}
|
||
.pill{display:inline-block;font-size:11px;font-weight:700;padding:2px 9px;border-radius:999px;margin-right:6px;background:rgba(95,176,201,.16);color:var(--accent2)}
|
||
.crit{color:#f0d9c4;font-weight:600}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="wrap">
|
||
|
||
<h1>Pangolin 功能 × 测试覆盖清单</h1>
|
||
<p class="sub">逐功能盘点:现有能力 → 已覆盖测试(怎么覆盖)→ 欠缺 → 只能人工。作为「验证五个终端是否正确」的对照工作表。</p>
|
||
|
||
<div class="lead">
|
||
<strong>怎么用:</strong>本清单分两部分——<b>A 共享逻辑 + 后端(控制面 + 数据面)</b>,跑一次自动化套件即对全平台生效;<b>B 平台隧道层</b>,五端各自不同,须逐端按底部验收清单打勾。每行的「覆盖」标签直接告诉你这条是<b>已自动化</b>、<b>部分/契约级</b>、<b>零覆盖</b> 还是<b>本质上只能人工</b>。
|
||
<div class="small" style="margin-top:8px">↔ 配套:<a href="test-architecture.html">统一测试框架·架构说明</a>(分层方法论 + ⑩ 已知缺口) · <a href="dev-conventions.html">开发规范·可测试性五支柱</a> · <a href="vpn-test-plan.md">VPN 测试计划</a>(站点矩阵)</div>
|
||
</div>
|
||
|
||
<div class="legend">
|
||
<span><span class="tag t-ok">自动</span> CI 里有断言守门,回归即红</span>
|
||
<span><span class="tag t-warn">部分</span> 只到契约/单元,未覆盖端到端或边界</span>
|
||
<span><span class="tag t-bad">零</span> 当前无任何测试</span>
|
||
<span><span class="tag t-man">人工</span> 本质上只能人工/真设备验证</span>
|
||
</div>
|
||
<p class="note">「覆盖」列给的是<b>当前状态</b>,不是目标;标 <span class="tag t-bad">零</span>/<span class="tag t-warn">部分</span> 的「欠缺」列写清下一步补什么。标 <span class="tag t-man">人工</span> 的不是缺陷,是真设备/真出网/真链路这类自动化跑不动、必须人测的部分——集中收在 <a href="#manual">§A.4 / §B 验收清单</a>。</p>
|
||
|
||
<!-- ============================ PART A ============================ -->
|
||
<h2>A · 共享逻辑 + 后端(控制面 + 数据面)</h2>
|
||
<p>这部分代码<b>四端共用</b>(Flutter 业务逻辑 / Go 控制面 / 数据面渲染),自动化跑一次即对五个终端同时生效。验证策略:<b>能自动化的全压到 CI,逐端不重复测</b>。</p>
|
||
|
||
<!-- A.1 控制面 -->
|
||
<h3 id="a1">A.1 控制面(Go 控制面 HTTP API + gRPC,<code>server/internal/*</code>)</h3>
|
||
<p class="domain">账号 / 鉴权</p>
|
||
<table>
|
||
<tr><th>功能</th><th>现有能力</th><th>已覆盖 + 怎么覆盖</th><th>欠缺 / 只能人工</th></tr>
|
||
<tr><td class="feat">注册 / 登录 / 刷新 <span class="tag t-ok">自动</span></td>
|
||
<td>邮箱注册、密码登录、refresh token 轮换、登出;<code>/v1/auth/{register,login,refresh,code}</code></td>
|
||
<td><code>auth/service_test · handler_test · token_test · integration_test</code>:真库(SQLite)跑完整 register→login→refresh 链;token 签发/校验/过期纯函数单测;<code>password_test</code> 哈希;<code>helpers_test</code></td>
|
||
<td>真邮件投递(SMTP)只 mock;账号枚举/暴力破解的真实速率压测仅 <code>ratelimit_test</code> 逻辑级</td></tr>
|
||
<tr><td class="feat">邮箱验证码 <span class="tag t-ok">自动</span></td>
|
||
<td>发码、校验、节流;<code>emailcheck</code></td>
|
||
<td><code>auth/emailcheck_test · ratelimit_test</code>:码生成/校验/节流窗口逻辑断言</td>
|
||
<td>真 SMTP 通道、到达率、垃圾箱判定 → <span class="tag t-man">人工</span></td></tr>
|
||
<tr><td class="feat">两步验证 TOTP <span class="tag t-ok">自动</span></td>
|
||
<td>TOTP 绑定/校验、备份码</td>
|
||
<td><code>totp/totp_test</code>(算法)+ <code>auth/totp_user_test</code>(绑定/校验流程)</td>
|
||
<td>真 Authenticator app 互操作 → <span class="tag t-man">人工</span>(一次性)</td></tr>
|
||
<tr><td class="feat">管理后台 <span class="tag t-ok">自动</span></td>
|
||
<td>admin 登录/会话、IP 白名单、配置、加密</td>
|
||
<td><code>admin/{auth,session,mw_ipallow,config,crypto,handlers,services_real}_test</code>:会话签发、IP allowlist 中间件、handler 行为</td>
|
||
<td>后台前端页面交互 → <span class="tag t-man">人工</span></td></tr>
|
||
</table>
|
||
|
||
<p class="domain">业务 / 计费</p>
|
||
<table>
|
||
<tr><th>功能</th><th>现有能力</th><th>已覆盖 + 怎么覆盖</th><th>欠缺 / 只能人工</th></tr>
|
||
<tr><td class="feat">兑换码 / 套餐 <span class="tag t-ok">自动</span></td>
|
||
<td>码生成、兑换、套餐授予;<code>/v1/redeem · /v1/plans</code></td>
|
||
<td><code>codes/{generator,service}_test</code>:码生成唯一性、兑换幂等、过期/已用拒绝</td>
|
||
<td>—</td></tr>
|
||
<tr><td class="feat">支付 webhook <span class="tag t-warn">部分</span></td>
|
||
<td>第三方支付回调 → 充值/开通</td>
|
||
<td><code>codes/webhook_test</code>:回调签名校验 + 入账逻辑(mock 上游)</td>
|
||
<td>真支付渠道端到端(下单→回调→开通)仅 mock;真渠道沙箱 → <span class="tag t-man">人工</span></td></tr>
|
||
<tr><td class="feat">激励解锁配额 <span class="tag t-ok">自动</span></td>
|
||
<td>看广告解锁额度;<code>/v1/ads/unlock</code></td>
|
||
<td><code>usage/ads_test</code>:解锁额度计算/上限</td>
|
||
<td>真广告 SDK 回调 → <span class="tag t-man">人工</span></td></tr>
|
||
<tr><td class="feat">设备管理 <span class="tag t-ok">自动</span></td>
|
||
<td>设备注册/列举/解绑、上限;<code>/v1/me/devices</code></td>
|
||
<td><code>devices/service_test · devices_integration_test</code>:真库注册→列举→解绑;<b>注册回填 last_seen</b>(修过的真 bug)</td>
|
||
<td>—</td></tr>
|
||
</table>
|
||
|
||
<p class="domain">节点 / 调度 / 供给</p>
|
||
<table>
|
||
<tr><th>功能</th><th>现有能力</th><th>已覆盖 + 怎么覆盖</th><th>欠缺 / 只能人工</th></tr>
|
||
<tr><td class="feat">节点注册表 + 连接 <span class="tag t-ok">自动</span></td>
|
||
<td>列节点、连接/断开、ping;<code>/v1/nodes · /connect · /disconnect</code></td>
|
||
<td><code>nodes/{grpc,hub,lifecycle}_test</code>:gRPC hub 收发、节点生命周期状态机</td>
|
||
<td>真节点延迟/可用性探测准确度 → 见数据面 §A.2</td></tr>
|
||
<tr><td class="feat">Agent mTLS 接入 <span class="tag t-ok">自动</span></td>
|
||
<td>CA 签发、CRL 吊销、节点身份、bootstrap token</td>
|
||
<td><code>mtls/{ca,crl,identity,bootstrap}_test</code> + <code>nodectl/bootstrap_token_test</code>:证书链签发/校验/吊销</td>
|
||
<td>真 mTLS 握手在 §A.2 e2e 覆盖一段</td></tr>
|
||
<tr><td class="feat">自动调度 / 故障替换 <span class="tag t-ok">自动</span></td>
|
||
<td>探测引擎、熔断、容量、自动替换编排、探针接入</td>
|
||
<td><code>scheduler/detect/engine_test · orchestrate/{breaker,capacity,config,replacer}_test · probe/{ingest,prober_agent}_test · wiring_{lifecycle,provision}_test</code>:健康判定→熔断→替换决策全链逻辑</td>
|
||
<td>真云厂商触发的端到端替换 → <span class="tag t-man">人工</span>(贵/慢)</td></tr>
|
||
<tr><td class="feat">云供给 provision <span class="tag t-warn">部分</span></td>
|
||
<td>cloud-init 渲染、provider 注册表、节点替换</td>
|
||
<td><code>provision/{cloudinit,replace,service}_test · providers/registry_test</code>(fakes 注入)</td>
|
||
<td>真厂商 API 开机/销毁 → <span class="tag t-man">人工</span>(按需,烧钱)</td></tr>
|
||
<tr><td class="feat">告警 <span class="tag t-ok">自动</span></td>
|
||
<td>Telegram 通知、runbook</td>
|
||
<td><code>alert/{notifier,runbook}_test</code>:触发条件 + 去抖逻辑</td>
|
||
<td>真 Telegram 投递 → <span class="tag t-man">人工</span></td></tr>
|
||
</table>
|
||
|
||
<p class="domain">持久层 / 可移植性</p>
|
||
<table>
|
||
<tr><th>功能</th><th>现有能力</th><th>已覆盖 + 怎么覆盖</th><th>欠缺 / 只能人工</th></tr>
|
||
<tr><td class="feat">双数据库 SQLite/MySQL <span class="tag t-ok">自动</span></td>
|
||
<td>裸 SQL + 方言层;迁移分两套;upsert/行锁中性记法</td>
|
||
<td><code>db/dialect_test · sqlite_smoke_test</code>;<code>store/{sqlite_stores,sqlite_migrate,sqlite_per_device,mysql,mysql_integration}_test</code>:SQLite 实库 + MySQL testcontainers 真库跑同一套断言;<code>ci/scan-portable-sql.sh</code> 扫禁用 MySQL 专属构造</td>
|
||
<td>真 512MB VPS 上的并发/锁竞争压测 → <span class="tag t-man">人工</span></td></tr>
|
||
</table>
|
||
|
||
<!-- A.2 数据面 -->
|
||
<h3 id="a2">A.2 数据面(sing-box 配置渲染 + per-user 记账,<code>httpapi/clientconfig · agentd/*</code>)</h3>
|
||
<p>「连上了能不能真出网、记账准不准」的源头逻辑。<b>渲染/解析有测试,真链路靠 e2e 一段 + 人工兜底。</b></p>
|
||
<table>
|
||
<tr><th>功能</th><th>现有能力</th><th>已覆盖 + 怎么覆盖</th><th>欠缺 / 只能人工</th></tr>
|
||
<tr><td class="feat">客户端配置渲染 <span class="tag t-ok">自动</span></td>
|
||
<td><code>BuildClientConfig</code> 服务端渲染原样下发:REALITY 出站、TUN 入站(strict_route 杀开关)、<span class="crit">DNS 劫持首条规则</span>、国内分流</td>
|
||
<td><code>httpapi/clientconfig_test</code>:断言 REALITY 公钥/short-id/端口、TUN auto_route/strict_route、<b>hijack-dns 规则排在 LAN 前</b>、split_cn 开关</td>
|
||
<td>渲染对 ≠ 内核吃得下:真 sing-box 加载该配置并连通 → §B 人工</td></tr>
|
||
<tr><td class="feat">节点配置渲染(agent 侧)<span class="tag t-ok">自动</span></td>
|
||
<td>agent 渲染 sing-box 服务端配置 + 凭证增删/轮换/吊销</td>
|
||
<td><code>agentd/singbox_test</code>:Upsert/Revoke/Rotate/ApplyConfig 状态机;<code>derive_test</code> 数据口凭证派生;<code>hy2cert_test</code> 证书;<code>command_test</code></td>
|
||
<td>真 <code>systemctl restart sing-box</code> 后端口真监听 → <span class="tag t-man">人工</span></td></tr>
|
||
<tr><td class="feat">per-user 流量采集 <span class="tag t-warn">部分</span></td>
|
||
<td><code>V2RayUsageSource</code>:agent 读 sing-box v2ray_api StatsService per-user 计数器 → 聚合 → ReportUsage</td>
|
||
<td><b>新增</b> <code>agentd/usage_v2ray_test</code>:表驱动 <code>parseUserStat</code> + loopback 假 StatsService 跑真 <code>Collect()</code>,验聚合/方向不串/全0丢弃/<code>reset=true</code> 窗口语义</td>
|
||
<td>假 StatsService ≠ 真 sing-box:真出网流量经真 v2ray_api 的计数准确度 → <span class="tag t-man">人工</span></td></tr>
|
||
<tr><td class="feat">记账全链 e2e <span class="tag t-warn">部分</span></td>
|
||
<td>enroll → ReportUsage → 统计入库 → API 读出</td>
|
||
<td><code>server/test/e2e/smoke_test</code>:进程级 gRPC 全链路(agent enroll→上报→控制面统计真入库真读出),HTTP 段 + miniredis</td>
|
||
<td>缺 <code>/v1/usage/devices</code> 分设备断言(stats-overhaul 合并后补回);真节点真流量不在 e2e</td></tr>
|
||
<tr><td class="feat">REALITY 数据口连通 <span class="tag t-man">人工</span></td>
|
||
<td>客户端经 REALITY 连节点 443 真出网</td>
|
||
<td>—(握手是真协议真节点,自动化跑不动)</td>
|
||
<td>真握手成功 + 出网 → §B 每端验收清单第 1–2 步</td></tr>
|
||
</table>
|
||
|
||
<!-- A.3 客户端共享逻辑 -->
|
||
<h3 id="a3">A.3 客户端共享逻辑(Flutter <code>client/lib/*</code>,四端共用 Dart)</h3>
|
||
<table>
|
||
<tr><th>功能</th><th>现有能力</th><th>已覆盖 + 怎么覆盖</th><th>欠缺 / 只能人工</th></tr>
|
||
<tr><td class="feat">API 客户端 + 契约 <span class="tag t-ok">自动</span></td>
|
||
<td><code>ApiClient</code> 取数、错误映射;<code>/v1/me · usage · usage/devices</code></td>
|
||
<td><code>unit/api_client_test</code>;<code>contract/{api_contract,stats_contract}_test</code> 冻结 wire 字段形状;Go 侧 <code>httpapi/contract_test · usage/contract_test · pb/agentv1/contract_test</code> 两侧对齐</td>
|
||
<td>—</td></tr>
|
||
<tr><td class="feat">登录 / 续登 / 登出流程 <span class="tag t-ok">自动</span></td>
|
||
<td><code>AuthNotifier</code> 状态机 + token 存储</td>
|
||
<td><code>unit/flow_auth_test</code>:真控制器 + MockClient 注入,跑登录→重启续登→登出</td>
|
||
<td>真 <code>flutter_secure_storage</code> keychain 行为 → <span class="tag t-man">人工</span>(一次性)</td></tr>
|
||
<tr><td class="feat">连接状态机 <span class="tag t-ok">自动</span></td>
|
||
<td><code>connectionProvider</code> connecting→connected→error;ping 探测</td>
|
||
<td><code>unit/{connection_controller,flow_connect}_test</code>:真控制器 + 假桥,验状态流转;<code>connect_passthrough_test</code></td>
|
||
<td>真隧道回调时序(连上才启动 app 等触发条件)→ §B</td></tr>
|
||
<tr><td class="feat">统计计算 + 上屏 <span class="tag t-ok">自动</span></td>
|
||
<td>月 GB/时长聚合、周柱、分设备归因;<code>stats_page</code></td>
|
||
<td><code>unit/{device_usage,format}_test</code> 解析/格式化;<b>新增</b> <code>widget/stats_page_test</code>:真 wire 形态喂 StatsPage 断言指标卡/周柱/分设备<b>数值真上屏</b></td>
|
||
<td>—(解析对 + 上屏对都已守)</td></tr>
|
||
<tr><td class="feat">配额 / 节点列表 <span class="tag t-ok">自动</span></td>
|
||
<td><code>quotaProvider · nodesProvider</code></td>
|
||
<td><code>unit/{quota_controller,nodes_provider}_test</code></td>
|
||
<td>—</td></tr>
|
||
<tr><td class="feat">UI 视觉一致性 <span class="tag t-ok">自动</span></td>
|
||
<td>各页面/组件像素还原(亮/暗 × 中/英 × 手机/平板/桌面)</td>
|
||
<td><code>golden/{components,auth_redesign,desktop_pages,tablet_pages}_test</code> Linux 权威基线;<code>widget/{cards,connect_button}_test</code>;<code>responsive/form_factor_test</code>;<code>fonts_test</code> 字体锁</td>
|
||
<td>真机不同 DPI/字号/深色模式实机观感 → <span class="tag t-man">人工</span>(抽查)</td></tr>
|
||
<tr><td class="feat">桥接口契约 <span class="tag t-warn">部分</span></td>
|
||
<td><code>VpnBridge</code> 抽象 + 桌面子进程实现 + mock</td>
|
||
<td><code>bridge/{vpn_bridge_mock,kernel_process,desktop_vpn_bridge_m4m5}_test</code>:内核进程查找/启停、mock 桥行为</td>
|
||
<td>原生 MethodChannel 两侧真实编解码 → §B(各端原生层)</td></tr>
|
||
<tr><td class="feat">交互组件(本轮)<span class="tag t-warn">部分</span></td>
|
||
<td>切换节点确认框(连接态防误触,节点行 + 智能选择卡同款);节点页搜索框配色(<code>PangolinFieldBox</code> 修双色缝);<code>showPangolinToast</code>(主显示区内、自适应宽、上限 80%、超长多行居中)</td>
|
||
<td><b>Windows 人工已验 2026-06-29</b>:确认框两处弹出 + 防误触正常;搜索框配色一致无缝;toast 常规文案显示正常(位置/明暗/动画)。<code>node_connect_confirm_test</code> 守确认框逻辑;toast 走真相源规格 <code>design/CONTRACT.md §2</code></td>
|
||
<td>toast <b>超长文本多行居中 + ≤80% 上限</b>边界<b>未验</b> <span class="tag t-bad">待测</span>;其余四端(mac/Android/iOS/iPad)同源未逐端抽查 → <span class="tag t-man">人工</span></td></tr>
|
||
</table>
|
||
|
||
<p class="domain">设计契约 · 五端 UI 同源</p>
|
||
<p>五端跑<b>同一个 Flutter 工程</b>(<code>client/lib</code> 共享 Dart),UI 不按平台分叉——颜色/样式/组件<b>不是「约定一致」,是物理上同一份代码</b>。下表是「五端同源」各维度的<b>单源 + 守门</b>状态。两处关键盲区:<span class="crit">① token 有漂移闸,logo/app-icon 资产没有;② 「生成物不漂移」有闸,但「screen 是否真用真相源」(依从性) 零闸、且所有闸无反向 case 自检</span>。</p>
|
||
<table>
|
||
<tr><th>维度</th><th>单源(真相源 → 生成/消费)</th><th>守门机制</th><th>欠缺 / 风险</th></tr>
|
||
<tr><td class="feat">颜色/间距/圆角/字体 <span class="tag t-ok">自动</span></td>
|
||
<td><code>design/colors_and_type.css</code>(clay/sand 色板)→ <code>gen_flutter_tokens.mjs</code> → <code>pangolin_tokens.gen.dart</code>(勿手改)→ <code>pangolin_theme.dart</code> 语义层;业务禁硬编码 hex</td>
|
||
<td><b>CI <code>codegen-drift</code> 闸</b>(<code>ci/check-codegen-drift.sh</code>):重生成与提交版不一致即红,防「改 CSS 没重生成 / 手改生成物」</td>
|
||
<td>—(单源 + 自动闸,最规范的一块)</td></tr>
|
||
<tr><td class="feat">组件 <span class="tag t-ok">自动</span></td>
|
||
<td><code>client/lib/widgets/</code> 唯一实现;<code>design/</code> 禁放 Dart 副本(会漂移);规格在 <code>design/CONTRACT.md</code> + <code>design/preview/</code></td>
|
||
<td><b>golden 测试</b>:组件 + 各页面 × 明/暗 × 中/英 × 手机/平板/桌面,像素回归即红</td>
|
||
<td>—</td></tr>
|
||
<tr><td class="feat">图标库 <span class="tag t-ok">自动</span></td>
|
||
<td><code>lucide_icons</code>(细线条)全端同一套字体图标</td>
|
||
<td>随 golden 像素守</td>
|
||
<td>—</td></tr>
|
||
<tr><td class="feat">品牌 logo(应用内)<span class="tag t-warn">部分</span></td>
|
||
<td><code>design/assets/*.svg</code>(mark / wordmark / app-icon)→ <b>拷贝一份到</b> <code>client/assets/</code>,<code>flutter_svg</code> 加载,四端同一份矢量</td>
|
||
<td><b>无自动闸</b>——<code>design↔client</code> 双拷贝靠手动同步(当前 4 个 SVG 已 diff 确认一致)</td>
|
||
<td>改 <code>design/assets</code> 那份忘同步 <code>client/assets</code>,CI 静默放过 → <span class="tag t-warn">待补 drift 闸</span></td></tr>
|
||
<tr><td class="feat">app 启动图标 <span class="tag t-warn">部分</span></td>
|
||
<td><code>assets/app-icon.svg</code> → <code>app-icon-ios-1024.png</code> → <code>flutter_launcher_icons</code> 一键铺五端全尺寸(iOS appiconset / Android mipmap / macOS / Windows .ico)</td>
|
||
<td><b>无 drift 闸</b>——生成物(各端 PNG/ico,如 iOS 22 张)入库,靠手动 <code>dart run flutter_launcher_icons</code></td>
|
||
<td>改 <code>app-icon.svg</code> 忘重跑生成,五端图标与源图分叉,CI 静默放过 → <span class="tag t-warn">待补 drift 闸</span></td></tr>
|
||
<tr><td class="feat">设计契约文档 <span class="tag t-man">人工</span></td>
|
||
<td><code>design/CONTRACT.md</code>(design-distill 蒸馏):token 映射 / 组件映射 / 逐屏像素规格 / 逐屏验收清单(明×暗×中×英)</td>
|
||
<td>人读契约;像素侧由 golden 兜底</td>
|
||
<td>「还原对不对」的逐屏验收是人工抽查(截图 diff 可半自动)</td></tr>
|
||
<tr><td class="feat">screen 依从性<br>(源自真相源)<span class="tag t-bad">零</span></td>
|
||
<td>要求 screen/widget 的颜色/字号/间距<b>必须源自</b> <code>PangolinColors</code>/<code>PangolinText</code>/<code>PangolinSpacing</code>,不得硬编码 hex/数值或绕过 <code>widgets/</code> 自拼组件</td>
|
||
<td><b>无任何检测</b>。<code>codegen-drift</code> 只守「生成物=CSS 源」<b>不守消费端是否用它</b>;<code>analysis_options.yaml</code> 无禁硬编码规则;<code>design/_adherence.oxlintrc.json</code> 能抓 raw hex/px 但<b>只对 JSX 原型、warn 级、未接 CI</b></td>
|
||
<td>实测 <code>lib/screens</code> 已有 <b>62+ 处</b>硬编码 <code>fontSize:</code>/<code>EdgeInsets</code> 数值无人拦(如 <code>account_page.dart:187</code> <code>fontSize: 16</code>)→ <span class="tag t-warn">待补依从性扫描闸(第①档:正则扫 raw 字面量,仿 <code>scan-portable-sql.sh</code>)</span></td></tr>
|
||
<tr><td class="feat">闸的反向 case 自检 <span class="tag t-bad">零</span></td>
|
||
<td>每个闸应有「注入违规 → 断言闸变红」的负向自检,证明闸<b>非永真</b>(怎么改都绿 = 形同虚设)</td>
|
||
<td><b>无任何反向 case</b>(全仓 grep 零结果)——含 <code>codegen-drift</code> 在内的所有闸都没验证过「真能拦住违规」</td>
|
||
<td>→ <span class="tag t-warn">待补 fixture 自检</span>(备一行违规样本,CI 跑「扫它必须非零退出」;新依从性闸与 codegen-drift 都该配)</td></tr>
|
||
</table>
|
||
<p class="note"><b>关键区别:</b><code>codegen-drift</code>(生成物不漂移)<b>≠</b> 依从性(消费端真用真相源)。前者保证<b>真相源本身</b>没被改歪,后者保证 <b>screen 真的去用</b>了真相源——当前<b>只有前者有闸</b>,后者零守门、且所有闸都缺反向 case 自检。规划见 <code>plan:UI 真相源依从性收口</code>(本轮仅登记,补闸后续做)。</p>
|
||
<p class="note"><b>待补(性价比高):</b>仿 <code>ci/check-codegen-drift.sh</code> 给 <b>logo SVG 双拷贝</b> 和 <b>app-icon 生成物</b> 各加一个 drift 闸——重新生成/比对 <code>design↔client</code>,不一致即红,把「图标 logo 五端同源」从<b>靠纪律</b>升成<b>靠闸</b>。这是 token 已有、资产尚缺的一行。</p>
|
||
|
||
<!-- A.4 manual consolidated -->
|
||
<h3 id="manual">A.4 后端「只能人工」汇总</h3>
|
||
<div class="ck">
|
||
<ul>
|
||
<li><span class="box">☐</span> 真 SMTP / Telegram / 支付渠道 / 广告 SDK 的真实投递与回调(外部第三方,仅 mock 到逻辑边界)</li>
|
||
<li><span class="box">☐</span> 真云厂商 provision 开机→销毁→替换端到端(按需手测,烧钱)</li>
|
||
<li><span class="box">☐</span> 真 sing-box 加载渲染配置后端口真监听、真 v2ray_api 计数准确(渲染/解析已自动,运行时人测)</li>
|
||
<li><span class="box">☐</span> 512MB VPS 真并发/锁竞争压测</li>
|
||
<li><span class="box">☐</span> 后台前端页面交互(admin UI)</li>
|
||
</ul>
|
||
</div>
|
||
|
||
<!-- ============================ PART B ============================ -->
|
||
<h2>B · 平台隧道层(五端逐一)</h2>
|
||
<p>真正逐端不同的只有<b>原生隧道实现</b>——这是测试架构 ⑩ 标注的<b>零自动化盲区</b>。三种策略:</p>
|
||
<ul>
|
||
<li><span class="pill">策略①</span><b>内嵌 libbox + 系统 VPN 扩展</b>:iOS / iPad(<code>NEPacketTunnelProvider</code> + <code>VpnManager.swift</code>)、Android(<code>libbox.aar</code> + <code>PangolinVpnService.kt</code> VpnService)、macOS(<code>PacketTunnel</code> 系统扩展,<b>当前默认关</b> <code>kUseNativeVpnMacOS=false</code>)</li>
|
||
<li><span class="pill">策略②</span><b>sing-box 子进程 + TUN</b>:macOS(默认,<code>DesktopVpnBridge</code> + <code>kernel_process.dart</code>)、Windows(<code>sing-box.exe</code> + <code>wintun.dll</code>)、Linux</li>
|
||
<li><span class="pill">桥接</span><b>原生↔Dart</b>:MethodChannel/EventChannel(<code>VpnEventBus.kt</code> / <code>VpnChannel.swift</code> / <code>StatsClient.swift</code>)传状态与统计——「mac 统计恒为 0」就出在这条没契约守门</li>
|
||
</ul>
|
||
|
||
<h3>各端隧道状态矩阵</h3>
|
||
<table>
|
||
<tr><th>终端</th><th>隧道实现</th><th>已覆盖</th><th>欠缺 / 验证重点</th></tr>
|
||
<tr><td class="feat">Android</td>
|
||
<td><code>PangolinVpnService.kt</code>(VpnService) + libbox.aar;<code>DefaultNetworkMonitor.kt</code> 网络监听;<code>VpnEventBus.kt</code> 事件桥</td>
|
||
<td><span class="tag t-bad">零</span> 原生层无自动化测试</td>
|
||
<td>VpnService 授权弹窗、TUN fd 建立、libbox 启动、断网重连、统计回传桥;真机连通 + 出网</td></tr>
|
||
<tr><td class="feat">iOS</td>
|
||
<td><code>VpnManager.swift</code>(NEPacketTunnelProvider) + 内嵌 libbox;<code>StatsClient.swift</code> 统计</td>
|
||
<td><span class="tag t-bad">零</span> 原生层无自动化测试</td>
|
||
<td>NE 内存 ≤50MB 闸、NE profile 安装授权、libbox 后台队列启动、TestFlight 分发;真机连通 + 出网</td></tr>
|
||
<tr><td class="feat">iPad</td>
|
||
<td>同 iOS 二进制;额外横屏侧栏布局</td>
|
||
<td><span class="tag t-warn">部分</span> 布局走 golden(<code>tablet_pages</code>),隧道同 iOS <span class="tag t-bad">零</span></td>
|
||
<td>同 iOS + 横屏/分屏多任务下隧道与 UI;真机连通 + 出网</td></tr>
|
||
<tr><td class="feat">macOS</td>
|
||
<td><b>默认</b>子进程(<code>DesktopVpnBridge</code>+sing-box);<b>可选</b> PacketTunnel 系统扩展(站外 Developer ID + 公证)</td>
|
||
<td><span class="tag t-warn">部分</span> 子进程查找/启停有 <code>kernel_process_test · desktop_vpn_bridge_m4m5_test</code>;sysext realize <span class="tag t-bad">零</span></td>
|
||
<td>sysext 能否被 sysextd realize(见<a href="macos-sysext-realize-troubleshooting.html">踩坑复盘</a>)、CFBundleVersion 递增、公证/staple、三方死锁规避;真机连通 + 出网</td></tr>
|
||
<tr><td class="feat">Windows</td>
|
||
<td><code>sing-box.exe</code> 子进程 + <code>wintun.dll</code> TUN;<code>kernel_process.dart</code> 管理</td>
|
||
<td><span class="tag t-warn">部分</span> 进程管理逻辑同 <code>kernel_process_test</code>(跨平台共用);wintun/打包 <span class="tag t-bad">零</span></td>
|
||
<td>wintun.dll 随 exe 落位、TUN 适配器创建、UAC 提权、安装包;真机连通 + 出网</td></tr>
|
||
</table>
|
||
|
||
<h3 id="acceptance">每端真连通验收清单(同一张表,逐端打勾)</h3>
|
||
<p>第 1–7 步对五端是<b>同一份判据</b>,差异只在第 8 步平台专项。用<b>客观信号</b>代替「看着像连上了」。当前节点:<code>107.172.55.251</code>(REALITY 443)。</p>
|
||
<div class="ck">
|
||
<ul>
|
||
<li><span class="box">☐</span> <b>1 流量真走节点</b> · 连前/连后各查出口 IP(<code class="kbd">curl ifconfig.me</code> / ip.sb)。必须从本地 IP 变成节点 IP——<span class="crit">没变 = 隧道没真接管</span>,最易自欺的一步。</li>
|
||
<li><span class="box">☐</span> <b>2 DNS 劫持 + 路由对</b> · 浏览器开 youtube / google 能通 → 证明 <code>hijack-dns</code> 首条规则在设备上真生效(缺它「连上也打不开网站」)。</li>
|
||
<li><span class="box">☐</span> <b>3 国内分流不绕道</b> · 开 bilibili 等国内站走直连不进隧道(<code>split_cn</code>)。</li>
|
||
<li><span class="box">☐</span> <b>4 统计记账对</b> · 跑一段已知流量,对客户端统计页 GB/时长 vs 节点端 v2ray 计数器(咬合 §A.2,验 stats-overhaul 是否真对的天然 E2E)。</li>
|
||
<li><span class="box">☐</span> <b>5 断网保护 KillSwitch</b> · 隧道中途断开,确认无明文泄漏(分级见 <a href="killswitch-design.html">KillSwitch 设计</a>;TUN <code>strict_route</code> 已渲染)。</li>
|
||
<li><span class="box">☐</span> <b>6 切节点</b> · 切换后出口 IP 跟着变、不掉线、不卡 connecting。</li>
|
||
<li><span class="box">☐</span> <b>7 重连 / 网络切换</b> · Wi-Fi↔蜂窝切换、息屏/休眠恢复后隧道自愈。</li>
|
||
<li><span class="box">☐</span> <b>8 平台专项</b>:
|
||
<ul style="margin-top:4px">
|
||
<li>iOS/iPad:NE 内存 ≤50MB(Instruments)· profile 授权弹窗 · 后台保活</li>
|
||
<li>macOS(sysext):sysextd realize 成功 · 已公证 · CFBundleVersion 已递增</li>
|
||
<li>macOS(子进程)/Windows:内核子进程提权(sudo/UAC)· 退出清理 TUN</li>
|
||
<li>Android:VpnService 授权弹窗 · 厂商省电杀后台白名单</li>
|
||
<li>Windows:wintun.dll 落位 · TUN 适配器创建 · 安装包</li>
|
||
</ul>
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
<p class="note"><b>半自动化建议:</b>第 1–3 步(出口 IP / 站点可达 / 分流判定)可写成一个连通探针脚本自动跑,把客观判据固化下来,只留第 4–8 步手测——这是把 §B 盲区往自动化推进的最小一步。</p>
|
||
|
||
<h3>下一步「功能更完善」可补的自动化(从盲区往里推)</h3>
|
||
<ul>
|
||
<li><b>原生↔Dart 统计契约</b>:给 <code>pangolin/vpn/stats</code> MethodChannel 两侧加契约测试(防「mac 统计恒为 0」复发)——这是 §B 里<b>唯一能自动化</b>的一块,性价比最高。</li>
|
||
<li><b>连通探针脚本</b>:固化验收清单第 1–3 步(出口 IP 变化 / 墙外站可达 / 国内站直连),CI 之外按需跑真节点。</li>
|
||
<li><b>记账对账</b>:stats-overhaul 合并后,补 e2e <code>/v1/usage/devices</code> 分设备断言 + 客户端↔节点计数对账口径。</li>
|
||
<li><b>真 sing-box 烟测</b>(重,可选):起真 sing-box 吃渲染配置,验端口监听 + v2ray_api 真计数,替换当前假 StatsService 的一段。</li>
|
||
<li><b>资产 drift 闸</b>(见 §A.3 设计契约):仿 <code>ci/check-codegen-drift.sh</code>,给 <b>logo SVG 双拷贝(design↔client)</b> 和 <b>app-icon 生成物</b> 各加比对闸,把「图标 logo 五端同源」从靠纪律升成靠闸——token 已有、资产尚缺。</li>
|
||
<li><b>Flutter 依从性扫描闸</b>(见 §A.3):仿 <code>ci/scan-portable-sql.sh</code>,正则扫 <code>lib/screens</code>+<code>lib/widgets</code> 的 raw 颜色/字号/间距字面量(第①档),命中即红,守「screen 真用真相源」——需先清存量 62+ 处或白名单冻结增量守门。</li>
|
||
<li><b>闸反向 case 自检</b>(见 §A.3):给依从性闸 + <code>codegen-drift</code> 各配一个违规 fixture,CI 跑「扫它必须非零退出」,证明闸非永真——当前所有闸都缺这一类自检。</li>
|
||
</ul>
|
||
|
||
<p class="small" style="margin-top:40px">维护:新功能上线时同步更新本表对应行的「覆盖」标签与「欠缺」列;与 <a href="test-architecture.html">test-architecture.html ⑩ 已知缺口</a> 互为索引——本表是「逐功能」视角,那里是「分层方法论 + 盲区跟踪」视角。</p>
|
||
|
||
</div>
|
||
</body>
|
||
</html>
|