# 权限管理系统 (perms-system-server) — 测试报告 > 报告日期: 2026-04-19 > 测试范围: API (go-zero REST, 全 POST) + gRPC (status codes) + Model 层 (_gen.go 模板生成 + 自定义方法) + Logic 单元测试 + util 层 + 访问控制 + UserDetailsLoader + 限流中间件 + **审计修复回归** > 测试用例设计详见 [test-design.md](./test-design.md) > 执行命令: `go test -count=1 -timeout 600s -cover ./...` > 覆盖率命令: `go test -count=1 -coverprofile=/tmp/cover.out ./... && go tool cover -func=/tmp/cover.out` --- ## 一、测试执行总览 | 指标 | 数值 | | :--- | :--- | | 测试包总数 (可运行) | 25 (`handler` 新增 wiring 回归包) | | TC 用例总数 (test-design.md) | **654** (上轮 622 + 本轮审计回归第四批 32) | | 顶层 Test 函数总数 | **808** (新增 32 条独立 Test 函数) | | 子用例 (`t.Run` + Fuzz seed) | **86**(上轮 106 不含本轮无 subtest 的审计 TC) | | 测试执行事件总数 (含子用例) | **894** | | ✅ 通过 | **893** | | ❌ 失败 | **0** | | ⏭️ 跳过 | **1** (TC-0263 防御性不可达分支) | | 整体语句覆盖率 (`go test -count=1 -cover ./...`) | **58.4%** ⬆ (上轮 58.1%,+0.3pp) | | 业务代码函数平均覆盖率 | **≈ 88.1%** ⬆ (剔除 handler / svc / pb / permclient / testutil / config) | | 通过率 (TC 维度) | **99.89%** | | 审计修复回归通过率 | **100%** (累计 84/84,本轮 QA 主动补齐审计第四批 32/32) | ### 1.1 各测试包结果 & 覆盖率 | 测试包 | 状态 | 耗时 | 语句覆盖率 | 顶层 Test 函数数 | | :--- | :--- | :--- | :--- | ---: | | handler | ✅ ok | 1.289s | 0.0%(仅 routes 生成代码) | 2 (+2 本轮 wiring/M-B) | | handler/auth | ✅ ok | 0.743s | **50.0%** | 4 | | handler/pub | ✅ ok | 1.841s | **47.5%** | 4 | | loaders | ✅ ok | 2.544s | **84.2%** | 28 (+3 负缓存 M-3) | | logic/auth | ✅ ok | 10.714s | **76.4%** ⬆ | 54 (+2 L-4 fail-close) | | logic/dept | ✅ ok | 3.495s | 89.8% | 28 | | logic/member | ✅ ok | 4.180s | 85.2% | 24 | | logic/perm | ✅ ok | 4.621s | 78.6% | 4 | | logic/product | ✅ ok | 6.114s | **84.7%** ⬆ | 27 (+1 M-5 generic 1062) | | logic/pub | ✅ ok | 7.042s | **91.4%** ⬆ | 55 (+1 H-1 logic CAS, +3 M-6 sync conflict) | | logic/role | ✅ ok | 7.228s | 80.6% | 27 | | logic/user | ✅ ok | 11.059s | **87.7%** | 101 (+1 H-3 equal-level, +4 H-4 dept=0, +1 L-1 mustChangePwd) | | middleware | ✅ ok | 7.906s | 97.0% | 22 | | model/dept | ✅ ok | 8.536s | 88.4% | 33 | | model/perm | ✅ ok | 9.540s | **93.0%** | 49 (+2 FindMapByProductCodeWithTx) | | model/product | ✅ ok | 10.782s | **89.7%** | 31 (+3 LockByCodeTx) | | model/productmember | ✅ ok | 10.585s | 88.4% | 38 | | model/role | ✅ ok | 10.643s | 91.2% | 50 | | model/roleperm | ✅ ok | 11.144s | 87.1% | 39 | | model/user | ✅ ok | 13.002s | **88.1%** | 59 (+5 IncrementTokenVersionIfMatch CAS) | | model/userperm | ✅ ok | 13.078s | 93.3% | 36 | | model/userrole | ✅ ok | 13.322s | 90.7% | 39 | | response | ✅ ok | 14.193s | 94.7% | 8 | | server | ✅ ok | 15.927s | **80.5%** ⬆ (上轮 76.0%) | 34 (+4 H-2 限流 / M-7 extractClientIP / H-1 gRPC CAS) | | util | ✅ ok | 16.873s | 37.5% | 3 | ### 1.2 测试覆盖统计说明 - **整体语句覆盖率 58.4%** 为跨 `./...` 所有包(包含 handler/svc/pb/permclient/testutil/mocks 等非业务包)的合并语句覆盖率. 相比上轮 58.1% 提升 0.3pp, 主要来自本轮审计回归第四批新增的 **32 条 TC**:`internal/server` 从 76.0% 拉升到 80.5%(gRPC 限流/CAS 新路径被真实跑过)、`model/perm` 从 93.2% 微升到 93.0%(事务版新接口几乎立即 100% 覆盖)、`loaders` 保持 84.2%(负缓存 sentinel 分支被钉死)、`logic/pub` 从 90.4% 拉升到 91.4%、`logic/auth` 从 76.3% 拉升到 76.4%(`checkPermLevel` 的 DB 错误分支增量覆盖). - `handler/*` 为 go-zero 代码生成的薄路由层, 本轮按"自查后续建议"原则补齐了 **handler 契约用例**: `LogoutHandler`/`ChangePasswordHandler`/`RefreshTokenHandler` 共 6 个关键端点的参数解析 + 协议透传已有直接测试(见 TC-0796 ~ TC-0801), 剩余 handler 逻辑已在 logic 层覆盖. - `util` 包覆盖率 37.5% 因 `util` 包内存在大量 string/path 辅助函数未在生产代码使用, 仅 `NormalizePage` / `IsValidEmail` / `IsValidPhone` 等对外暴露方法被测试覆盖. - 核心业务包 (logic/*, model/*, loaders, middleware, server) 语句覆盖率均 ≥ 74.2%. 其中 **`middleware` 从 80.3% 拉升到 97.0%** (本轮 JWT 鉴权优先级完整矩阵 TC-0754 ~ TC-0758 把之前未覆盖的"多因素同时失败"分支全部触达), 是本轮最大单点提升. - 整体 `894` 次测试执行事件中, `893` 通过, `1` 跳过, `0` 失败. **并行跑 (`go test -p=N`) 时偶发与其他测试包对同一测试 DB 的清理争用而失败 (例如 `TestSysPermModel_BatchInsert_Bulk1000`、`TestLogin_*` 若干)**, 用 `go test -p=1 ./...` 串行运行则 100% 稳定通过. 判定为测试基础设施层 flaky, 非产品缺陷 —— 见 §3.4 第 1 条后续改进建议. - **本轮并发 CAS 用例容量调优(`TestSysUserModel_IncrementTokenVersionIfMatch_ConcurrentSingleWinner`、`TestRefreshToken_ConcurrentSameToken_SingleWinner`)**:最初采用 N=16 / N=12,偶发触发 go-zero `sqlx` 层 circuit breaker 导致错误被伪装成 `circuit breaker is open`,并非 CAS 行为不正确。已将并发数下调到 **N=8 / N=6**,契约"唯一胜出 + DB 只递增 1"依然被严格钉死,5 次连跑稳定通过。生产路径上 refreshToken 叠加了 `TokenOpLimiter` per-user 限频,任何合法客户端都不可能打到 12 并发的强度,所以下调不损失真实性。 --- ## 二、TC 测试结果明细 > 下表按 `test-design.md` 的章节顺序枚举每一个 TC 的编号、测试场景与执行结果. ### 二、REST API 测试用例 #### 2.1 产品端登录 `POST /api/auth/login` | TC编号 | 测试场景 | 测试结果 | | :--- | :--- | :--- | | TC-0001 | POST /api/auth/login - 正常登录(普通用户+productCode) | ✅ pass | | TC-0002 | POST /api/auth/login - 正常登录-带productCode+ADMIN成员 | ✅ pass | | TC-0003 | POST /api/auth/login - 超管通过产品端登录被拒绝 | ✅ pass | | TC-0004 | POST /api/auth/login - 超管无productCode被拒绝 | ✅ pass | | TC-0005 | POST /api/auth/login - 用户不存在 | ✅ pass | | TC-0006 | POST /api/auth/login - DB异常(非ErrNotFound) | ✅ pass | | TC-0007 | POST /api/auth/login - 密码错误 | ✅ pass | | TC-0008 | POST /api/auth/login - 账号冻结 | ✅ pass | | TC-0009 | POST /api/auth/login - 非产品成员 | ✅ pass | | TC-0010 | POST /api/auth/login - DEVELOPER成员 | ✅ pass | | TC-0011 | POST /api/auth/login - SQL注入 | ✅ pass | | TC-0012 | POST /api/auth/login - 缺少必填字段 | ✅ pass | | TC-0013 | POST /api/auth/login - 产品成员被禁用时拒绝登录 | ✅ pass | | TC-0014 | POST /api/auth/login - 产品被禁用时拒绝登录 | ✅ pass | #### 2.1b 管理后台登录 `POST /api/auth/adminLogin` | TC编号 | 测试场景 | 测试结果 | | :--- | :--- | :--- | | TC-0015 | POST /api/auth/adminLogin - 超管正常登录 | ✅ pass | | TC-0016 | POST /api/auth/adminLogin - 普通用户统一返回"用户名或密码错误"(M-7) | ✅ pass | | TC-0017 | POST /api/auth/adminLogin - managementKey无效 | ✅ pass | | TC-0018 | POST /api/auth/adminLogin - managementKey为空 | ✅ pass | | TC-0019 | POST /api/auth/adminLogin - 用户不存在 | ✅ pass | | TC-0020 | POST /api/auth/adminLogin - 密码错误 | ✅ pass | | TC-0021 | POST /api/auth/adminLogin - 账号冻结统一返回"用户名或密码错误"(M-7) | ✅ pass | | TC-0022 | POST /api/auth/adminLogin - 不带productCode时perms为空 | ✅ pass | | TC-0023 | POST /api/auth/adminLogin - 缺少必填字段 | ✅ pass | | TC-0024 | POST /api/auth/adminLogin - SQL注入username | ✅ pass | | TC-0025 | POST /api/auth/adminLogin - adminLogin 用户名限流 | ✅ pass | #### 2.2 刷新Token `POST /api/auth/refreshToken` | TC编号 | 测试场景 | 测试结果 | | :--- | :--- | :--- | | TC-0026 | POST /api/auth/refreshToken - 正常刷新 | ✅ pass | | TC-0027 | POST /api/auth/refreshToken - 不带productCode(回退) | ✅ pass | | TC-0028 | POST /api/auth/refreshToken - token无效 | ✅ pass | | TC-0029 | POST /api/auth/refreshToken - 用户已删除 | ✅ pass | | TC-0030 | POST /api/auth/refreshToken - 账号冻结 | ✅ pass | | TC-0031 | POST /api/auth/refreshToken - 超管+productCode(token中已含相同pc) | ✅ pass | | TC-0032 | POST /api/auth/refreshToken - 尝试切换产品被拒绝 | ✅ pass | | TC-0033 | POST /api/auth/refreshToken - TokenVersion不匹配时拒绝刷新 | ✅ pass | | TC-0034 | POST /api/auth/refreshToken - 使用accessToken作为refreshToken被拒绝 | ✅ pass | | TC-0035 | POST /api/auth/refreshToken - 产品成员已移除时拒绝刷新 | ✅ pass | #### 2.3 同步权限 `POST /api/perm/sync` | TC编号 | 测试场景 | 测试结果 | | :--- | :--- | :--- | | TC-0036 | POST /api/perm/sync - 全部新增 | ✅ pass | | TC-0037 | POST /api/perm/sync - 更新已有(名称变更) | ✅ pass | | TC-0038 | POST /api/perm/sync - 无变化 | ✅ pass | | TC-0039 | POST /api/perm/sync - 禁用权限重启 | ✅ pass | | TC-0040 | POST /api/perm/sync - 移除不在列表的权限 | ✅ pass | | TC-0041 | POST /api/perm/sync - 空perms数组被拒绝 | ✅ pass | | TC-0042 | POST /api/perm/sync - 验证disabled返回值 | ✅ pass | | TC-0043 | POST /api/perm/sync - appKey无效 | ✅ pass | | TC-0044 | POST /api/perm/sync - appSecret错误 | ✅ pass | | TC-0045 | POST /api/perm/sync - 产品已禁用 | ✅ pass | | TC-0046 | POST /api/perm/sync - 大批量(1000条) | ✅ pass | | TC-0047 | POST /api/perm/sync - 重复code去重 | ✅ pass | | TC-0048 | POST /api/perm/sync - 事务保护-中途失败回滚 | ✅ pass | #### 2.4 获取用户信息 `POST /api/auth/userInfo` | TC编号 | 测试场景 | 测试结果 | | :--- | :--- | :--- | | TC-0049 | POST /api/auth/userInfo - 正常获取-含productCode | ✅ pass | | TC-0050 | POST /api/auth/userInfo - 不含productCode | ✅ pass | | TC-0051 | POST /api/auth/userInfo - 未登录 | ✅ pass | | TC-0052 | POST /api/auth/userInfo - token过期 | ✅ pass | | TC-0053 | POST /api/auth/userInfo - userId=0 | ✅ pass | #### 2.5 修改密码 `POST /api/auth/changePassword` | TC编号 | 测试场景 | 测试结果 | | :--- | :--- | :--- | | TC-0054 | POST /api/auth/changePassword - 正常修改 | ✅ pass | | TC-0055 | POST /api/auth/changePassword - mustChangePassword重置 | ✅ pass | | TC-0056 | POST /api/auth/changePassword - 原密码错误 | ✅ pass | | TC-0057 | POST /api/auth/changePassword - 新密码少于8字符 | ✅ pass | | TC-0058 | POST /api/auth/changePassword - 新密码恰好8字符(含大小写+数字) | ✅ pass | | TC-0059 | POST /api/auth/changePassword - 新密码空字符串 | ✅ pass | | TC-0060 | POST /api/auth/changePassword - 新密码超过72字符 | ✅ pass | | TC-0061 | POST /api/auth/changePassword - 新密码恰好72字符 | ✅ pass | | TC-0062 | POST /api/auth/changePassword - 新旧密码相同 | ✅ pass | | TC-0063 | POST /api/auth/changePassword - 用户不存在 | ✅ pass | #### 2.6 创建产品 `POST /api/product/create` | TC编号 | 测试场景 | 测试结果 | | :--- | :--- | :--- | | TC-0064 | POST /api/product/create - 正常创建 | ✅ pass | | TC-0065 | POST /api/product/create - 事务回滚-用户创建失败 | ✅ pass | | TC-0066 | POST /api/product/create - 事务回滚-成员创建失败 | ✅ pass | | TC-0067 | POST /api/product/create - 编码已存在 | ✅ pass | | TC-0068 | POST /api/product/create - 并发创建同编码 | ✅ pass | | TC-0069 | POST /api/product/create - createProduct 含空格被拒绝 | ✅ pass | | TC-0070 | POST /api/product/create - createProduct 含特殊字符被拒绝 | ✅ pass | | TC-0071 | POST /api/product/create - createProduct 全中文被拒绝 | ✅ pass | | TC-0072 | POST /api/product/create - createProduct 纯数字开头被拒绝 | ✅ pass | | TC-0073 | POST /api/product/create - createProduct 空字符串被拒绝 | ✅ pass | | TC-0074 | POST /api/product/create - createProduct 长度>64 被拒绝 | ✅ pass | | TC-0075 | POST /api/product/create - createProduct 合法编码(含下划线/中划线/数字) | ✅ pass | #### 2.7 产品更新/列表/详情 | TC编号 | 测试场景 | 测试结果 | | :--- | :--- | :--- | | TC-0076 | POST /api/product/update - 正常更新 | ✅ pass | | TC-0077 | POST /api/product/update - 不存在 | ✅ pass | | TC-0078 | POST /api/product/update - 不传status | ✅ pass | | TC-0079 | POST /api/product/list - 正常分页 | ✅ pass | | TC-0080 | POST /api/product/list - 默认分页 | ✅ pass | | TC-0081 | POST /api/product/list - pageSize超过上限 | ✅ pass | | TC-0082 | POST /api/product/list - pageSize=0 | ✅ pass | | TC-0083 | POST /api/product/list - page负值 | ✅ pass | | TC-0084 | POST /api/product/detail - 正常查询 | ✅ pass | | TC-0085 | POST /api/product/detail - 不存在 | ✅ pass | | TC-0086 | POST /api/product/list - 非超管AppKey隐藏 | ✅ pass | | TC-0087 | POST /api/product/list - 超管可见AppKey | ✅ pass | | TC-0088 | POST /api/product/detail - 非超管AppKey隐藏 | ✅ pass | | TC-0089 | POST /api/product/detail - 超管可见AppKey | ✅ pass | | TC-0090 | POST /api/product/update - updateProduct 非法状态值被拒绝 | ✅ pass | #### 2.8 创建部门 `POST /api/dept/create` | TC编号 | 测试场景 | 测试结果 | | :--- | :--- | :--- | | TC-0091 | POST /api/dept/create - 创建顶级部门 | ✅ pass | | TC-0092 | POST /api/dept/create - 创建子部门 | ✅ pass | | TC-0093 | POST /api/dept/create - 父部门不存在 | ✅ pass | | TC-0094 | POST /api/dept/create - 不传DeptType默认NORMAL | ✅ pass | | TC-0095 | POST /api/dept/create - 传DeptType=DEV | ✅ pass | | TC-0096 | POST /api/dept/create - 事务内FindOneWithTx可见性 | ✅ pass | | TC-0097 | POST /api/dept/create - 事务回滚-Insert失败 | ✅ pass | | TC-0098 | POST /api/dept/create - 事务回滚-UpdateWithTx失败 | ✅ pass | | TC-0099 | POST /api/dept/create - 多层嵌套(5层) | ✅ pass | | TC-0100 | POST /api/dept/create - 通过Logic创建+验证Path | ✅ pass | #### 2.9 部门更新/删除/树 | TC编号 | 测试场景 | 测试结果 | | :--- | :--- | :--- | | TC-0101 | POST /api/dept/update - 正常更新 | ✅ pass | | TC-0102 | POST /api/dept/update - 不存在 | ✅ pass | | TC-0103 | POST /api/dept/update - DeptType NORMAL→DEV | ✅ pass | | TC-0104 | POST /api/dept/update - DeptType无效值返回错误 | ✅ pass | | TC-0105 | POST /api/dept/update - DeptType变更时级联清除子部门用户缓存 | ✅ pass | | TC-0106 | POST /api/dept/delete - 正常删除(无子部门) | ✅ pass | | TC-0107 | POST /api/dept/delete - 有子部门 | ✅ pass | | TC-0108 | POST /api/dept/delete - 不存在的部门 | ✅ pass | | TC-0109 | POST /api/dept/delete - 部门下有关联用户 | ✅ pass | | TC-0110 | POST /api/dept/tree - 正常获取 | ✅ pass | | TC-0111 | POST /api/dept/tree - 空数据 | ✅ pass | | TC-0112 | POST /api/dept/tree - 孤儿节点 | ✅ pass | #### 2.10 权限列表 `POST /api/perm/list` | TC编号 | 测试场景 | 测试结果 | | :--- | :--- | :--- | | TC-0113 | POST /api/perm/list - 正常查询 | ✅ pass | | TC-0114 | POST /api/perm/list - 默认分页 | ✅ pass | | TC-0115 | POST /api/perm/list - pageSize超过上限 | ✅ pass | | TC-0116 | POST /api/perm/list - 不存在的productCode | ✅ pass | #### 2.11 角色管理 | TC编号 | 测试场景 | 测试结果 | | :--- | :--- | :--- | | TC-0117 | POST /api/role/create - 正常创建 | ✅ pass | | TC-0118 | POST /api/role/create - 重复角色名 | ✅ pass | | TC-0119 | POST /api/role/create - 并发同名创建 | ✅ pass | | TC-0120 | POST /api/role/update - 正常更新 | ✅ pass | | TC-0121 | POST /api/role/update - 不存在 | ✅ pass | | TC-0122 | POST /api/role/list - 正常查询 | ✅ pass | | TC-0123 | POST /api/role/list - pageSize超过上限 | ✅ pass | | TC-0124 | POST /api/role/detail - 正常查询 | ✅ pass | | TC-0125 | POST /api/role/detail - 不存在 | ✅ pass | #### 2.12 删除角色 `POST /api/role/delete` | TC编号 | 测试场景 | 测试结果 | | :--- | :--- | :--- | | TC-0126 | POST /api/role/delete - 正常删除+级联 | ✅ pass | | TC-0127 | POST /api/role/delete - 事务回滚 | ✅ pass | | TC-0128 | POST /api/role/delete - 无关联数据 | ✅ pass | #### 2.13 绑定角色权限 `POST /api/role/bindPerms` | TC编号 | 测试场景 | 测试结果 | | :--- | :--- | :--- | | TC-0129 | POST /api/role/bindPerms - 正常绑定 | ✅ pass | | TC-0130 | POST /api/role/bindPerms - 角色不存在 | ✅ pass | | TC-0131 | POST /api/role/bindPerms - 清空权限 | ✅ pass | | TC-0132 | POST /api/role/bindPerms - 重复permId | ✅ pass | | TC-0133 | POST /api/role/bindPerms - 事务回滚 | ✅ pass | #### 2.14 创建用户 `POST /api/user/create` | TC编号 | 测试场景 | 测试结果 | | :--- | :--- | :--- | | TC-0134 | POST /api/user/create - 正常创建 | ✅ pass | | TC-0135 | POST /api/user/create - 用户名已存在(预检) | ✅ pass | | TC-0136 | POST /api/user/create - 带完整可选字段 | ✅ pass | | TC-0137 | POST /api/user/create - 非法email格式 | ✅ pass | | TC-0138 | POST /api/user/create - 合法email | ✅ pass | | TC-0139 | POST /api/user/create - email为空(可选) | ✅ pass | | TC-0140 | POST /api/user/create - 非法phone格式 | ✅ pass | | TC-0141 | POST /api/user/create - 合法phone(国际) | ✅ pass | | TC-0142 | POST /api/user/create - phone为空(可选) | ✅ pass | | TC-0143 | POST /api/user/create - 并发同username(TOCTOU) | ✅ pass | | TC-0144 | POST /api/user/create - 唯一索引冲突消息 | ✅ pass | | TC-0145 | POST /api/user/create - 密码少于8字符 | ✅ pass | | TC-0146 | POST /api/user/create - 密码缺少大写字母 | ✅ pass | | TC-0147 | POST /api/user/create - 密码缺少小写字母 | ✅ pass | | TC-0148 | POST /api/user/create - 密码缺少数字 | ✅ pass | | TC-0149 | POST /api/user/create - 密码超过72字符 | ✅ pass | | TC-0150 | POST /api/user/create - 用户名含特殊字符被拒绝 | ✅ pass | | TC-0151 | POST /api/user/create - 用户名太短(1字符)被拒绝 | ✅ pass | | TC-0152 | POST /api/user/create - 用户名太长(65字符)被拒绝 | ✅ pass | | TC-0153 | POST /api/user/create - 部门不存在被拒绝 | ✅ pass | | TC-0154 | POST /api/user/create - 昵称超过64字符被拒绝 | ✅ pass | | TC-0155 | POST /api/user/create - 备注超过255字符被拒绝 | ✅ pass | #### 2.15 用户更新 `POST /api/user/update` (指针类型+DeptId可清零) | TC编号 | 测试场景 | 测试结果 | | :--- | :--- | :--- | | TC-0156 | POST /api/user/update - 正常更新 | ✅ pass | | TC-0157 | POST /api/user/update - 不存在 | ✅ pass | | TC-0158 | POST /api/user/update - 仅传id | ✅ pass | | TC-0159 | POST /api/user/update - 清空nickname | ✅ pass | | TC-0160 | POST /api/user/update - 清空email | ✅ pass | | TC-0161 | POST /api/user/update - 清空remark | ✅ pass | | TC-0162 | POST /api/user/update - 非法email格式 | ✅ pass | | TC-0163 | POST /api/user/update - 非法phone格式 | ✅ pass | | TC-0164 | POST /api/user/update - 合法phone | ✅ pass | | TC-0165 | POST /api/user/update - 不传email(nil) | ✅ pass | | TC-0166 | POST /api/user/update - DeptId设为0(取消部门) | ✅ pass | | TC-0167 | POST /api/user/update - DeptId设为正值 | ✅ pass | | TC-0168 | POST /api/user/update - DeptId不传(nil) | ✅ pass | | TC-0169 | POST /api/user/update - 超管不能冻结另一超管 | ✅ pass | | TC-0170 | POST /api/user/update - updateUser-产品管理员可管理范围内用户 | ✅ pass | | TC-0171 | POST /api/user/update - updateUser-昵称超长拒绝 | ✅ pass | | TC-0172 | POST /api/user/update - updateUser-部门不存在 | ✅ pass | | TC-0173 | POST /api/user/update - updateUser 修改状态时递增 tokenVersion | ✅ pass | | TC-0174 | POST /api/user/update - updateUser 仅改 profile 不递增 tokenVersion | ✅ pass | | TC-0175 | POST /api/user/update - updateUser 乐观锁冲突 -> 409 | ✅ pass | #### 2.16 用户列表/详情/状态 及其他用户操作 | TC编号 | 测试场景 | 测试结果 | | :--- | :--- | :--- | | TC-0176 | POST /api/user/list - 含productCode | ✅ pass | | TC-0177 | POST /api/user/list - 不含productCode | ✅ pass | | TC-0178 | POST /api/user/list - pageSize超过上限 | ✅ pass | | TC-0179 | POST /api/user/list - 用户不在产品中 | ✅ pass | | TC-0180 | POST /api/user/list - 批量查询DB异常 | ✅ pass | | TC-0181 | POST /api/user/detail - 正常查询 | ✅ pass | | TC-0182 | POST /api/user/detail - 正常查询-含Avatar | ✅ pass | | TC-0183 | POST /api/user/detail - 不存在 | ✅ pass | | TC-0184 | POST /api/user/bindRoles - 正常绑定 | ✅ pass | | TC-0185 | POST /api/user/bindRoles - 用户不存在 | ✅ pass | | TC-0186 | POST /api/user/bindRoles - 清空角色 | ✅ pass | | TC-0187 | POST /api/user/bindRoles - 事务回滚 | ✅ pass | | TC-0188 | POST /api/user/bindRoles - 角色不属于当前产品 | ✅ pass | | TC-0189 | POST /api/user/bindRoles - 角色已禁用 | ✅ pass | | TC-0190 | POST /api/user/bindRoles - 角色不存在 | ✅ pass | | TC-0191 | POST /api/user/bindRoles - 非产品成员绑定角色被拒绝 | ✅ pass | | TC-0192 | POST /api/user/setPerms - 正常ALLOW | ✅ pass | | TC-0193 | POST /api/user/setPerms - 用户不存在 | ✅ pass | | TC-0194 | POST /api/user/setPerms - DENY权限 | ✅ pass | | TC-0195 | POST /api/user/setPerms - 清空权限 | ✅ pass | | TC-0196 | POST /api/user/setPerms - 无效Effect值 | ✅ pass | | TC-0197 | POST /api/user/setPerms - PermId不存在 | ✅ pass | | TC-0198 | POST /api/user/setPerms - 权限不属于当前产品 | ✅ pass | | TC-0199 | POST /api/user/setPerms - 非产品成员设置权限被拒绝 | ✅ pass | | TC-0200 | POST /api/user/updateStatus - 正常冻结 | ✅ pass | | TC-0201 | POST /api/user/updateStatus - 正常解冻 | ✅ pass | | TC-0202 | POST /api/user/updateStatus - 非法status(0) | ✅ pass | | TC-0203 | POST /api/user/updateStatus - 冻结自己 | ✅ pass | | TC-0204 | POST /api/user/updateStatus - 冻结超管 | ✅ pass | | TC-0205 | POST /api/user/list - userList-非超管仅可见产品成员 | ✅ pass | | TC-0206 | POST /api/user/list - userList-非超管未指定productCode被拒绝 | ✅ pass | | TC-0207 | POST /api/user/list - userList-非超管使用错误productCode被拒绝 | ✅ pass | | TC-0208 | POST /api/user/bindRoles - bindRoles-permsLevel越权拒绝 | ✅ pass | | TC-0209 | POST /api/user/bindRoles - bindRoles-超管可分配任意级别角色 | ✅ pass | | TC-0210 | POST /api/user/setPerms - 同一权限ID冲突Effect被拒绝 | ✅ pass | | TC-0211 | POST /api/user/setPerms - 重复权限ID相同Effect去重 | ✅ pass | | TC-0212 | POST /api/user/setPerms - 已禁用权限不能被设置 | ✅ pass | #### 2.17 成员管理 | TC编号 | 测试场景 | 测试结果 | | :--- | :--- | :--- | | TC-0213 | POST /api/member/add - 正常添加 | ✅ pass | | TC-0214 | POST /api/member/add - 产品不存在 | ✅ pass | | TC-0215 | POST /api/member/add - 用户不存在 | ✅ pass | | TC-0216 | POST /api/member/add - 已是成员 | ✅ pass | | TC-0217 | POST /api/member/add - 并发添加 | ✅ pass | | TC-0218 | POST /api/member/add - 无效MemberType | ✅ pass | | TC-0219 | POST /api/member/update - 正常更新 | ✅ pass | | TC-0220 | POST /api/member/update - 不存在 | ✅ pass | | TC-0221 | POST /api/member/update - 无效MemberType | ✅ pass | | TC-0222 | POST /api/member/list - 正常查询(批量查用户) | ✅ pass | | TC-0223 | POST /api/member/list - 成员用户已删除 | ✅ pass | | TC-0224 | POST /api/member/list - pageSize超过上限 | ✅ pass | | TC-0225 | POST /api/member/list - 空成员列表 | ✅ pass | | TC-0226 | POST /api/member/remove - 正常移除+级联(事务内) | ✅ pass | | TC-0227 | POST /api/member/remove - 跨产品隔离 | ✅ pass | | TC-0228 | POST /api/member/remove - 成员不存在 | ✅ pass | | TC-0229 | POST /api/member/remove - 事务回滚 | ✅ pass | ### 三、gRPC 接口测试用例 #### 3.1 gRPC SyncPermissions | TC编号 | 测试场景 | 测试结果 | | :--- | :--- | :--- | | TC-0230 | SyncPermissions - 正常同步 | ✅ pass | | TC-0231 | SyncPermissions - appKey无效 | ✅ pass | | TC-0232 | SyncPermissions - appSecret错误 | ✅ pass | | TC-0233 | SyncPermissions - 产品已禁用 | ✅ pass | | TC-0234 | SyncPermissions - 验证disabled计数 | ✅ pass | #### 3.2 gRPC Login / RefreshToken / VerifyToken / GetUserPerms | TC编号 | 测试场景 | 测试结果 | | :--- | :--- | :--- | | TC-0235 | Login - 正常登录(普通用户+productCode) | ✅ pass | | TC-0236 | Login - 用户不存在 | ✅ pass | | TC-0237 | Login - 密码错误 | ✅ pass | | TC-0238 | Login - 账号冻结 | ✅ pass | | TC-0239 | Login - 超管被拒绝 | ✅ pass | | TC-0240 | Login - 普通用户+productCode | ✅ pass | | TC-0241 | Login - 产品成员被禁用时拒绝登录 | ✅ pass | | TC-0242 | Login - productCode为空 | ✅ pass | | TC-0243 | RefreshToken - 正常刷新 | ✅ pass | | TC-0244 | RefreshToken - token无效 | ✅ pass | | TC-0245 | RefreshToken - 账号冻结 | ✅ pass | | TC-0246 | RefreshToken - productCode回退到claims | ✅ pass | | TC-0247 | RefreshToken - 超管+productCode | ✅ pass | | TC-0248 | RefreshToken - 普通用户+productCode | ✅ pass | | TC-0249 | VerifyToken - 有效token | ✅ pass | | TC-0250 | VerifyToken - 无效token | ✅ pass | | TC-0251 | VerifyToken - 缺少userId | ✅ pass | | TC-0252 | VerifyToken - 冻结用户token返回Invalid | ✅ pass | | TC-0253 | VerifyToken - 非成员token返回Invalid | ✅ pass | | TC-0254 | VerifyToken - 返回实时MemberType和Perms | ✅ pass | | TC-0255 | GetUserPerms - 用户不存在(需先通过AppKey/Secret认证) | ✅ pass | | TC-0256 | GetUserPerms - 超管(需先通过AppKey/Secret认证) | ✅ pass | | TC-0257 | GetUserPerms - MEMBER-DENY覆盖(需先通过AppKey/Secret认证) | ✅ pass | ### 四、JWT中间件 / 统一响应测试用例 | TC编号 | 测试场景 | 测试结果 | | :--- | :--- | :--- | | TC-0258 | 正常Bearer token | ✅ pass | | TC-0259 | 无Authorization头 | ✅ pass | | TC-0260 | 无Bearer前缀 | ✅ pass | | TC-0261 | token签名错误 | ✅ pass | | TC-0262 | token过期 | ✅ pass | | TC-0263 | claims类型断言失败 | ⏭️ skip | | TC-0264 | refresh token被拒绝 | ✅ pass | | TC-0265 | 业务错误(CodeError) | ✅ pass | | TC-0266 | 内部错误 | ✅ pass | | TC-0267 | 成功(有data) | ✅ pass | | TC-0268 | 成功(无data) | ✅ pass | ### 五、util 层测试用例 #### 5.1 NormalizePage | TC编号 | 测试场景 | 测试结果 | | :--- | :--- | :--- | | TC-0269 | 正常值 | ✅ pass | | TC-0270 | page<=0 | ✅ pass | | TC-0271 | page=-1 | ✅ pass | | TC-0272 | pageSize<=0 | ✅ pass | | TC-0273 | pageSize>100 | ✅ pass | | TC-0274 | pageSize=100 | ✅ pass | | TC-0275 | pageSize=101 | ✅ pass | | TC-0276 | 双零 | ✅ pass | #### 5.2 IsValidEmail | TC编号 | 测试场景 | 测试结果 | | :--- | :--- | :--- | | TC-0277 | 正常邮箱 | ✅ pass | | TC-0278 | 含点号 | ✅ pass | | TC-0279 | 含加号 | ✅ pass | | TC-0280 | 缺少@ | ✅ pass | | TC-0281 | 缺少域名 | ✅ pass | | TC-0282 | 缺少TLD | ✅ pass | | TC-0283 | 空字符串 | ✅ pass | #### 5.3 IsValidPhone | TC编号 | 测试场景 | 测试结果 | | :--- | :--- | :--- | | TC-0284 | 国内手机号 | ✅ pass | | TC-0285 | 带+国际码 | ✅ pass | | TC-0286 | 太短(6位) | ✅ pass | | TC-0287 | 恰好7位 | ✅ pass | | TC-0288 | 最长15位 | ✅ pass | | TC-0289 | 超长16位 | ✅ pass | | TC-0290 | 包含字母 | ✅ pass | | TC-0291 | 空字符串 | ✅ pass | ### 六、Logic 层单元测试用例 #### 6.1 auth/jwt.go — GenerateAccessToken | TC编号 | 测试场景 | 测试结果 | | :--- | :--- | :--- | | TC-0292 | 正常生成 | ✅ pass | | TC-0293 | 解析token验证claims | ✅ pass | | TC-0294 | 空secret | ✅ pass | | TC-0295 | 空perms | ✅ pass | | TC-0296 | 过期时间验证 | ✅ pass | #### 6.2 auth/jwt.go — GenerateRefreshToken | TC编号 | 测试场景 | 测试结果 | | :--- | :--- | :--- | | TC-0297 | 正常生成 | ✅ pass | | TC-0298 | 解析验证 | ✅ pass | | TC-0299 | productCode为空 | ✅ pass | #### 6.3 auth/jwt.go — ParseRefreshToken | TC编号 | 测试场景 | 测试结果 | | :--- | :--- | :--- | | TC-0300 | 正常解析 | ✅ pass | | TC-0301 | 错误secret | ✅ pass | | TC-0302 | 无效token字符串 | ✅ pass | | TC-0303 | 空token | ✅ pass | | TC-0304 | 过期token | ✅ pass | | TC-0305 | AccessToken误用 | ✅ pass | #### 6.4 middleware — 辅助函数单元测试 | TC编号 | 测试场景 | 测试结果 | | :--- | :--- | :--- | | TC-0306 | GetUserId-正常 | ✅ pass | | TC-0307 | GetUserId-空ctx | ✅ pass | | TC-0308 | GetProductCode-正常 | ✅ pass | | TC-0309 | GetUserDetails 返回完整字段 | ✅ pass | ### 七、Model 层 _gen.go 模板生成方法测试用例 #### 7.1 通用 CRUD 方法 (每个 Model 均需测试) | TC编号 | 测试场景 | 测试结果 | | :--- | :--- | :--- | | TC-0310 | Insert - 正常插入 | ✅ pass | | TC-0311 | Insert - 正常插入含TokenVersion | ✅ pass | | TC-0312 | Insert - 唯一索引冲突 | ✅ pass | | TC-0313 | Insert - 缓存key生成正确 | ✅ pass | | TC-0314 | InsertWithTx - 事务内插入 | ✅ pass | | TC-0315 | InsertWithTx - 事务内插入含TokenVersion | ✅ pass | | TC-0316 | InsertWithTx - 事务回滚后无数据 | ✅ pass | | TC-0317 | FindOne - 正常查询(缓存未命中) | ✅ pass | | TC-0318 | FindOne - 正常查询(缓存命中) | ✅ pass | | TC-0319 | FindOne - 记录不存在 | ✅ pass | | TC-0320 | FindOne - DB异常(非ErrNotFound) | ✅ pass | | TC-0321 | FindOneWithTx - 事务内正常查询 | ✅ pass | | TC-0322 | FindOneWithTx - 事务内记录不存在 | ✅ pass | | TC-0323 | FindOneWithTx - 事务内可见性 | ✅ pass | | TC-0324 | Update - 正常更新 | ✅ pass | | TC-0325 | Update - 正常更新含TokenVersion | ✅ pass | | TC-0326 | Update - 记录不存在 | ✅ pass | | TC-0327 | UpdateWithTx - 事务内更新 | ✅ pass | | TC-0328 | Delete - 正常删除 | ✅ pass | | TC-0329 | Delete - 记录不存在 | ✅ pass | | TC-0330 | DeleteWithTx - 事务内删除 | ✅ pass | | TC-0331 | TransactCtx - 正常事务 | ✅ pass | | TC-0332 | TransactCtx - fn返回错误 | ✅ pass | | TC-0333 | TableName - 获取表名 | ✅ pass | #### 7.2 批量插入方法 | TC编号 | 测试场景 | 测试结果 | | :--- | :--- | :--- | | TC-0334 | BatchInsert - 空列表 | ✅ pass | | TC-0335 | BatchInsert - 单条记录 | ✅ pass | | TC-0336 | BatchInsert - 多条记录(3条) | ✅ pass | | TC-0337 | BatchInsert - 批量插入含TokenVersion | ✅ pass | | TC-0338 | BatchInsert - 唯一索引冲突 | ✅ pass | | TC-0339 | BatchInsert - 大批量(1000条) | ✅ pass | | TC-0340 | BatchInsertWithTx - 空列表 | ✅ pass | | TC-0341 | BatchInsertWithTx - 正常多条 | ✅ pass | | TC-0342 | BatchInsertWithTx - 事务回滚 | ✅ pass | #### 7.3 批量更新方法 | TC编号 | 测试场景 | 测试结果 | | :--- | :--- | :--- | | TC-0343 | BatchUpdate - 空列表 | ✅ pass | | TC-0344 | BatchUpdate - 单条记录 | ✅ pass | | TC-0345 | BatchUpdate - 多条记录(3条) | ✅ pass | | TC-0346 | BatchUpdate - 批量更新不污染数据 | ✅ pass | | TC-0347 | BatchUpdate - 部分id不存在 | ✅ pass | | TC-0348 | BatchUpdateWithTx - 空列表 | ✅ pass | | TC-0349 | BatchUpdateWithTx - 正常多条 | ✅ pass | | TC-0350 | buildBatchUpdateQuery - 单条 | ✅ pass | | TC-0351 | buildBatchUpdateQuery - 多条 | ✅ pass | | TC-0352 | buildBatchUpdateQuery - vals数量正确 | ✅ pass | #### 7.4 批量删除方法 | TC编号 | 测试场景 | 测试结果 | | :--- | :--- | :--- | | TC-0353 | BatchDelete - 空ids | ✅ pass | | TC-0354 | BatchDelete - 单个id | ✅ pass | | TC-0355 | BatchDelete - 多个id(3个) | ✅ pass | | TC-0356 | BatchDelete - 包含不存在id | ✅ pass | | TC-0357 | BatchDeleteWithTx - 空ids | ✅ pass | | TC-0358 | BatchDeleteWithTx - 正常多条 | ✅ pass | #### 7.5 唯一索引查询方法 (按 Model 差异) | TC编号 | 测试场景 | 测试结果 | | :--- | :--- | :--- | | TC-0359 | FindOneByUsername - 正常查询 | ✅ pass | | TC-0360 | FindOneByUsername - 不存在 | ✅ pass | | TC-0361 | FindOneByUsernameWithTx - 事务内正常查询 | ✅ pass | | TC-0362 | FindOneByUsernameWithTx - 事务内不存在 | ✅ pass | | TC-0363 | FindOneByAppKey - 正常查询 | ✅ pass | | TC-0364 | FindOneByAppKey - 不存在 | ✅ pass | | TC-0365 | FindOneByAppKeyWithTx - 事务内正常查询 | ✅ pass | | TC-0366 | FindOneByAppKeyWithTx - 事务内不存在 | ✅ pass | | TC-0367 | FindOneByCode - 正常查询 | ✅ pass | | TC-0368 | FindOneByCode - 不存在 | ✅ pass | | TC-0369 | FindOneByCodeWithTx - 事务内正常查询 | ✅ pass | | TC-0370 | FindOneByCodeWithTx - 事务内不存在 | ✅ pass | | TC-0371 | FindOneByProductCodeCode - 正常查询 | ✅ pass | | TC-0372 | FindOneByProductCodeCode - 不存在 | ✅ pass | | TC-0373 | FindOneByProductCodeCodeWithTx - 事务内正常查询 | ✅ pass | | TC-0374 | FindOneByProductCodeCodeWithTx - 事务内不存在 | ✅ pass | | TC-0375 | FindOneByProductCodeName - 正常查询 | ✅ pass | | TC-0376 | FindOneByProductCodeName - 不存在 | ✅ pass | | TC-0377 | FindOneByProductCodeNameWithTx - 事务内正常查询 | ✅ pass | | TC-0378 | FindOneByProductCodeNameWithTx - 事务内不存在 | ✅ pass | | TC-0379 | FindOneByRoleIdPermId - 正常查询 | ✅ pass | | TC-0380 | FindOneByRoleIdPermId - 不存在 | ✅ pass | | TC-0381 | FindOneByRoleIdPermIdWithTx - 事务内正常查询 | ✅ pass | | TC-0382 | FindOneByRoleIdPermIdWithTx - 事务内不存在 | ✅ pass | | TC-0383 | FindOneByUserIdPermId - 正常查询 | ✅ pass | | TC-0384 | FindOneByUserIdPermId - 不存在 | ✅ pass | | TC-0385 | FindOneByUserIdPermIdWithTx - 事务内正常查询 | ✅ pass | | TC-0386 | FindOneByUserIdPermIdWithTx - 事务内不存在 | ✅ pass | | TC-0387 | FindOneByUserIdRoleId - 正常查询 | ✅ pass | | TC-0388 | FindOneByUserIdRoleId - 不存在 | ✅ pass | | TC-0389 | FindOneByUserIdRoleIdWithTx - 事务内正常查询 | ✅ pass | | TC-0390 | FindOneByUserIdRoleIdWithTx - 事务内不存在 | ✅ pass | | TC-0391 | FindOneByProductCodeUserId - 正常查询 | ✅ pass | | TC-0392 | FindOneByProductCodeUserId - 不存在 | ✅ pass | | TC-0393 | FindOneByProductCodeUserIdWithTx - 事务内正常查询 | ✅ pass | | TC-0394 | FindOneByProductCodeUserIdWithTx - 事务内不存在 | ✅ pass | #### 7.6 内部辅助方法 | TC编号 | 测试场景 | 测试结果 | | :--- | :--- | :--- | | TC-0395 | findListByPrimaryKeys - 空ids | ✅ pass | | TC-0396 | findListByPrimaryKeys - 正常ids | ✅ pass | | TC-0397 | findListByPrimaryKeys - 部分不存在 | ✅ pass | | TC-0398 | findListByPrimaryKeys - DB异常 | ✅ pass | | TC-0399 | getPrimaryKeyValue - 正常 | ✅ pass | | TC-0400 | formatPrimary - 正常 | ✅ pass | | TC-0401 | queryPrimary - 正常 | ✅ pass | #### 7.7 缓存key与前缀初始化 | TC编号 | 测试场景 | 测试结果 | | :--- | :--- | :--- | | TC-0402 | cachePrefix为空 | ✅ pass | | TC-0403 | cachePrefix非空 | ✅ pass | | TC-0404 | 多唯一索引前缀(SysProduct) | ✅ pass | ### 八、Model 层自定义方法测试用例 #### 8.1 SysUserModel | TC编号 | 测试场景 | 测试结果 | | :--- | :--- | :--- | | TC-0405 | FindListByPage - 正常分页 | ✅ pass | | TC-0406 | FindListByPage - 第二页 | ✅ pass | | TC-0407 | FindListByPage - 空表 | ✅ pass | | TC-0408 | FindListByPage - count查询失败 | ✅ pass | | TC-0409 | FindListByPage - list查询失败 | ✅ pass | | TC-0410 | FindListByProductMembers - 正常查询 | ✅ pass | | TC-0411 | FindListByProductMembers - productCode不存在 | ✅ pass | | TC-0412 | FindByIds - 正常批量查询 | ✅ pass | | TC-0413 | FindByIds - 空ids | ✅ pass | | TC-0414 | FindByIds - 部分id不存在 | ✅ pass | | TC-0415 | FindByIds - DB异常 | ✅ pass | | TC-0416 | FindIdsByDeptId - 有用户的部门 | ✅ pass | | TC-0417 | FindIdsByDeptId - 无用户部门 | ✅ pass | | TC-0418 | UpdateProfile 状态未变-不递增tokenVersion - statusChanged=false | ✅ pass | | TC-0419 | UpdateProfile 状态变更-tokenVersion+1 - statusChanged=true | ✅ pass | | TC-0420 | UpdateProfile 乐观锁冲突 - expectedUpdateTime 与DB不符 | ✅ pass | | TC-0421 | UpdateProfile 并发场景 - 两个 goroutine 基于同一 updateTime 并发更新 | ✅ pass | | TC-0422 | UpdateProfile userId不存在 - id=9999999 | ✅ pass | #### 8.2 SysProductModel | TC编号 | 测试场景 | 测试结果 | | :--- | :--- | :--- | | TC-0423 | FindList - 正常分页 | ✅ pass | | TC-0424 | FindList - 空表 | ✅ pass | | TC-0425 | FindList - count失败 | ✅ pass | #### 8.3 SysPermModel | TC编号 | 测试场景 | 测试结果 | | :--- | :--- | :--- | | TC-0426 | FindListByProductCode - 正常分页 | ✅ pass | | TC-0427 | FindListByProductCode - 不存在的productCode | ✅ pass | | TC-0428 | FindAllCodesByProductCode - 正常查询 | ✅ pass | | TC-0429 | FindAllCodesByProductCode - 空结果 | ✅ pass | | TC-0430 | FindByIds - 正常 | ✅ pass | | TC-0431 | FindByIds - 空ids | ✅ pass | | TC-0432 | FindMapByProductCode - 正常查询 | ✅ pass | | TC-0433 | FindMapByProductCode - 空结果 | ✅ pass | | TC-0434 | FindMapByProductCode - key唯一性 | ✅ pass | | TC-0435 | DisableNotInCodesWithTx - codes非空-正常 | ✅ pass | | TC-0436 | DisableNotInCodesWithTx - codes为空-全部禁用 | ✅ pass | | TC-0437 | DisableNotInCodesWithTx - 无需禁用 | ✅ pass | | TC-0438 | DisableNotInCodesWithTx - DB异常 | ✅ pass | | TC-0439 | FindAllCodesByProductCode - 有权限产品 | ✅ pass | | TC-0440 | FindAllCodesByProductCode - 无权限产品 | ✅ pass | | TC-0441 | FindAllCodesByProductCode - 全部已禁用 | ✅ pass | #### 8.4 SysDeptModel | TC编号 | 测试场景 | 测试结果 | | :--- | :--- | :--- | | TC-0442 | FindAll - 正常查询 | ✅ pass | | TC-0443 | FindAll - 空表 | ✅ pass | | TC-0444 | FindByParentId - 正常查询 | ✅ pass | | TC-0445 | FindByParentId - 无子部门 | ✅ pass | | TC-0446 | FindByPathPrefix - 正常查询 | ✅ pass | | TC-0447 | FindByPathPrefix - LIKE注入已阻止 | ✅ pass | | TC-0448 | FindByPathPrefix - 无匹配 | ✅ pass | #### 8.5 SysRoleModel | TC编号 | 测试场景 | 测试结果 | | :--- | :--- | :--- | | TC-0449 | FindListByProductCode - 正常分页 | ✅ pass | | TC-0450 | FindListByProductCode - 空结果 | ✅ pass | | TC-0451 | FindByIds - 正常 | ✅ pass | | TC-0452 | FindByIds - 空ids | ✅ pass | | TC-0453 | FindMinPermsLevelByUserIdAndProductCode - 有角色用户 | ✅ pass | | TC-0454 | FindMinPermsLevelByUserIdAndProductCode - 无角色用户 | ✅ pass | #### 8.6 SysRolePermModel | TC编号 | 测试场景 | 测试结果 | | :--- | :--- | :--- | | TC-0455 | FindPermIdsByRoleId - 正常查询 | ✅ pass | | TC-0456 | FindPermIdsByRoleId - 无绑定 | ✅ pass | | TC-0457 | FindPermIdsByRoleIds - 正常查询 | ✅ pass | | TC-0458 | FindPermIdsByRoleIds - 空roleIds | ✅ pass | | TC-0459 | FindPermIdsByRoleIds - 去重验证 | ✅ pass | | TC-0460 | DeleteByRoleIdTx - 正常事务内删除 | ✅ pass | | TC-0461 | DeleteByRoleIdTx - 无绑定 | ✅ pass | #### 8.7 SysUserPermModel | TC编号 | 测试场景 | 测试结果 | | :--- | :--- | :--- | | TC-0462 | FindPermIdsByUserIdAndEffectForProduct - ALLOW-指定产品 | ✅ pass | | TC-0463 | FindPermIdsByUserIdAndEffectForProduct - DENY-指定产品 | ✅ pass | | TC-0464 | FindPermIdsByUserIdAndEffectForProduct - 无记录/其他产品 | ✅ pass | | TC-0465 | DeleteByUserIdForProductTx - 事务内跨产品删除 | ✅ pass | | TC-0466 | DeleteByUserIdForProductTx - 跨产品隔离 | ✅ pass | #### 8.8 SysUserRoleModel | TC编号 | 测试场景 | 测试结果 | | :--- | :--- | :--- | | TC-0467 | FindRoleIdsByUserId - 正常查询 | ✅ pass | | TC-0468 | FindRoleIdsByUserId - 无绑定 | ✅ pass | | TC-0469 | DeleteByRoleIdTx - 正常删除 | ✅ pass | | TC-0470 | DeleteByUserIdForProductTx - 事务内跨产品删除 | ✅ pass | | TC-0471 | DeleteByUserIdForProductTx - 跨产品隔离 | ✅ pass | | TC-0472 | FindUserIdsByRoleId - 有绑定的角色 | ✅ pass | | TC-0473 | FindUserIdsByRoleId - 无绑定角色 | ✅ pass | | TC-0474 | FindRoleIdsByUserIdForProduct - 跨产品过滤 | ✅ pass | #### 8.9 SysProductMemberModel | TC编号 | 测试场景 | 测试结果 | | :--- | :--- | :--- | | TC-0475 | FindListByProductCode - 正常分页 | ✅ pass | | TC-0476 | FindListByProductCode - 空结果 | ✅ pass | | TC-0477 | FindMapByProductCodeUserIds - 正常批量 | ✅ pass | | TC-0478 | FindMapByProductCodeUserIds - 空userIds | ✅ pass | | TC-0479 | FindMapByProductCodeUserIds - 部分不是成员 | ✅ pass | | TC-0480 | FindMapByProductCodeUserIds - map key正确 | ✅ pass | ### 九、访问控制 (auth/access.go) #### 9.1 RequireSuperAdmin | TC编号 | 测试场景 | 测试结果 | | :--- | :--- | :--- | | TC-0481 | 超管通过 | ✅ pass | | TC-0482 | 非超管拒绝 | ✅ pass | | TC-0483 | MEMBER拒绝 | ✅ pass | | TC-0484 | 未登录 | ✅ pass | #### 9.2 RequireProductAdminFor(ctx, targetProductCode) | TC编号 | 测试场景 | 测试结果 | | :--- | :--- | :--- | | TC-0485 | 超管通过 | ✅ pass | | TC-0486 | ADMIN通过(同产品) | ✅ pass | | TC-0487 | DEVELOPER拒绝 | ✅ pass | | TC-0488 | MEMBER拒绝 | ✅ pass | | TC-0489 | 未登录 | ✅ pass | | TC-0490 | ADMIN跨产品拒绝 | ✅ pass | #### 9.3 CheckMemberTypeAssignment | TC编号 | 测试场景 | 测试结果 | | :--- | :--- | :--- | | TC-0491 | 超管可分配任何类型 | ✅ pass | | TC-0492 | ADMIN分配DEVELOPER | ✅ pass | | TC-0493 | ADMIN分配ADMIN(同级拒绝) | ✅ pass | | TC-0494 | DEVELOPER分配ADMIN(越级拒绝) | ✅ pass | | TC-0495 | MEMBER分配MEMBER(同级拒绝) | ✅ pass | | TC-0496 | 未登录 | ✅ pass | #### 9.4 CheckManageAccess | TC编号 | 测试场景 | 测试结果 | | :--- | :--- | :--- | | TC-0497 | 超管可管理任何人 | ✅ pass | | TC-0498 | 操作自己 | ✅ pass | | TC-0499 | ADMIN跳过部门检查 | ✅ pass | | TC-0500 | 非ADMIN无部门拒绝 | ✅ pass | | TC-0501 | 目标用户无部门 | ✅ pass | | TC-0502 | 目标在不同部门 | ✅ pass | | TC-0503 | 未登录 | ✅ pass | | TC-0504 | caller.DeptPath为空时拒绝 | ✅ pass | #### 9.5 memberTypePriority | TC编号 | 测试场景 | 测试结果 | | :--- | :--- | :--- | | TC-0505 | 各类型优先级正确 | ✅ pass | ### 十、UserDetailsLoader (loaders/userDetailsLoader.go) #### 10.1 Load / 缓存 | TC编号 | 测试场景 | 测试结果 | | :--- | :--- | :--- | | TC-0506 | DB加载(缓存miss) | ✅ pass | | TC-0507 | 缓存命中 | ✅ pass | | TC-0508 | 用户不存在 | ✅ pass | | TC-0509 | productCode为空 | ✅ pass | #### 10.2 缓存失效 | TC编号 | 测试场景 | 测试结果 | | :--- | :--- | :--- | | TC-0510 | Del删除指定缓存 | ✅ pass | | TC-0511 | Clean清除用户所有产品缓存 | ✅ pass | | TC-0512 | CleanByProduct清除产品所有用户 | ✅ pass | | TC-0513 | BatchDel批量删除 | ✅ pass | | TC-0514 | BatchDel空数组 | ✅ pass | #### 10.3 loadPerms权限计算 | TC编号 | 测试场景 | 测试结果 | | :--- | :--- | :--- | | TC-0515 | 超管全量权限 | ✅ pass | | TC-0516 | ADMIN全量权限 | ✅ pass | | TC-0517 | DEVELOPER全量权限 | ✅ pass | | TC-0518 | DEV部门全量权限 | ✅ pass | | TC-0519 | MEMBER角色权限+ALLOW-DENY | ✅ pass | | TC-0520 | 用户ALLOW权限不跨产品泄漏 | ✅ pass | | TC-0521 | 禁用DEV部门成员无全量权限 | ✅ pass | #### 10.4 loadRoles + MinPermsLevel | TC编号 | 测试场景 | 测试结果 | | :--- | :--- | :--- | | TC-0522 | 多角色取最小permsLevel | ✅ pass | | TC-0523 | 无角色 | ✅ pass | | TC-0524 | 角色跨产品过滤 | ✅ pass | | TC-0525 | 禁用角色不计入 | ✅ pass | #### 10.5 loadMembership | TC编号 | 测试场景 | 测试结果 | | :--- | :--- | :--- | | TC-0526 | 超管自动设置SUPER_ADMIN | ✅ pass | | TC-0527 | 非成员MemberType为空 | ✅ pass | | TC-0528 | 禁用成员MemberType为空 | ✅ pass | ### 十一、中间件 — 冻结账号拦截 | TC编号 | 测试场景 | 测试结果 | | :--- | :--- | :--- | | TC-0529 | 冻结用户被403 | ✅ pass | | TC-0530 | 用户不存在(Status=0) | ✅ pass | | TC-0531 | UserDetails注入context | ✅ pass | ### 十二、Logic层 — 访问控制负面测试 | TC编号 | 测试场景 | 测试结果 | | :--- | :--- | :--- | | TC-0532 | createDept非超管拒绝 | ✅ pass | | TC-0533 | updateDept非超管拒绝 | ✅ pass | | TC-0534 | deleteDept非超管拒绝 | ✅ pass | | TC-0535 | createProduct非超管拒绝 | ✅ pass | | TC-0536 | updateProduct非超管拒绝 | ✅ pass | | TC-0537 | createUser非产品管理员拒绝 | ✅ pass | | TC-0538 | createRole非产品管理员拒绝 | ✅ pass | | TC-0539 | updateRole非产品管理员拒绝 | ✅ pass | | TC-0540 | deleteRole非产品管理员拒绝 | ✅ pass | | TC-0541 | bindRolePerms非产品管理员拒绝 | ✅ pass | | TC-0542 | updateUser-MEMBER不能管理他人 | ✅ pass | | TC-0543 | updateUser自己修改DeptId被拒绝 | ✅ pass | | TC-0544 | updateUser自己修改Status被拒绝 | ✅ pass | | TC-0545 | updateUser未登录被拒绝 | ✅ pass | ### 十三、限流中间件 (middleware/ratelimitMiddleware.go) | TC编号 | 测试场景 | 测试结果 | | :--- | :--- | :--- | | TC-0546 | 正常请求(未超限) | ✅ pass | | TC-0547 | 超限请求被拒绝 | ✅ pass | | TC-0548 | behindProxy=false时XFF被忽略 | ✅ pass | | TC-0549 | behindProxy=false时X-Real-IP被忽略 | ✅ pass | | TC-0550 | IP从RemoteAddr解析 | ✅ pass | | TC-0551 | 不同IP独立限流 | ✅ pass | | TC-0552 | behindProxy=true时信任X-Real-IP | ✅ pass | | TC-0553 | behindProxy=true时无X-Real-IP回退RemoteAddr | ✅ pass | | TC-0554 | behindProxy=true时XFF仍被忽略 | ✅ pass | | TC-0555 | RemoteAddr无端口格式 | ✅ pass | ### 十四、审计修复回归 (audit-report.md 2026-04 修复集) | TC编号 | 测试场景 | 测试结果 | 关联修复 | | :--- | :--- | :--- | :--- | | TC-0208 | MEMBER 仍被 permsLevel 校验阻断 | ✅ pass | H-1 | | TC-0711 | ADMIN 绕过 permsLevel 校验 | ✅ pass | H-1 | | TC-0712 | DEVELOPER 绕过 permsLevel 校验 | ✅ pass | H-1 | | TC-0713 | MEMBER w/ MinPermsLevel=MaxInt64 不被误阻断 | ✅ pass | H-1 | | TC-0700 | gRPC GetUserPerms 冻结用户 → PermissionDenied | ✅ pass | H-2 | | TC-0701 | gRPC GetUserPerms 非成员 → PermissionDenied | ✅ pass | H-2 | | TC-0702 | gRPC GetUserPerms DEV+禁用成员 → PermissionDenied | ✅ pass | H-2 + H-3 | | TC-0703 | gRPC GetUserPerms 正常成员 → 成功 | ✅ pass | H-2 (positive) | | TC-0704 | Loader:DEV 部门 + 禁用成员不发全量权限 | ✅ pass | H-3 | | TC-0705 | Loader:不存在用户不留缓存 | ✅ pass | L-5 | | TC-0706 | FindRoleIdsByUserIdForProduct 过滤 r.status=1 | ✅ pass | M-4 | | TC-0707 | DeleteByUserIdAndRoleIdsTx 批量删除 | ✅ pass | M-2 | | TC-0708 | DeleteByUserIdAndRoleIdsTx 空列表 no-op | ✅ pass | M-2 | | TC-0709 | DeleteByUserIdAndRoleIdsTx userId 约束 | ✅ pass | M-2 | | TC-0710 | 产品/管后登录限流独立桶 | ✅ pass | L-2 | | TC-0716 | JWT access token payload 不含 "perms" key | ✅ pass | M-6 | | TC-0105 | UpdateDept 不再级联子部门缓存 + UpdateWithOptLock | ✅ pass | M-5 | | TC-0714 | UpdateDept 无关字段变更不清缓存 | ✅ pass | M-5 | | TC-0715 | UpdateDept 乐观锁冲突返回 ErrConflict | ✅ pass | M-5 | | TC-0181 | UserDetail 超管在产品上下文仅返回该产品 roleIds | ✅ pass | M-3 | | TC-0108 | DeleteDept 不存在部门返回 404 | ✅ pass | M-11 | ### 十五、审计修复回归 — 本轮新增(H-4 / M-1 / M-14 / L-3 / L-5) | TC编号 | 测试场景 | 测试结果 | 关联修复 | | :--- | :--- | :--- | :--- | | TC-0720 | Logout 正常:tokenVersion 0→1 且 loader 缓存被清理 | ✅ pass | M-1 | | TC-0721 | Logout 未登录返回 401 | ✅ pass | M-1 | | TC-0722 | Logout 连续两次 tokenVersion 累加至 2 | ✅ pass | M-1 | | TC-0723 | RemoveMember 移除产品最后一个 ADMIN 被拒 | ✅ pass | H-4 | | TC-0724 | RemoveMember 有 2 个 ADMIN 时可以移除其一 | ✅ pass | H-4 | | TC-0725 | UpdateMember 降级唯一 ADMIN 被拒 | ✅ pass | H-4 | | TC-0726 | UpdateMember 有 2 个 ADMIN 时降级其一成功 | ✅ pass | H-4 | | TC-0727 | UpdateMember 仅 1 个启用 ADMIN 时降级被拒(CountActiveAdmins 只算 Enabled) | ✅ pass | H-4 | | TC-0728 | RemoveMember 非 ADMIN 不触发 last-admin 校验 | ✅ pass | H-4 | | TC-0729 | AddMember 禁用产品拒绝添加成员 | ✅ pass | L-5 | | TC-0730 | UpdateRole 非超管降低 PermsLevel 被拒 | ✅ pass | L-3 | | TC-0731 | UpdateRole 非超管保持/提升 PermsLevel 允许 | ✅ pass | L-3 | | TC-0732 | UpdateRole 超管可任意降低 PermsLevel | ✅ pass | L-3 | | TC-0733 | UpdateRole PermsLevel 越界拒绝 (0/-1/1000/10000) | ✅ pass | L-3 | | TC-0734 | SetUserPerms 产品被禁用时拒绝 | ✅ pass | M-14 | | TC-0735 | SetUserPerms 产品不存在返回 404 | ✅ pass | M-14 | ### 十六、审计修复回归 — 本轮新增(H-A / H-B / M-B / M-C / L-B / L-C / L-F) | TC编号 | 测试场景 | 测试结果 | 关联修复 | | :--- | :--- | :--- | :--- | | TC-0736 | IncrementTokenVersion 返回值 == DB 持久值 | ✅ pass | H-B | | TC-0737 | IncrementTokenVersion 事务成功后主动清缓存 | ✅ pass | H-B | | TC-0738 | 10 goroutine 并发自增:返回值唯一 & 最终 DB = 起始+N | ✅ pass | H-B | | TC-0739 | Logout 超过 TokenOpLimiter 配额返 429 且不再递增 tokenVersion | ✅ pass | L-C | | TC-0740 | Logout 限流按 userId 隔离(A 满额不影响 B) | ✅ pass | L-C | | TC-0741 | RefreshToken 超配额返 429 且不递增 tokenVersion | ✅ pass | M-B | | TC-0742 | RefreshToken 限流按 userId 隔离(productCode 无关) | ✅ pass | M-B | | TC-0743 | SetUserPerms 普通 MEMBER 不得给自己授权 | ✅ pass | H-A | | TC-0744 | SetUserPerms DEVELOPER 调用者被拦截(非 self 场景) | ✅ pass | H-A | | TC-0745 | SetUserPerms 同产品 ADMIN 操作合法 MEMBER 放行 | ✅ pass | H-A | | TC-0746 | UpdateUser DEVELOPER 跨子树移动目标被拒 | ✅ pass | L-F | | TC-0747 | UpdateUser DEVELOPER 子树内移动放行 | ✅ pass | L-F | | TC-0748 | UpdateUser 产品 ADMIN 不受子树限制 | ✅ pass | L-F | | TC-0749 | jwtauth TokenVersion 失效优先于 ProductStatus 返回 401 | ✅ pass | L-B | | TC-0750 | jwtauth TokenVersion 通过后才返回 403 "产品已禁用" | ✅ pass | L-B | | TC-0751 | ValidateProductLogin 用户名不存在走 dummy bcrypt 返同文案 | ✅ pass | M-C | | TC-0752 | ValidateProductLogin 用户名存在但密码错:与 TC-0751 对照一致 | ✅ pass | M-C | | TC-0753 | UsernameLoginLimit 按 `ip:username` 分桶,不误伤同 IP 其他用户 | ✅ pass | M-C | ### 十七、审计修复回归 — 第四轮 (audit-report.md 2026-04-19) > 覆盖第四轮审计报告修复项:H-1 ~ H-5(P0),M-1/M-2/M-5/M-6/M-7/M-10/M-11/M-13/M-14/M-15(P1/P2),L-1/L-2(P3)。 > 已有用例 TC-0016/TC-0021 预期结果同步对齐 M-7 安全修复,不再向旧逻辑妥协。 | TC编号 | 测试场景 | 测试结果 | 修复项 | | :--- | :--- | :--- | :--- | | TC-0760 | UpdateMember 保持 ADMIN 但禁用最后一个 ADMIN 被拒 | ✅ pass | H-1 | | TC-0761 | UpdateMember 同时降级+禁用唯一 ADMIN 被拒 | ✅ pass | H-1 | | TC-0762 | UpdateMember 有 2 个 ADMIN 时禁用其一成功 | ✅ pass | H-1 | | TC-0763 | RemoveMember 事务内 locked 数据判断唯一 ADMIN 拒绝 | ✅ pass | H-2 | | TC-0764 | RemoveMember 非 ADMIN 不触发 last-admin 校验 | ✅ pass | H-2 | | TC-0765 | CountActiveAdminsTx FOR UPDATE 正确计数活跃 ADMIN | ✅ pass | H-3 | | TC-0766 | DeleteDept FOR UPDATE 子部门存在拒绝 | ✅ pass | H-4 | | TC-0767 | DeleteDept FOR UPDATE 关联用户存在拒绝 | ✅ pass | H-4 | | TC-0768 | CreateDept 父部门 FOR SHARE 锁防并发删除 | ✅ pass | H-4 | | TC-0769 | ChangePassword 限流超额返回 429 | ✅ pass | H-5 | | TC-0770 | 冻结用户 ChangePassword 返回 403 | ✅ pass | M-15 | | TC-0771 | ChangePassword 原密码错误记录审计日志 | ✅ pass | H-5 | | TC-0772 | UpdateUserStatus 状态无变化不递增 tokenVersion | ✅ pass | M-1 | | TC-0773 | UpdateUserStatus 状态实际变化时正常递增 | ✅ pass | M-1 | | TC-0774 | CreateProduct appKey 长度 32 hex (16 字节) | ✅ pass | M-2 | | TC-0775 | CreateProduct appSecret 长度 64 hex (32 字节) | ✅ pass | M-2 | | TC-0776 | CreateProduct adminPassword 长度 24 hex (12 字节) | ✅ pass | M-2 | | TC-0777 | UpdateRole FindUserIdsByRoleId 失败返回 500 | ✅ pass | M-5 | | TC-0778 | UpdateRole 乐观锁冲突返回 409 | ✅ pass | M-6 | | TC-0779 | UpdateProduct 乐观锁冲突返回 409 | ✅ pass | M-6 | | TC-0780 | UpdateMember 事务内基于 locked 数据更新 | ✅ pass | M-6 | | TC-0016 | AdminLogin 普通用户统一返回 401 "用户名或密码错误" | ✅ pass | M-7 | | TC-0021 | AdminLogin 冻结用户统一返回 401 "用户名或密码错误" | ✅ pass | M-7 | | TC-0781 | AdminLogin 不存在用户走 dummy bcrypt 恒时返回 | ✅ pass | M-7 | | TC-0782 | VerifyToken 失败分支日志含 reason 字段 | ✅ pass | M-10 | | TC-0783 | gRPC Login 无 peer 时限流 key="unknown" 不跳过 | ✅ pass | M-11 | | TC-0784 | IsDuplicateEntryErr 识别 MySQL 1062 | ✅ pass | M-14 | | TC-0785 | IsDuplicateEntryErr 不误判非 1062 | ✅ pass | M-14 | | TC-0786 | SetUserPerms 调用后 req.Perms 不被修改 | ✅ pass | L-1 | | TC-0787 | BindRoles 调用后 req.RoleIds 不被修改 | ✅ pass | L-1 | | TC-0788 | BindRolePerms 调用后 req.PermIds 不被修改 | ✅ pass | L-1 | | TC-0789 | 空 memberType 显式返回 403 "缺少产品成员上下文" | ✅ pass | L-2 | | TC-0754 | JWT 优先级: 用户已删 vs 冻结, 先返 401 "用户不存在" | ✅ pass | QA-P5 JWT 矩阵 | | TC-0755 | JWT 优先级: 冻结 + TokenVer 过期, 先返 401 "账号已冻结" | ✅ pass | QA-P5 JWT 矩阵 | | TC-0756 | JWT 优先级: TokenVer 过期 + 产品禁用, 先返 401 "TokenVersion 已过期" | ✅ pass | QA-P5 JWT 矩阵 | | TC-0757 | JWT 优先级: 产品禁用 + 非成员, 先返 403 "产品已禁用" | ✅ pass | QA-P5 JWT 矩阵 | | TC-0758 | JWT 优先级: 仅非成员身份, 返 403 "非产品成员" | ✅ pass | QA-P5 JWT 矩阵 | | TC-0759 | `UpdateWithOptLock` 10 并发, 恰好 1 成功 + 9 个 `ErrUpdateConflict` | ✅ pass | QA-P5 并发乐观锁 | | TC-0790 | `TokenOpLimiter` 配额耗尽后等 TTL, 下一周期恢复放行 | ✅ pass | QA-P5 时间窗滚动 | | TC-0791 | Redis 不可达时 `TokenOpLimiter` fail-open, Logout 仍然成功 + tokenVersion+1 | ✅ pass | QA-P5 fail-open | | TC-0792 | `UserDetailsLoader` 并发 50 路 Load 合并为 1 次 `FindOne` (singleflight) | ✅ pass | QA-P5 singleflight | | TC-0793 | singleflight 首次加载后再次 Load 命中 Redis 缓存, `FindOne` 不再递增 | ✅ pass | QA-P5 缓存命中 | | TC-0794 | `FuzzVerifyToken` 对畸形/alg=none/unicode 噪声 token 永不 panic, 返回 Valid=false | ✅ pass | QA-P5 gRPC fuzz | | TC-0795 | `FuzzGetUserPerms` 错误码稳定在 Unauth/PermDenied/InvalidArg/NotFound/Internal 集合内 | ✅ pass | QA-P5 gRPC fuzz | | TC-0796 | `LogoutHandler` 无用户上下文返 401 "未登录" | ✅ pass | QA-P5 handler 契约 | | TC-0797 | `LogoutHandler` 合法用户上下文返 200 且 tokenVersion+1 | ✅ pass | QA-P5 handler 契约 | | TC-0798 | `ChangePasswordHandler` 非法 JSON body 返 400, 不泄漏业务语义 | ✅ pass | QA-P5 handler 契约 | | TC-0799 | `ChangePasswordHandler` 缺失 oldPassword/newPassword 字段返 400 | ✅ pass | QA-P5 handler 契约 | | TC-0800 | `RefreshTokenHandler` 缺 Authorization 返 401/400, 不泄漏实现 | ✅ pass | QA-P5 handler 契约 | | TC-0801 | `RefreshTokenHandler` 垃圾 bearer token 返 401, 无 panic/500 | ✅ pass | QA-P5 handler 契约 | --- ## 三、测试结论 ### 3.1 整体质量评估:**极高** - **622 个 TC 全部执行,通过 621,跳过 1 (防御性不可达分支),失败 0。** - 第 5 批"QA 主动补齐":按照上一版 `test-report.md` §3.4 自列的 8 条后续建议,由 QA 而非用户亲自实现落地,共新增 **18 组对抗性测试用例 (TC-0754 ~ TC-0759、TC-0790 ~ TC-0801)**,覆盖 JWT 鉴权优先级矩阵、`SysDept` 真并发乐观锁、`TokenOpLimiter` 时间窗滚动 + Redis fail-open、`UserDetailsLoader` singleflight 并发合并、gRPC `VerifyToken` / `GetUserPerms` fuzz、以及 4 个关键 handler 入口的 HTTP 契约。 - 连同第 4 轮 30 组 (TC-0760 ~ TC-0789) + 第 3 批 18 组 (TC-0736 ~ TC-0753) + 第 2 批 16 组 (TC-0720 ~ TC-0735) + 第 1 批零散修复 15 组 (TC-0105、TC-0108、TC-0181、TC-0208、TC-0700 ~ TC-0716) = **累计 97 组专项审计/主动补齐回归用例全部通过**,断言严格对齐修复后行为,未向旧逻辑妥协。 - 共 776 个顶层 Test 函数 + 106 个子用例/Fuzz seed = 882 次测试执行事件,通过 881,跳过 1,失败 0。 - 业务代码 (logic / model / loaders / middleware / server / response) 覆盖率加权平均 ≈ 87.9%,核心包均在 74.2% 以上;**`middleware` 从 80.3% 拉升到 97.0%**;整体 `./...` 覆盖率 58.1% 包含大量 handler 薄层 / pb / permclient / testutil 生成或桩代码。 - 唯一跳过用例 TC-0263 为防御性不可达分支 (`claims` 类型断言失败,运行时无法触达),已标记 `t.Skip`,不影响业务正确性。 - 并行跑 (`go test ./...`) 偶发 flaky: `model/perm.TestSysPermModel_BatchInsert_Bulk1000`、`internal/server.TestLogin_*` 等与其他包争抢同一测试 DB 清理时会失败;串行 (`go test -p=1 ./...`) 100% 稳定通过。定位为测试基础设施层, 非产品缺陷, 已在 §3.4 第 1 条列为改进项. ### 3.2 修复验证亮点 | 风险项 | 修复验证断言 | 价值 | | :--- | :--- | :--- | | H-1 | ADMIN/DEVELOPER 不再被 permsLevel 误阻 (TC-0711/0712);MEMBER sentinel 场景被保护 (TC-0713);MEMBER 越权仍拒绝 (TC-0208) | 阻止"本该有权的管理员被自家规则误杀"的 P0 生产故障 | | H-2 + H-3 | gRPC GetUserPerms 对冻结/非成员/DEV 部门禁用成员全部 PermissionDenied (TC-0700~0702);Loader 层亦验证 DEV+禁用成员不再命中全量权限分支 (TC-0704) | 堵住攻击面最广的 token 发放旁路,横跨 API 与 RPC | | M-2 | 批量 `DELETE ... IN (...)` 正确性 + 空集合保护 + userId WHERE 约束 (TC-0707~0709) | 消除 N+1,提升 BindRoles/BindRolePerms 大批量操作性能 | | M-3 | 超管产品上下文只返回该产品角色 (TC-0181) | 防止跨产品信息越权 | | M-4 | 禁用角色被严格过滤 (TC-0706) | 避免 Loader 因过期绑定返回错误 MemberType | | M-5 | 乐观锁冲突可检出;DeptType/Status 未变时不触发级联缓存清理 (TC-0105/0714/0715) | 防并发更新丢字段 + 消除 O(N) 缓存抖动 | | M-6 | access token payload 确实不存在 `perms` 字段 (TC-0716,base64 解码校验) | 清理长期 Dead field,缩小 token 体积/攻击面 | | M-11 | 删除不存在部门返回 404 而非静默成功 (TC-0108) | 消除 TOCTOU + 调用方误以为成功的隐性 BUG | | L-2 | 产品登录耗尽配额后管后登录仍可放行 (TC-0710) | 避免攻击一条入口拖垮另一条 | | L-5 (Loader) | Loader 对不存在用户不留缓存,多次调用均走 DB (TC-0705) | 避免零值 UserDetails 长期污染缓存 | | **H-4** | 产品最后一个启用 ADMIN 不能被移除或降级 (TC-0723~0727);禁用成员不计入 active 计数 (TC-0727);非 ADMIN 不受影响 (TC-0728) | 阻断"误删最后一个管理员导致产品完全失控"的 P0 事故 | | **M-1** | `/auth/logout` 真正递增 tokenVersion 并清 Loader 缓存;未登录返 401;多次登出累加 (TC-0720~0722) | 让"登出"不再是假操作,后续 refresh token 直接作废 | | **M-14** | setUserPerms 对已禁用产品 400 拒绝;产品不存在 404 (TC-0734/0735) | 消除"产品已禁用但管理员仍能给成员发权限"的漏洞 | | **L-3** | 非超管 admin 不得降低 role.PermsLevel,但保持/提升被允许;超管例外 (TC-0730~0732);越界参数 400 (TC-0733) | 阻断"普通 admin 通过降低角色级别绕过权限层级"的越权路径 | | **L-5 (addMember)** | 禁用产品禁止新增成员 (TC-0729) | 让"产品禁用"真正形成写操作闭环,不仅 `login` / `refresh` 失效,DDL 类写入亦被拦截 | | **H-A** | `SetUserPerms` 调用者必须是同产品 ADMIN/超管:MEMBER 自提权被拦 (TC-0743);DEVELOPER 操作他人同样被拦 (TC-0744);合法 ADMIN 通路未受损 (TC-0745) | 阻断"普通成员直接调用 /setUserPerms 自我赋权"的 P0 越权,切断最短 root 路径 | | **H-B** | `IncrementTokenVersion` 原子自增 (`LAST_INSERT_ID(tokenVersion+1)`):返回值 == DB 值 (TC-0736)、主动清缓存 (TC-0737)、10 并发返回值唯一且终值 = 起始+N (TC-0738) | 消除旧实现"读缓存 + 1"在并发下的 stale write,保证登出/强制踢出后所有老 token 必然作废 | | **M-B** | `/auth/refreshToken` 接入 `TokenOpLimiter`:超配额 429 且**不再递增 tokenVersion** (TC-0741);按 userId 隔离 (TC-0742) | 阻止攻击者用 refresh 接口持续废除合法用户的 refresh token(DoS 自己);且限流命中路径不进入业务层副作用 | | **L-C** | `/auth/logout` 接入 `TokenOpLimiter`:超配额 429 且不递增 tokenVersion (TC-0739);按 userId 分桶 (TC-0740) | 切断"反复调 logout 把 tokenVersion 冲高 + 污染缓存"的低成本骚扰攻击 | | **M-C** | 产品登录用户名枚举防护:不存在用户仍走 dummy bcrypt 返同一错误文案 (TC-0751/0752);`UsernameLoginLimit` key = `ip:username` (TC-0753) | 消除通过响应文案/时序差异枚举用户名的攻击面;限流按用户粒度隔离避免同 IP 被一个失败账号拖垮 | | **L-B** | `jwtauthMiddleware` 校验顺序:TokenVersion 失效优先于 ProductStatus (TC-0749),通过后才看产品状态 (TC-0750) | 保证强制踢出/登出后客户端立刻拿到 401 (而非 403 "产品禁用"),前端能走正确的"重新登录"分支 | | **L-F** | `UpdateUser` DEVELOPER 必须在自身子树内移动目标 (TC-0746/0747);ADMIN 豁免 (TC-0748) | 防止 DEVELOPER 把成员挪出自己职责子树从而变相"扩大接管范围" | | **H-1 (R4)** | `UpdateMember` 禁用唯一 ADMIN 被拒、降级+禁用同时被拒、有冗余 ADMIN 时放行 (TC-0760~0762) | 堵住"保持 ADMIN 身份但冻结"绕过 last-admin 保护的 P0 漏洞 | | **H-2 (R4)** | `RemoveMember` 事务内使用 locked 行判断,唯一 ADMIN 拒绝、非 ADMIN 放行 (TC-0763/0764) | 消除事务外快照读导致的 stale-data 决策错误 | | **H-3 (R4)** | `CountActiveAdminsTx` SELECT FOR UPDATE 仅计启用 ADMIN (TC-0765) | 防止 snapshot read 导致并发降级/删除突破计数 | | **H-4 (R4)** | `DeleteDept` FOR UPDATE 子部门/用户存在拒绝 (TC-0766/0767);`CreateDept` FOR SHARE 锁父部门 (TC-0768) | 消除 dept 级联 TOCTOU | | **H-5/M-15 (R4)** | `ChangePassword` 限流 429 (TC-0769)、冻结用户 403 (TC-0770)、审计日志 (TC-0771) | 防暴力猜旧密码 + 冻结账号深度防御 | | **M-1 (R4)** | `UpdateUserStatus` 无变化短路不递增 tokenVersion (TC-0772);实际变化时递增 (TC-0773) | 消除管理员重复操作导致用户误踢下线 | | **M-2 (R4)** | `generateRandomHex` 输出长度正确:appKey=32/appSecret=64/adminPwd=24 (TC-0774~0776) | 恢复被截断丢失的一半密码学熵 | | **M-5 (R4)** | `FindUserIdsByRoleId` 失败不再静默吞错,返回 500 + 日志 (TC-0777) | 保证缓存清理失败可感知、可追溯 | | **M-6 (R4)** | `UpdateRole`/`UpdateProduct` 乐观锁 409 (TC-0778/0779);`UpdateMember` 基于 locked 更新 (TC-0780) | 防并发更新丢字段 | | **M-7 (R4)** | `AdminLogin` 所有失败路径统一 401 "用户名或密码错误",不存在用户走 dummy bcrypt (TC-0016/0021/0781) | 从响应码 + 文案 + 时序三维消除 admin 用户枚举 | | **M-10 (R4)** | `VerifyToken` 各失败分支有结构化日志 (TC-0782) | 安全团队可审计每一次 token 校验失败原因 | | **M-11 (R4)** | gRPC Login 无 peer 上下文时使用 `"unknown"` 作限流 key (TC-0783) | 阻断 fail-open 限流旁路 | | **M-14 (R4)** | `IsDuplicateEntryErr` 类型断言 1062 (TC-0784/0785) | 不再依赖脆弱的字符串匹配 | | **L-1 (R4)** | `SetUserPerms`/`BindRoles`/`BindRolePerms` 调用后 req 对象不变 (TC-0786~0788) | 消除请求入参副作用 | | **L-2 (R4)** | 空 memberType 显式 403 "缺少产品成员上下文" (TC-0789) | 不再依赖 sentinel 值的隐式行为 | | **QA-P5 JWT 矩阵** | JWT 多因素叠加下错误优先级固化: 用户已删 > 冻结 > TokenVer 过期 > 产品禁用 > 非成员 (TC-0754 ~ TC-0758) | 让 middleware 返回码对前端可预测, 杜绝"同一故障两个版本两个错误码" | | **QA-P5 真并发乐观锁** | `UpdateWithOptLock` 10 并发恰好 1 成功 + 9 × `ErrUpdateConflict` (TC-0759) | 真并发下验证乐观锁 (之前只有 mock 层断言), 把 M-5 修复的保护从"结构正确"升级到"行为正确" | | **QA-P5 限流时间窗 & 容错** | `TokenOpLimiter` 配额耗尽后 TTL 窗口滚动恢复 (TC-0790); Redis 不可达时 fail-open 不阻塞登出 (TC-0791) | 防止"时钟抖动 / Redis 过期策略"导致意外 fail-open, 同时保证 Redis 故障不影响用户登出这一安全动作 | | **QA-P5 Loader 并发合并** | `UserDetailsLoader` 50 并发 Load 合并为 1 次 `FindOne` (TC-0792), 后续查询命中 Redis 缓存不再打 DB (TC-0793) | 高并发首次加载防止 DB 击穿, 覆盖 L-5 修复在真实并发下的行为 | | **QA-P5 gRPC Fuzz** | `VerifyToken` 对畸形/alg=none/unicode 噪声永不 panic 且稳定返 Valid=false (TC-0794); `GetUserPerms` 错误码落在固定分类集合 (TC-0795) | 把 gRPC 边界的异常输入面从"抽查"升级到"随机化覆盖", 建立错误分类护栏 | | **QA-P5 Handler 契约** | Logout/ChangePassword/RefreshToken 6 个 HTTP 契约: 未登录 401, 非法 body 400, 垃圾 bearer 401, 合法请求 200 + 副作用 (TC-0796 ~ TC-0801) | `handler/auth` 从 0% → 50% 覆盖, `handler/pub` 25% → 47.5%; 顶替原"仅 logic 层覆盖 → handler 纯薄层无测试"的空白 | | **QA-P6 审计回归 H-1 (R5)** | `IncrementTokenVersionIfMatch` 原子 CAS: 命中递增、失配返 `ErrTokenVersionMismatch`、并发仅 1 胜出且 DB 只 +1、成功后双路缓存一致 (TC-0802 / TC-0803 / TC-0805 / TC-0806);logic 层 6 并发仅 1 胜 + 5 × 401 "登录状态已失效" (TC-0812)(TC-0804 已随 M-8 新契约删除,CAS 未命中统一为 `ErrTokenVersionMismatch`) | 彻底消除刷新令牌并发窗口下"两枚合法新 rt"造成的会话劫持;中断攻击者的静默接管路径 | | **QA-P6 审计回归 H-2 + M-7 (R5)** | gRPC RefreshToken / VerifyToken IP 级限流在配额用尽后 `ResourceExhausted`,同 IP 不同端口共享桶;`extractClientIP` 对 `host:port` 必须剥成 host,无 peer 必 error;gRPC refresh 成功后重放旧 rt 返 Unauthenticated (TC-0828 ~ TC-0831) | 阻断"通过切换 TCP 源端口绕过限流"的枚举 / DoS / token-oracle 攻击面 | | **QA-P6 审计回归 H-3 (R5)** | `BindRoles` 中 MEMBER 调用者不得把与自身同级的角色赋给他人;DB 状态不变 (TC-0813) | `GuardRoleLevelAssignable` 的 `>=` 护栏,封堵"自等升权"侧信道 | | **QA-P6 审计回归 H-4 (R5)** | `UpdateUser` 将 `deptId` 置 0 必须是 ADMIN / 超管;DEVELOPER/MEMBER 403 且 DB 不变;ADMIN/超管放行 (TC-0814 ~ TC-0817) | 防"把用户挪出部门树"从而变相脱离管理视野的低成本越权 | | **QA-P6 审计回归 L-1 (R5)** | `CreateUser` 未显式指定时 `mustChangePassword` DB 默认落盘 1 (TC-0818) | 新账号必须走强制首次改密,杜绝"默认永久弱口令" | | **QA-P6 审计回归 L-4 (R5)** | `checkPermLevel` 对通用 DB 错误返 500(fail-close),只有 `sqlx.ErrNotFound` 才走 403 (TC-0819 / TC-0820) | 禁止把"DB 暂时不可用"曲解为"没角色 → 403"的静默放行 | | **QA-P6 审计回归 M-3 (R5)** | `UserDetailsLoader` 对不存在用户写入 sentinel 并设置 `negativeCacheTTL`;sentinel 不登记到 Clean 索引;50 并发收敛到 sentinel (TC-0821 ~ TC-0823) | 切断"携带已删除用户 token"的持续 DB 击穿 DoS | | **QA-P6 审计回归 M-5 (R5)** | `CreateProduct` 遇到通用 1062(message 不含 "uk_code")仍返 `ErrConflict`(409) (TC-0827) | 去掉脆弱 `strings.Contains` 判定,靠 `mysql.MySQLError.Number` | | **QA-P6 审计回归 M-6 (R5)** | 新基础设施 `FindMapByProductCodeWithTx` / `LockByCodeTx` 语义齐备:事务内 map 查等价非事务、空产品返回非 nil map、存在 code 锁行、并发 FOR UPDATE 阻塞、不存在返 `ErrNotFound` (TC-0807 ~ TC-0811);`SyncPerms` 入参级去重避免 tx 内自撞 UNIQUE (TC-0826) | 让产品权限同步在并发窗口下被 FOR UPDATE 串行化;避免自引用 1062(TC-0824/0825 的 409 映射契约随 H-3 彻底取消,由 TC-0048 "任何 tx 错误统一 500 不泄露驱动细节"取代) | | **QA-P6 审计回归 M-B (R5)** | `/api/auth/refreshToken` 的 `rest.WithMiddlewares` 块静态包含 `serverCtx.RefreshTokenRateLimit`(源码正则断言);行为侧同 IP 不同端口第 2 次即 429 "过于频繁",不同 IP 不受影响 (TC-0832 / TC-0833) | 结构性防剥离 + 运行期验证双保险,杜绝"中间件被误删但接口看似正常"的静默回滚 | ### 3.3 发现的核心缺陷 - **本轮测试未发现新 BUG**:所有断言严格对齐修复后的预期行为 (真实场景驱动),未出现因迁就源码而放宽的断言。 - 对于历史遗留缺陷 (H-1 ~ L-5) 及第四轮审计修复 (H-1~H-5, M-1~M-15, L-1~L-2) 的回归,以及本轮(第五轮)审计 `audit-report.md` 中 H-1/H-2/H-3/H-4/M-3/M-5/M-6/L-1/L-4/M-B/M-7 的修复,测试脚本均已作为"防退化护栏"沉淀。后续一旦有人: - 把 `IncrementTokenVersionIfMatch` 改回 "读一次 + 写回" 的非原子实现 - 把 `/api/auth/refreshToken` 的 `RefreshTokenRateLimit` 中间件从 routes.go 里剥离 - 把 gRPC `extractClientIP` 退化为不剥端口的 `p.Addr.String()` - 把 `BindRoles` 的 `>=` 护栏改回 `>` - 把 `UpdateUser` 放开 deptId=0 的 ADMIN 门禁 - 把 `CreateUser` 的 `mustChangePassword` 默认改回 No - 把 `checkPermLevel` 的 DB 错误分支重新吞成 "no roles → 403" - 移除 `UserDetailsLoader` 的负缓存 sentinel - 把 `CreateProduct` 的 1062 判定改回 `strings.Contains("uk_code")` - 在 `SyncPerms` 里丢掉 `LockByCodeTx` / `FindMapByProductCodeWithTx` / 入参去重 相应 TC 会立即失败。 ### 3.4 后续测试建议 (本轮进度) > 说明: 上一版报告 §3.4 列出的 8 条建议, 作为 QA 本职, 已在第 5 批 (TC-0754 ~ TC-0759 / TC-0790 ~ TC-0801) 内主动落地实现, 不再作为遗留事项抛给开发. 本轮(第 6 批)又对齐最新 `audit-report.md` 追加了 32 条审计回归 TC(TC-0802 ~ TC-0833). 仍然保留未完成的测试基础设施改进项, 以及本轮运行中新发现的后续可扩展方向. #### ✅ 已完成 (第 5 批 QA 主动补齐) | # | 原建议 | 实现情况 | 对应 TC | | :--- | :--- | :--- | :--- | | 1 | handler 薄层契约测试 | `LogoutHandler`/`ChangePasswordHandler`/`RefreshTokenHandler` 6 个契约用例, `handler/auth` 覆盖率从 0% → 50%、`handler/pub` 从 25% → 47.5% | TC-0796 ~ TC-0801 | | 2 | 真实并发双写 sys_dept | 10 goroutine 并发 `UpdateWithOptLock`, 断言恰好 1 个成功 + 9 个 `ErrUpdateConflict` | TC-0759 | | 3 | gRPC fuzz (VerifyToken / GetUserPerms) | 两个 `Fuzz*` 函数 + 种子语料, 断言"永不 panic + 错误码落在稳定分类集内" | TC-0794 / TC-0795 | | 4 | Loader singleflight 并发 | 并发 50 路 `Load(same userId)` 合并为 1 次 `FindOne`; 后续再 Load 命中 Redis 缓存, `FindOne` 不再递增 | TC-0792 / TC-0793 | | 5 | 限流 Redis fail-open | `TokenOpLimiter` 指向不可达 Redis 时走 fail-open, `Logout` 仍然成功并递增 tokenVersion | TC-0791 | | 7 | JWT 鉴权优先级完整矩阵 | 补齐 "用户已删 vs 已冻结 vs TokenVer 过期 vs 产品禁用 vs 非成员" 5 组多因素叠加场景的错误优先级断言 | TC-0754 ~ TC-0758 | | 8 | `TokenOpLimiter` 时间窗滚动 | 窗口耗尽 → 等待 TTL → 下一周期恢复放行, 保证不会因时钟偏移 / 过期策略意外 fail-open | TC-0790 | #### ✅ 已完成 (第 6 批 QA 主动补齐 · 审计回归) | # | 审计条目 | 实现情况 | 对应 TC | | :--- | :--- | :--- | :--- | | 1 | H-1 RefreshToken CAS | 模型层 5 条(含 16→8 并发唯一胜出)+ logic 层 1 条 6 并发 | TC-0802 ~ TC-0806 / TC-0812 | | 2 | H-2 gRPC 限流 + M-7 剥端口 | gRPC Refresh/Verify 同 IP 第 2 次 429 + 剥端口契约 + CAS 失效 | TC-0828 ~ TC-0831 | | 3 | H-3 BindRoles 等级护栏 | MEMBER 给同级角色被 403,DB 状态无变化 | TC-0813 | | 4 | H-4 UpdateUser deptId=0 | 4 种 caller 身份的放行/拒绝矩阵,含合法 ADMIN/超管放行正例 | TC-0814 ~ TC-0817 | | 5 | L-1 CreateUser 默认强制改密 | 未指定字段时 DB 值必须是 1 | TC-0818 | | 6 | L-4 checkPermLevel fail-close | 通用 DB 错误 500,ErrNotFound 仍 403 | TC-0819 / TC-0820 | | 7 | M-3 UserDetailsLoader 负缓存 | sentinel 写入/不进索引/并发收敛 | TC-0821 ~ TC-0823 | | 8 | M-5 CreateProduct 通用 1062 映射 | message 不含 "uk_code" 仍返 409 | TC-0827 | | 9 | M-6 SyncPerms 基础设施 + 入参去重 | Tx 读 / 锁产品行 / 入参去重(1062→409 映射契约随 H-3 取消) | TC-0807 ~ TC-0811 / TC-0826 | | 10 | M-B HTTP 路由中间件挂载 | routes.go 静态 wiring + 行为级 429 | TC-0832 / TC-0833 | #### ⏳ 仍建议后续补强 (非产品缺陷, 基础设施或扩展方向) 1. **测试 DB 并发隔离 (第 6 条)**: 并行跑 `go test ./...` 时 `model/perm.TestSysPermModel_BatchInsert_Bulk1000`、`internal/server.TestLogin_*` 等仍会偶发与其他包争用同一测试库的清理流程. 建议为大批量插入 / 登录相关用例启用独立 schema 或 `t.Cleanup + 唯一表前缀` 隔离, 彻底消除 flaky. 当前通过 `go test -p=1 ./...` 串行执行规避, 不阻塞发布. 2. **Fuzz 语料纳入 CI**: 目前 TC-0794/TC-0795 以 seed corpus 形式跑过一次, 建议把 `testdata/fuzz/**` 的回归用例在 CI 中用 `go test -run=^Fuzz.*$` 形式每次执行, 避免新引入的崩溃路径静默遗漏. 3. **Chaos 级 Redis/DB 故障注入**: 本轮 fail-open 只覆盖 "Redis 整个不可达" 的全断场景; 后续可补 Redis 超时 / 抖动 / 读副本滞后, 以及 MySQL 只读副本迟滞下 `FOR UPDATE` 行为的混沌测试. 4. **HTTP 层 E2E**: 当前 handler 契约测试走 `httptest.NewRecorder`; 建议在发布前以 `go-zero rest.Server` 起实例, 用真实 HTTP Client 打一轮冒烟, 以覆盖 go-zero 框架级中间件链。 --- ## 九、第 6 轮审计回归补充(2026-04-19 · QA 主动补齐第五批 + 同日清理) > 本节对齐 `audit-report.md` 第 6 轮审计条目 H-1 / H-2 / H-3 / M-1 / M-2 / M-4 / M-5 / M-6 / M-8 / L-5。 > 初批新增 **41 条独立 Test 函数**(对应 `test-design.md` TC-0834 ~ TC-0870 共 37 条 TC 及少量补充防御用例); > 同日又按用户指示"过时用例按新契约重写,无用的直接删除"完成清理: > > - 新增 **2 条** (TC-0871 ProductList 超管路径 / TC-0872 ProductDetail 无差别 404) > - 就地重写 **1 条** (TC-0048 tx 回滚按 H-3 新契约) > - 迁移 **1 条** (TC-0826 入参去重从被删文件拆出来) > - 删除 **18 条** 过时用例(见 §9.3.1) > > 每条新/留存用例均对修复后的**契约**断言,任何人只要把修复改回旧路径,对应 TC 立即 FAIL。 > 清理完成后全仓 `go test ./... = 0 FAIL`。 ### 9.1 新增测试文件与 TC 映射 | 新增文件 | 审计条目 | 覆盖 TC | 新增 Test 数 | 全部 pass | | :--- | :--- | :--- | ---: | :---: | | `internal/logic/pub/adminLoginIpLimit_audit_test.go` | H-1 | TC-0834 ~ TC-0837 | 4 | ✅ | | `internal/logic/pub/loginServiceConstantTime_audit_test.go` | H-2 | TC-0838 ~ TC-0842 | 5 | ✅ | | `internal/logic/pub/syncPermsTxLock_audit_test.go` | H-3 | TC-0843 ~ TC-0845 | 3 | ✅ | | `internal/logic/dept/updateDeptCleanBatch_audit_test.go` | M-1 | TC-0848 / TC-0849 + 1 边界 | 3 | ✅ | | `internal/loaders/userDetailsLoaderCleanByUserIds_audit_test.go` | M-1 | TC-0846 / TC-0847 | 2 | ✅ | | `internal/logic/product/productAccessControl_audit_test.go` | M-2 | TC-0850 ~ TC-0854 | 5 | ✅ | | `internal/logic/dept/deptTreeAccessControl_audit_test.go` | M-2 | TC-0855 ~ TC-0857 | 3 | ✅ | | `internal/logic/role/postCommitCacheDegraded_audit_test.go` | M-4 | TC-0858 / TC-0859 | 2 | ✅ | | `internal/logic/auth/checkManageAccessPrefetch_audit_test.go` | M-5 | TC-0860 / TC-0861 + 1 防御 | 3 | ✅ | | `internal/middleware/ratelimitMiddlewareXff_audit_test.go` | M-6 | TC-0862 ~ TC-0866 + 2 防御 | 7 | ✅ | | `internal/model/productmember/countOtherActiveAdmins_audit_test.go` | L-5 | TC-0867 ~ TC-0870 | 4 | ✅ | | `internal/logic/product/productAccessControl_audit_test.go`(清理增补) | M-2 | TC-0871 / TC-0872 | 2 | ✅ | | `internal/logic/pub/syncPermsLogic_mock_test.go`(按新契约就地重写) | H-3 | TC-0048 | 1(改写) | ✅ | | `internal/logic/pub/syncPermsDedup_audit_test.go`(从被删文件迁移) | H-3 | TC-0826 | 1 | ✅ | | **合计** | — | **39 TC + 4 防御 + 1 就地重写** | **45** | **45/45** | > M-8 (`IncrementTokenVersionIfMatch` 新签名) 在前一轮已有 `incrementTokenVersionIfMatch_audit_test.go`,本轮同步签名并删除 TC-0804(旧"用户不存在 ≠ Mismatch"契约已被新契约取代)。 ### 9.2 新增测试结果一览 命令:`go test -count=1 -run '<第 9.1 节 41 条新增 Test 的并集正则>' ./...` | 统计 | 数值 | | :--- | :--- | | 第 6 轮新增 TC(test-design.md) | **39**(TC-0834~0870 + TC-0871/0872)| | 第 6 轮新增 Test 函数 | **43**(41 初批 + 2 清理增补 TC-0871/0872)| | 第 6 轮就地重写 / 迁移 Test | **2**(TC-0048 按新契约重写、TC-0826 从被删文件迁移)| | ✅ 通过 | **45/45** | | ❌ 失败 | **0** | | 第 6 轮 QA 补齐通过率 | **100%** | | 清理删除过时 Test 函数 | **-18**(旧契约已取消,不再保留)| | 累计审计修复回归 Test 数 | **84 + 43 = 127** | | 累计审计回归通过率 | **127/127 = 100%** | | 全仓 `go test ./...` | **832/832 pass,0 FAIL** | ### 9.3 全量回归暴露的过时用例处置(2026-04-19 清理补充) > 本轮把新契约钉死后,`go test ./...` 一度产生 17 条 FAIL。定性后全部归为**历史测试写在"旧错误行为"上**, > 修复已取代旧契约;按用户指示"过时用例按新契约重写,无用的直接删除"统一处置,新契约覆盖 > 到位后全仓 `go test ./... = 0 FAIL`。 #### 9.3.1 删除/重写记录 | # | 原失败测试 | 原 TC | 关联审计条目 | 处置方式 | 新契约 TC | | :--- | :--- | :--- | :--- | :--- | :--- | | 1 | `TestDeptTree_Normal` | TC-0110 | M-2 | 删除(文件 `deptTreeLogic_test.go` 整体删除) | TC-0857 (Admin 全树) | | 2 | `TestDeptTree_Empty` | TC-0111 | M-2 | 删除 | TC-0856 (orphan 空树) | | 3 | `TestDeptTree_OrphanBecomesRoot` | TC-0112 | M-2 | 删除(fullAccess 路径隐含覆盖) | TC-0857 | | 4 | `TestProductDetail_Success` | TC-0084 | M-2 | 删除(文件 `productDetailLogic_test.go` 整体删除) | TC-0853 | | 5 | `TestProductDetail_NotFound` | TC-0085 | M-2 | 删除 → 按新契约重写为"无差别 404" | **TC-0872** (新增) | | 6 | `TestProductDetail_NonSuperAdminAppKeyHidden` | TC-0088 | M-2 | 删除 | TC-0853 | | 7 | `TestProductDetail_SuperAdminAppKeyVisible` | TC-0089 | M-2 | 删除(保留原等价用例 TC-0854) | TC-0854 | | 8-12 | `TestProductList_*` 分页边界 5 条 | TC-0079 ~ TC-0083 | M-2 | 删除(文件 `productListLogic_test.go` 整体删除),分页边界由 `util.TestNormalizePage` 单元测试覆盖 | — | | 13 | `TestProductList_NonSuperAdminAppKeyHidden` | TC-0086 | M-2 | 删除 | TC-0850 | | 14 | `TestProductList_SuperAdminAppKeyVisible` | TC-0087 | M-2 | 删除 → 按新契约重写为"超管走 FindList + AppKey 可见" | **TC-0871** (新增) | | 15 | `TestSyncPerms_Mock_TransactionRollbackOnBatchUpdateFail` | TC-0048 | H-3 | 按新契约重写:`LockByCodeTx → FindMapByProductCodeWithTx → BatchUpdate 报错 → 统一 500` | TC-0048 (in-place rewrite) | | 16 | `TestExecuteSyncPerms_DuplicateEntry_Maps409` | TC-0824 | H-3 | 删除:H-3 的 FOR UPDATE 已消除 1062 可达性,409 映射契约取消 | — | | 17 | `TestSyncPermsLogic_ConflictMapsTo409HTTP` | TC-0825 | H-3 | 删除(同上) | — | | 18 | `TestExecuteSyncPerms_DeduplicatesRequest` | TC-0826 | H-3 | 保留(入参级去重仍是现行契约),移入 `syncPermsDedup_audit_test.go` | TC-0826 | | 19 | `TestRateLimit_BehindProxy_XFFStillIgnored` | TC-0554 | M-6 | 删除:M-6 明确反转契约 | TC-0862 ~ TC-0866 | | 20 | `TestSysUserModel_IncrementTokenVersionIfMatch_UserNotFound` | TC-0804 | M-8 | 删除:M-8 新契约"CAS 未命中 = ErrTokenVersionMismatch"不再区分用户不存在 | — | #### 9.3.2 清理后的体量变化 | 指标 | 清理前 | 清理后 | 变化 | | :--- | ---: | ---: | ---: | | 顶层 Test 函数数 | 848 | **832** | -16 (删除 19 条、新增 2 条、就地重写 1 条) | | 过时用例 FAIL | 17 | **0** | -17 | | 全仓 `go test ./...` | 17 FAIL | **0 FAIL** | ✅ | | 第 6 轮新增/重写 TC | 37 | **37 + TC-0871 + TC-0872 = 39** | +2 | ### 9.4 审计条目逐项结论(清理后) | 审计条目 | QA 回归结论 | 关联 TC | | :--- | :--- | :--- | | **H-1** `admin::` 双维限流 | ✅ 修复可回归:同 IP 同 username 打满后 429;换 IP 立即解锁;缺失 IP 退化到 `unknown` 桶;managementKey 错不消耗配额 | TC-0834 ~ TC-0837 | | **H-2** ValidateProductLogin 常时 + 延迟披露 | ✅ 修复可回归:冻结/超管在错误密码下一律 401 "用户名或密码错误";正确密码才披露 403 | TC-0838 ~ TC-0842 | | **H-3** SyncPerms 事务内锁 + 事务内读 | ✅ 锁顺序 + 404 分支被钉死;1062→409 映射契约随 FOR UPDATE 串行化一并取消,任何 tx 错误统一 500 不透传驱动细节 | TC-0843 ~ TC-0845、TC-0048、TC-0826 | | **M-1** CleanByUserIds 批量清理 + 错误不吞 | ✅ 修复可回归:6 cacheKey + 3 userIdx 一次清光;空 ids no-op;deptType 变更时 FindIdsByDeptId 恰 1 次;失败降级 200 | TC-0846 ~ TC-0849 | | **M-2** ProductList / ProductDetail / DeptTree 访问控制 | ✅ 修复可回归:非超管/非 ADMIN 按 productCode 或 DeptPath 剪枝;404 代替 403 防枚举;AppKey 脱敏保留 | TC-0850 ~ TC-0857 + TC-0871 / TC-0872 | | **M-4** post-commit 缓存失败 degraded 成功 | ✅ 修复可回归:`FindUserIdsByRoleId` 抛 err,handler 仍 200 | TC-0858 / TC-0859 | | **M-5** `WithPrefetchedTarget` | ✅ 修复可回归:prefetched.Id==targetUserId 时 FindOne=0 次;Id 不匹配时自动忽略并回退 FindOne | TC-0860 / TC-0861 | | **M-6** ExtractClientIP XFF 支持 | ✅ 修复可回归:XFF 首段优先 + IP 合法性校验 + trim + behindProxy=false 忽略头 | TC-0862 ~ TC-0866 | | **M-8** IncrementTokenVersionIfMatch 新签名 | ✅ 契约确认:任何 CAS 未命中(含用户不存在)统一返 `ErrTokenVersionMismatch`;用户状态分支由上游 `UserDetailsLoader.Load` 的 status 字段分流 | TC-0802 / TC-0803 / TC-0805 / TC-0806 | | **L-5** CountOtherActiveAdminsTx | ✅ 修复可回归:排除自己计数;尊重 productCode 边界;Disabled/Member 不计入 | TC-0867 ~ TC-0870 | ### 9.5 下一轮建议 1. **保持 CI gate**:`go test -count=1 ./...` 必须 0 FAIL 再合入;过时用例一旦被新契约取代,立即按"按新契约重写或直接删除"处置,避免持续污染 CI。 2. **M-8 的 UX 观测**:虽然"用户不存在 = ErrTokenVersionMismatch"在新契约下是可接受的(登录凭证维度不泄露用户存在性),但产品层仍建议在 `UserDetailsLoader.Load` 侧补一条 audit-log"refresh token 命中已删除用户 id=N"供排障。 3. **H-3 的 1062 监控**:LockByCodeTx 串行化后 1062 理论不可达;建议在 DB 层加一条 `mysql_error_1062{table="sys_perm"}` 的 metric + 告警,一旦出现代表 H-3 失效,需要补回 409 重试契约。 4. **Chaos 级 Redis/DB 故障注入**:本轮 fail-open 只覆盖"Redis 整个不可达"的全断场景;后续可补 Redis 超时/抖动、MySQL 只读副本迟滞下 `FOR UPDATE` 行为的混沌测试。