逐功能盘点:现有能力 → 已覆盖测试(怎么覆盖)→ 欠缺 → 只能人工。作为「验证五个终端是否正确」的对照工作表。
「覆盖」列给的是当前状态,不是目标;标 零/部分 的「欠缺」列写清下一步补什么。标 人工 的不是缺陷,是真设备/真出网/真链路这类自动化跑不动、必须人测的部分——集中收在 §A.4 / §B 验收清单。
这部分代码四端共用(Flutter 业务逻辑 / Go 控制面 / 数据面渲染),自动化跑一次即对五个终端同时生效。验证策略:能自动化的全压到 CI,逐端不重复测。
server/internal/*)账号 / 鉴权
| 功能 | 现有能力 | 已覆盖 + 怎么覆盖 | 欠缺 / 只能人工 |
|---|---|---|---|
| 注册 / 登录 / 刷新 自动 | 邮箱注册、密码登录、refresh token 轮换、登出;/v1/auth/{register,login,refresh,code} |
auth/service_test · handler_test · token_test · integration_test:真库(SQLite)跑完整 register→login→refresh 链;token 签发/校验/过期纯函数单测;password_test 哈希;helpers_test |
真邮件投递(SMTP)只 mock;账号枚举/暴力破解的真实速率压测仅 ratelimit_test 逻辑级 |
| 邮箱验证码 自动 | 发码、校验、节流;emailcheck |
auth/emailcheck_test · ratelimit_test:码生成/校验/节流窗口逻辑断言 |
真 SMTP 通道、到达率、垃圾箱判定 → 人工 |
| 两步验证 TOTP 自动 | TOTP 绑定/校验、备份码 | totp/totp_test(算法)+ auth/totp_user_test(绑定/校验流程) |
真 Authenticator app 互操作 → 人工(一次性) |
| 管理后台 自动 | admin 登录/会话、IP 白名单、配置、加密 | admin/{auth,session,mw_ipallow,config,crypto,handlers,services_real}_test:会话签发、IP allowlist 中间件、handler 行为 |
后台前端页面交互 → 人工 |
业务 / 计费
| 功能 | 现有能力 | 已覆盖 + 怎么覆盖 | 欠缺 / 只能人工 |
|---|---|---|---|
| 兑换码 / 套餐 自动 | 码生成、兑换、套餐授予;/v1/redeem · /v1/plans |
codes/{generator,service}_test:码生成唯一性、兑换幂等、过期/已用拒绝 |
— |
| 支付 webhook 部分 | 第三方支付回调 → 充值/开通 | codes/webhook_test:回调签名校验 + 入账逻辑(mock 上游) |
真支付渠道端到端(下单→回调→开通)仅 mock;真渠道沙箱 → 人工 |
| 激励解锁配额 自动 | 看广告解锁额度;/v1/ads/unlock |
usage/ads_test:解锁额度计算/上限 |
真广告 SDK 回调 → 人工 |
| 设备管理 自动 | 设备注册/列举/解绑、上限;/v1/me/devices |
devices/service_test · devices_integration_test:真库注册→列举→解绑;注册回填 last_seen(修过的真 bug) |
— |
节点 / 调度 / 供给
| 功能 | 现有能力 | 已覆盖 + 怎么覆盖 | 欠缺 / 只能人工 |
|---|---|---|---|
| 节点注册表 + 连接 自动 | 列节点、连接/断开、ping;/v1/nodes · /connect · /disconnect |
nodes/{grpc,hub,lifecycle}_test:gRPC hub 收发、节点生命周期状态机 |
真节点延迟/可用性探测准确度 → 见数据面 §A.2 |
| Agent mTLS 接入 自动 | CA 签发、CRL 吊销、节点身份、bootstrap token | mtls/{ca,crl,identity,bootstrap}_test + nodectl/bootstrap_token_test:证书链签发/校验/吊销 |
真 mTLS 握手在 §A.2 e2e 覆盖一段 |
| 自动调度 / 故障替换 自动 | 探测引擎、熔断、容量、自动替换编排、探针接入 | scheduler/detect/engine_test · orchestrate/{breaker,capacity,config,replacer}_test · probe/{ingest,prober_agent}_test · wiring_{lifecycle,provision}_test:健康判定→熔断→替换决策全链逻辑 |
真云厂商触发的端到端替换 → 人工(贵/慢) |
| 云供给 provision 部分 | cloud-init 渲染、provider 注册表、节点替换 | provision/{cloudinit,replace,service}_test · providers/registry_test(fakes 注入) |
真厂商 API 开机/销毁 → 人工(按需,烧钱) |
| 告警 自动 | Telegram 通知、runbook | alert/{notifier,runbook}_test:触发条件 + 去抖逻辑 |
真 Telegram 投递 → 人工 |
持久层 / 可移植性
| 功能 | 现有能力 | 已覆盖 + 怎么覆盖 | 欠缺 / 只能人工 |
|---|---|---|---|
| 双数据库 SQLite/MySQL 自动 | 裸 SQL + 方言层;迁移分两套;upsert/行锁中性记法 | db/dialect_test · sqlite_smoke_test;store/{sqlite_stores,sqlite_migrate,sqlite_per_device,mysql,mysql_integration}_test:SQLite 实库 + MySQL testcontainers 真库跑同一套断言;ci/scan-portable-sql.sh 扫禁用 MySQL 专属构造 |
真 512MB VPS 上的并发/锁竞争压测 → 人工 |
httpapi/clientconfig · agentd/*)「连上了能不能真出网、记账准不准」的源头逻辑。渲染/解析有测试,真链路靠 e2e 一段 + 人工兜底。
| 功能 | 现有能力 | 已覆盖 + 怎么覆盖 | 欠缺 / 只能人工 |
|---|---|---|---|
| 客户端配置渲染 自动 | BuildClientConfig 服务端渲染原样下发:REALITY 出站、TUN 入站(strict_route 杀开关)、DNS 劫持首条规则、国内分流 |
httpapi/clientconfig_test:断言 REALITY 公钥/short-id/端口、TUN auto_route/strict_route、hijack-dns 规则排在 LAN 前、split_cn 开关 |
渲染对 ≠ 内核吃得下:真 sing-box 加载该配置并连通 → §B 人工 |
| 节点配置渲染(agent 侧)自动 | agent 渲染 sing-box 服务端配置 + 凭证增删/轮换/吊销 | agentd/singbox_test:Upsert/Revoke/Rotate/ApplyConfig 状态机;derive_test 数据口凭证派生;hy2cert_test 证书;command_test |
真 systemctl restart sing-box 后端口真监听 → 人工 |
| per-user 流量采集 部分 | V2RayUsageSource:agent 读 sing-box v2ray_api StatsService per-user 计数器 → 聚合 → ReportUsage |
新增 agentd/usage_v2ray_test:表驱动 parseUserStat + loopback 假 StatsService 跑真 Collect(),验聚合/方向不串/全0丢弃/reset=true 窗口语义 |
假 StatsService ≠ 真 sing-box:真出网流量经真 v2ray_api 的计数准确度 → 人工 |
| 记账全链 e2e 部分 | enroll → ReportUsage → 统计入库 → API 读出 | server/test/e2e/smoke_test:进程级 gRPC 全链路(agent enroll→上报→控制面统计真入库真读出),HTTP 段 + miniredis |
缺 /v1/usage/devices 分设备断言(stats-overhaul 合并后补回);真节点真流量不在 e2e |
| REALITY 数据口连通 人工 | 客户端经 REALITY 连节点 443 真出网 | —(握手是真协议真节点,自动化跑不动) | 真握手成功 + 出网 → §B 每端验收清单第 1–2 步 |
client/lib/*,四端共用 Dart)| 功能 | 现有能力 | 已覆盖 + 怎么覆盖 | 欠缺 / 只能人工 |
|---|---|---|---|
| API 客户端 + 契约 自动 | ApiClient 取数、错误映射;/v1/me · usage · usage/devices |
unit/api_client_test;contract/{api_contract,stats_contract}_test 冻结 wire 字段形状;Go 侧 httpapi/contract_test · usage/contract_test · pb/agentv1/contract_test 两侧对齐 |
— |
| 登录 / 续登 / 登出流程 自动 | AuthNotifier 状态机 + token 存储 |
unit/flow_auth_test:真控制器 + MockClient 注入,跑登录→重启续登→登出 |
真 flutter_secure_storage keychain 行为 → 人工(一次性) |
| 连接状态机 自动 | connectionProvider connecting→connected→error;ping 探测 |
unit/{connection_controller,flow_connect}_test:真控制器 + 假桥,验状态流转;connect_passthrough_test |
真隧道回调时序(连上才启动 app 等触发条件)→ §B |
| 统计计算 + 上屏 自动 | 月 GB/时长聚合、周柱、分设备归因;stats_page |
unit/{device_usage,format}_test 解析/格式化;新增 widget/stats_page_test:真 wire 形态喂 StatsPage 断言指标卡/周柱/分设备数值真上屏 |
—(解析对 + 上屏对都已守) |
| 配额 / 节点列表 自动 | quotaProvider · nodesProvider |
unit/{quota_controller,nodes_provider}_test |
— |
| UI 视觉一致性 自动 | 各页面/组件像素还原(亮/暗 × 中/英 × 手机/平板/桌面) | golden/{components,auth_redesign,desktop_pages,tablet_pages}_test Linux 权威基线;widget/{cards,connect_button}_test;responsive/form_factor_test;fonts_test 字体锁 |
真机不同 DPI/字号/深色模式实机观感 → 人工(抽查) |
| 桥接口契约 部分 | VpnBridge 抽象 + 桌面子进程实现 + mock |
bridge/{vpn_bridge_mock,kernel_process,desktop_vpn_bridge_m4m5}_test:内核进程查找/启停、mock 桥行为 |
原生 MethodChannel 两侧真实编解码 → §B(各端原生层) |
| 交互组件(本轮)部分 | 切换节点确认框(连接态防误触,节点行 + 智能选择卡同款);节点页搜索框配色(PangolinFieldBox 修双色缝);showPangolinToast(主显示区内、自适应宽、上限 80%、超长多行居中) |
Windows 人工已验 2026-06-29:确认框两处弹出 + 防误触正常;搜索框配色一致无缝;toast 常规文案显示正常(位置/明暗/动画)。node_connect_confirm_test 守确认框逻辑;toast 走真相源规格 design/CONTRACT.md §2 |
toast 超长文本多行居中 + ≤80% 上限边界未验 待测;其余四端(mac/Android/iOS/iPad)同源未逐端抽查 → 人工 |
设计契约 · 五端 UI 同源
五端跑同一个 Flutter 工程(client/lib 共享 Dart),UI 不按平台分叉——颜色/样式/组件不是「约定一致」,是物理上同一份代码。下表是「五端同源」各维度的单源 + 守门状态。两处关键盲区:① token 有漂移闸,logo/app-icon 资产没有;② 「生成物不漂移」有闸,但「screen 是否真用真相源」(依从性) 零闸、且所有闸无反向 case 自检。
| 维度 | 单源(真相源 → 生成/消费) | 守门机制 | 欠缺 / 风险 |
|---|---|---|---|
| 颜色/间距/圆角/字体 自动 | design/colors_and_type.css(clay/sand 色板)→ gen_flutter_tokens.mjs → pangolin_tokens.gen.dart(勿手改)→ pangolin_theme.dart 语义层;业务禁硬编码 hex |
CI codegen-drift 闸(ci/check-codegen-drift.sh):重生成与提交版不一致即红,防「改 CSS 没重生成 / 手改生成物」 |
—(单源 + 自动闸,最规范的一块) |
| 组件 自动 | client/lib/widgets/ 唯一实现;design/ 禁放 Dart 副本(会漂移);规格在 design/CONTRACT.md + design/preview/ |
golden 测试:组件 + 各页面 × 明/暗 × 中/英 × 手机/平板/桌面,像素回归即红 | — |
| 图标库 自动 | lucide_icons(细线条)全端同一套字体图标 |
随 golden 像素守 | — |
| 品牌 logo(应用内)部分 | design/assets/*.svg(mark / wordmark / app-icon)→ 拷贝一份到 client/assets/,flutter_svg 加载,四端同一份矢量 |
无自动闸——design↔client 双拷贝靠手动同步(当前 4 个 SVG 已 diff 确认一致) |
改 design/assets 那份忘同步 client/assets,CI 静默放过 → 待补 drift 闸 |
| app 启动图标 部分 | assets/app-icon.svg → app-icon-ios-1024.png → flutter_launcher_icons 一键铺五端全尺寸(iOS appiconset / Android mipmap / macOS / Windows .ico) |
无 drift 闸——生成物(各端 PNG/ico,如 iOS 22 张)入库,靠手动 dart run flutter_launcher_icons |
改 app-icon.svg 忘重跑生成,五端图标与源图分叉,CI 静默放过 → 待补 drift 闸 |
| 设计契约文档 人工 | design/CONTRACT.md(design-distill 蒸馏):token 映射 / 组件映射 / 逐屏像素规格 / 逐屏验收清单(明×暗×中×英) |
人读契约;像素侧由 golden 兜底 | 「还原对不对」的逐屏验收是人工抽查(截图 diff 可半自动) |
| screen 依从性 (源自真相源)零 |
要求 screen/widget 的颜色/字号/间距必须源自 PangolinColors/PangolinText/PangolinSpacing,不得硬编码 hex/数值或绕过 widgets/ 自拼组件 |
无任何检测。codegen-drift 只守「生成物=CSS 源」不守消费端是否用它;analysis_options.yaml 无禁硬编码规则;design/_adherence.oxlintrc.json 能抓 raw hex/px 但只对 JSX 原型、warn 级、未接 CI |
实测 lib/screens 已有 62+ 处硬编码 fontSize:/EdgeInsets 数值无人拦(如 account_page.dart:187 fontSize: 16)→ 待补依从性扫描闸(第①档:正则扫 raw 字面量,仿 scan-portable-sql.sh) |
| 闸的反向 case 自检 零 | 每个闸应有「注入违规 → 断言闸变红」的负向自检,证明闸非永真(怎么改都绿 = 形同虚设) | 无任何反向 case(全仓 grep 零结果)——含 codegen-drift 在内的所有闸都没验证过「真能拦住违规」 |
→ 待补 fixture 自检(备一行违规样本,CI 跑「扫它必须非零退出」;新依从性闸与 codegen-drift 都该配) |
关键区别:codegen-drift(生成物不漂移)≠ 依从性(消费端真用真相源)。前者保证真相源本身没被改歪,后者保证 screen 真的去用了真相源——当前只有前者有闸,后者零守门、且所有闸都缺反向 case 自检。规划见 plan:UI 真相源依从性收口(本轮仅登记,补闸后续做)。
待补(性价比高):仿 ci/check-codegen-drift.sh 给 logo SVG 双拷贝 和 app-icon 生成物 各加一个 drift 闸——重新生成/比对 design↔client,不一致即红,把「图标 logo 五端同源」从靠纪律升成靠闸。这是 token 已有、资产尚缺的一行。
真正逐端不同的只有原生隧道实现——这是测试架构 ⑩ 标注的零自动化盲区。三种策略:
NEPacketTunnelProvider + VpnManager.swift)、Android(libbox.aar + PangolinVpnService.kt VpnService)、macOS(PacketTunnel 系统扩展,当前默认关 kUseNativeVpnMacOS=false)DesktopVpnBridge + kernel_process.dart)、Windows(sing-box.exe + wintun.dll)、LinuxVpnEventBus.kt / VpnChannel.swift / StatsClient.swift)传状态与统计——「mac 统计恒为 0」就出在这条没契约守门| 终端 | 隧道实现 | 已覆盖 | 欠缺 / 验证重点 |
|---|---|---|---|
| Android | PangolinVpnService.kt(VpnService) + libbox.aar;DefaultNetworkMonitor.kt 网络监听;VpnEventBus.kt 事件桥 |
零 原生层无自动化测试 | VpnService 授权弹窗、TUN fd 建立、libbox 启动、断网重连、统计回传桥;真机连通 + 出网 |
| iOS | VpnManager.swift(NEPacketTunnelProvider) + 内嵌 libbox;StatsClient.swift 统计 |
零 原生层无自动化测试 | NE 内存 ≤50MB 闸、NE profile 安装授权、libbox 后台队列启动、TestFlight 分发;真机连通 + 出网 |
| iPad | 同 iOS 二进制;额外横屏侧栏布局 | 部分 布局走 golden(tablet_pages),隧道同 iOS 零 |
同 iOS + 横屏/分屏多任务下隧道与 UI;真机连通 + 出网 |
| macOS | 默认子进程(DesktopVpnBridge+sing-box);可选 PacketTunnel 系统扩展(站外 Developer ID + 公证) |
部分 子进程查找/启停有 kernel_process_test · desktop_vpn_bridge_m4m5_test;sysext realize 零 |
sysext 能否被 sysextd realize(见踩坑复盘)、CFBundleVersion 递增、公证/staple、三方死锁规避;真机连通 + 出网 |
| Windows | sing-box.exe 子进程 + wintun.dll TUN;kernel_process.dart 管理 |
部分 进程管理逻辑同 kernel_process_test(跨平台共用);wintun/打包 零 |
wintun.dll 随 exe 落位、TUN 适配器创建、UAC 提权、安装包;真机连通 + 出网 |
第 1–7 步对五端是同一份判据,差异只在第 8 步平台专项。用客观信号代替「看着像连上了」。当前节点:107.172.55.251(REALITY 443)。
curl ifconfig.me / ip.sb)。必须从本地 IP 变成节点 IP——没变 = 隧道没真接管,最易自欺的一步。hijack-dns 首条规则在设备上真生效(缺它「连上也打不开网站」)。split_cn)。strict_route 已渲染)。半自动化建议:第 1–3 步(出口 IP / 站点可达 / 分流判定)可写成一个连通探针脚本自动跑,把客观判据固化下来,只留第 4–8 步手测——这是把 §B 盲区往自动化推进的最小一步。
pangolin/vpn/stats MethodChannel 两侧加契约测试(防「mac 统计恒为 0」复发)——这是 §B 里唯一能自动化的一块,性价比最高。/v1/usage/devices 分设备断言 + 客户端↔节点计数对账口径。ci/check-codegen-drift.sh,给 logo SVG 双拷贝(design↔client) 和 app-icon 生成物 各加比对闸,把「图标 logo 五端同源」从靠纪律升成靠闸——token 已有、资产尚缺。ci/scan-portable-sql.sh,正则扫 lib/screens+lib/widgets 的 raw 颜色/字号/间距字面量(第①档),命中即红,守「screen 真用真相源」——需先清存量 62+ 处或白名单冻结增量守门。codegen-drift 各配一个违规 fixture,CI 跑「扫它必须非零退出」,证明闸非永真——当前所有闸都缺这一类自检。维护:新功能上线时同步更新本表对应行的「覆盖」标签与「欠缺」列;与 test-architecture.html ⑩ 已知缺口 互为索引——本表是「逐功能」视角,那里是「分层方法论 + 盲区跟踪」视角。