test-design.md 157 KB

权限管理系统 (perms-system-server) — 全路径覆盖测试设计

测试范围: API (go-zero REST, 全 POST) + gRPC (status codes) + Model 层 (_gen.go 模板生成 + 自定义方法) + Logic 单元测试 + util 层 + 访问控制 + UserDetailsLoader 测试报告与代码审计详见 test-report.md


一、系统架构与逻辑链路

1.1 整体调用链路

HTTP Client                                  gRPC Client
     │                                            │
     ▼                                            ▼
rest.Server (go-zero)                    zrpc.Server (go-zero)
     │ (全部 POST 路由)                            │
     ▼                                            ▼
Handler 层 (JSON Body 解析)              PermServer (permserver.go)
     │                                   (status.Error + codes.Xxx)
     ▼                                            │
JwtAuth Middleware (鉴权/上下文注入)                │
     │                                            │
     ▼                                            ▼
Logic 层 (业务逻辑)  ◄────── 共享 ────►  authHelper (jwt.go / perms.go)
     │                                            │
     ▼                                            ▼
util 层 (NormalizePage / IsValidEmail / IsValidPhone)
     │
     ▼
Model 层 (go-zero sqlc + cache + TransactCtx + 批量查询)
     │  ├── _gen.go (自定义模板生成: CRUD/Batch/WithTx/缓存管理)
     │  └── 自定义方法 (分页/按条件查询/级联删除等)
     ▼
MySQL (InnoDB) + Redis Cache

1.2 Model 层接口全景

共 9 个 Model,每个包含:

层级 方法类别 数量/模型 来源
_gen.go 基础 CRUD Insert, InsertWithTx, FindOne, FindOneWithTx, Update, UpdateWithTx, Delete, DeleteWithTx 8 自定义模板
_gen.go 批量操作 BatchInsert, BatchInsertWithTx, BatchUpdate, BatchUpdateWithTx, BatchDelete, BatchDeleteWithTx 6 自定义模板
_gen.go 唯一索引查询 FindOneBy{UniqueField}, FindOneBy{UniqueField}WithTx (因表而异) 0~2 组 自定义模板
_gen.go 内部辅助 TransactCtx, TableName, findListByPrimaryKeys, getPrimaryKeyValue, buildBatchUpdateQuery, formatPrimary, queryPrimary 7 自定义模板
自定义方法 分页查询/按条件查询/级联删除/批量ID查询等 3~7 手写

1.3 权限计算逻辑链路

输入: userId + deptId + productCode + isSuperAdmin(bool)
    │
    ├─ isSuperAdmin=true → 产品全部启用权限 + "SUPER_ADMIN"
    ├─ 非产品成员 → nil + ""
    ├─ DEVELOPER/ADMIN → 产品全部启用权限 + memberType
    ├─ MEMBER + deptId>0 + dept.DeptType="DEV" → 产品全部启用权限 + "MEMBER"
    ├─ MEMBER + deptId>0 + dept查询失败/DeptType≠"DEV" → 继续走角色权限流程
    └─ MEMBER → (角色权限 ∪ ALLOW) - DENY → 过滤 status=1

二、REST API 测试用例

注意: 所有路由统一为 POST 方法,请求参数均通过 JSON Body 传递。

2.1 产品端登录 POST /api/auth/login

TC编号 接口/方法 测试场景 输入参数 (JSON) 预期结果 测试类型 优先级 覆盖说明
TC-0001 POST /api/auth/login 正常登录(普通用户+productCode) {"username":"user1","password":"123456","productCode":"test"} code=0, accessToken/refreshToken/userInfo 正常路径 P0 loginLogic全路径
TC-0002 POST /api/auth/login 正常登录-带productCode+ADMIN成员 {"username":"user1","password":"123456","productCode":"test"} code=0, perms含用户可用权限, memberType="ADMIN" 正常路径 P0 GetUserPerms(false) MEMBER分支
TC-0003 POST /api/auth/login 超管通过产品端登录被拒绝 {"username":"super","password":"x","productCode":"p1"} code=403, "超级管理员不允许通过产品端登录,请使用管理后台" 安全 P0 IsSuperAdmin==1 → ErrForbidden
TC-0004 POST /api/auth/login 超管无productCode被拒绝 {"username":"super","password":"x"} code=403, "超级管理员不允许通过产品端登录,请使用管理后台" 安全 P0 IsSuperAdmin==1 → ErrForbidden
TC-0005 POST /api/auth/login 用户不存在 {"username":"notexist","password":"x"} code=401, "用户名或密码错误" 异常路径 P0 ErrNotFound分支
TC-0006 POST /api/auth/login DB异常(非ErrNotFound) FindOneByUsername连接失败 code=500, "服务器内部错误" 异常路径 P1 透传err→Setup兜底
TC-0007 POST /api/auth/login 密码错误 {"username":"admin","password":"wrong"} code=401 异常路径 P0 bcrypt比对失败
TC-0008 POST /api/auth/login 账号冻结 status=2用户 code=403, "账号已被冻结" 分支覆盖 P0 u.Status!=1
TC-0009 POST /api/auth/login 非产品成员 productCode指向用户不属于的产品 code=403, "您不是该产品的成员" 安全 P0 非成员禁止登录
TC-0010 POST /api/auth/login DEVELOPER成员 DEVELOPER类型成员 perms全量, memberType="DEVELOPER" 分支覆盖 P1 perms.go DEVELOPER分支
TC-0011 POST /api/auth/login SQL注入 {"username":"' OR 1=1 --","password":"x"} code=401 安全 P0 参数化查询
TC-0012 POST /api/auth/login 缺少必填字段 {} HTTP 400 边界 P1 httpx.Parse校验(productCode现为必填)
TC-0013 POST /api/auth/login 产品成员被禁用时拒绝登录 member.status=Disabled 403 "您的产品成员资格已被禁用" 安全 P0 H-3: loginService
TC-0014 POST /api/auth/login 产品被禁用时拒绝登录 product.status=Disabled 403 "该产品已被禁用" 安全 P0 H-3: loginService

2.1b 管理后台登录 POST /api/auth/adminLogin

TC编号 接口/方法 测试场景 输入参数 (JSON) 预期结果 测试类型 优先级 覆盖说明
TC-0015 POST /api/auth/adminLogin 超管正常登录 {"username":"super","password":"x","managementKey":"valid"} code=0, accessToken/refreshToken/userInfo, isSuperAdmin=1, memberType="SUPER_ADMIN", perms为空 正常路径 P0 adminLoginLogic全路径
TC-0016 POST /api/auth/adminLogin 普通用户被拒绝 {"username":"user1","password":"x","managementKey":"valid"} code=401, "用户名或密码错误" 安全 P0 审计M-7修复: 统一错误消息防用户枚举
TC-0017 POST /api/auth/adminLogin managementKey无效 {"username":"user1","password":"x","managementKey":"wrong"} code=401, "managementKey无效" 安全 P0 第一个校验点
TC-0018 POST /api/auth/adminLogin managementKey为空 {"username":"user1","password":"x","managementKey":""} code=401, "managementKey无效" 安全 P0 空字符串≠config值
TC-0019 POST /api/auth/adminLogin 用户不存在 {"username":"notexist","password":"x","managementKey":"valid"} code=401, "用户名或密码错误" 异常路径 P0 ErrNotFound分支
TC-0020 POST /api/auth/adminLogin 密码错误 {"username":"user1","password":"wrong","managementKey":"valid"} code=401, "用户名或密码错误" 异常路径 P0 bcrypt比对失败
TC-0021 POST /api/auth/adminLogin 账号冻结 status=2用户 code=401, "用户名或密码错误" 安全 P0 审计M-7修复: 冻结/非超管统一返回同一错误防枚举
TC-0022 POST /api/auth/adminLogin 不带productCode时perms为空 管理后台登录超管 userInfo.perms为空, memberType="SUPER_ADMIN"(超管标记由Loader自动填充) 功能验证 P0 Load(ctx, uid, "")
TC-0023 POST /api/auth/adminLogin 缺少必填字段 {} HTTP 400 边界 P1 httpx.Parse校验
TC-0024 POST /api/auth/adminLogin SQL注入username {"username":"' OR 1=1 --","password":"x","managementKey":"valid"} code=401 安全 P0 参数化查询
TC-0025 POST /api/auth/adminLogin adminLogin 用户名限流 对同一用户名连续多次失败登录 触发后返回 429 "请求过于频繁" 安全 P0 H-2: 防用户名枚举爆破

2.2 刷新Token POST /api/auth/refreshToken

TC编号 接口/方法 测试场景 输入参数 (JSON) 预期结果 测试类型 优先级 覆盖说明
TC-0026 POST /api/auth/refreshToken 正常刷新 Header Authorization: Bearer <refreshToken> code=0, 新accessToken, 新refreshToken(token轮转,保留原始过期时间) 正常路径 P0 refreshTokenLogic全路径
TC-0027 POST /api/auth/refreshToken 不带productCode(回退) Header Authorization, 无productCode 使用claims.ProductCode 分支覆盖 P1 productCode=""回退
TC-0028 POST /api/auth/refreshToken token无效 Header Authorization: Bearer invalid code=401 异常路径 P0 ParseRefreshToken失败
TC-0029 POST /api/auth/refreshToken 用户已删除 token中userId不存在 code=403, "账号已被冻结" 异常路径 P1 UserDetailsLoader返回Status=0
TC-0030 POST /api/auth/refreshToken 账号冻结 冻结用户 code=403 分支覆盖 P0 Status!=1
TC-0031 POST /api/auth/refreshToken 超管+productCode(token中已含相同pc) isSuperAdmin=1, token中productCode=pc, req.ProductCode=pc refreshToken原样返回, SUPER_ADMIN权限 分支覆盖 P1 isSuperAdmin分支+productCode不变
TC-0032 POST /api/auth/refreshToken 尝试切换产品被拒绝 token中productCode="p1", req.ProductCode="p2" code=400, "刷新令牌不允许切换产品" 安全 P0 H-02修复: 禁止跨产品切换
TC-0033 POST /api/auth/refreshToken TokenVersion不匹配时拒绝刷新 refreshToken含tokenVersion=999, DB中tokenVersion=0 401 "登录状态已失效,请重新登录" 安全 P0 claims.TokenVersion != ud.TokenVersion
TC-0034 POST /api/auth/refreshToken 使用accessToken作为refreshToken被拒绝 用accessSecret签发的accessToken作为refreshToken传入 401 "refreshToken无效或已过期" 安全 P0 ParseRefreshToken校验TokenType!=refresh
TC-0035 POST /api/auth/refreshToken 产品成员已移除时拒绝刷新 refreshToken含productCode, 但用户已从该产品移除 403 "您已不是该产品的成员" 安全 P0 ud.MemberType=="" && !ud.IsSuperAdmin

2.3 同步权限 POST /api/perm/sync

TC编号 接口/方法 测试场景 输入参数 (JSON) 预期结果 测试类型 优先级 覆盖说明
TC-0036 POST /api/perm/sync 全部新增 {"appKey":"ak","appSecret":"as","perms":[{"code":"x","name":"y"}]} code=0, added=1, updated=0, disabled=0 正常路径 P0 toInsert→BatchInsert
TC-0037 POST /api/perm/sync 更新已有(名称变更) 已存在code但name不同 updated=1 正常路径 P0 toUpdate→BatchUpdate
TC-0038 POST /api/perm/sync 无变化 已存在且name/remark/status均相同 added=0, updated=0 分支覆盖 P1 跳过更新
TC-0039 POST /api/perm/sync 禁用权限重启 已status=2的权限在列表中 updated=1, status恢复1 分支覆盖 P1 Status!=1条件
TC-0040 POST /api/perm/sync 移除不在列表的权限 DB有多余权限 disabled>0 正常路径 P0 DisableNotInCodes
TC-0041 POST /api/perm/sync 空perms数组被拒绝 {"...","perms":[]} code=400, "权限列表不能为空" 输入校验 P0 空列表校验,防止意外批量禁用
TC-0042 POST /api/perm/sync 验证disabled返回值 已知DB有5条,perms仅含2条 disabled=3 功能验证 P0 RowsAffected()
TC-0043 POST /api/perm/sync appKey无效 {"appKey":"invalid"} code=401 异常路径 P0 FindOneByAppKey失败
TC-0044 POST /api/perm/sync appSecret错误 secret不匹配 code=401 异常路径 P0 AppSecret比对
TC-0045 POST /api/perm/sync 产品已禁用 product.Status!=1 code=403 分支覆盖 P0 Status!=1
TC-0046 POST /api/perm/sync 大批量(1000条) 1000条perms added=1000 性能 P2 BatchInsert性能
TC-0047 POST /api/perm/sync 重复code去重 perms中包含两个相同code 仅处理一次, added=1(而非2) 分支覆盖 P0 M-09修复: seen去重
TC-0048 POST /api/perm/sync 事务保护-中途失败回滚 模拟BatchUpdate失败 全部操作回滚, 返回SyncPermsError(500,"同步权限事务失败"), 不透传DB错误 事务验证 P0 H-05/H-03 修复:LockByCodeTx→FindMapByProductCodeWithTx→BatchInsert→BatchUpdate 任一失败统一 500

2.4 获取用户信息 POST /api/auth/userInfo

TC编号 接口/方法 测试场景 输入参数 (JSON) 预期结果 测试类型 优先级 覆盖说明
TC-0049 POST /api/auth/userInfo 正常获取-含productCode Bearer token (含productCode) code=0, 完整UserInfo+实时perms 正常路径 P0 userInfoLogic全路径
TC-0050 POST /api/auth/userInfo 不含productCode Bearer token (无productCode) perms为空 分支覆盖 P1 productCode=""
TC-0051 POST /api/auth/userInfo 未登录 无Authorization头 code=401, "未登录" 异常路径 P0 middleware拦截
TC-0052 POST /api/auth/userInfo token过期 过期token code=401 异常路径 P0 middleware
TC-0053 POST /api/auth/userInfo userId=0 伪造claims code=401, "未登录" 分支覆盖 P1 userId==0

2.5 修改密码 POST /api/auth/changePassword

TC编号 接口/方法 测试场景 输入参数 (JSON) 预期结果 测试类型 优先级 覆盖说明
TC-0054 POST /api/auth/changePassword 正常修改 {"oldPassword":"123456","newPassword":"654321"} code=0 正常路径 P0 changePasswordLogic全路径
TC-0055 POST /api/auth/changePassword mustChangePassword重置 正常修改后 DB中mustChangePassword=2 功能验证 P0 user.MustChangePassword=2
TC-0056 POST /api/auth/changePassword 原密码错误 {"oldPassword":"wrong","newPassword":"newpwd"} code=400, "原密码错误" 异常路径 P0 bcrypt失败
TC-0057 POST /api/auth/changePassword 新密码少于8字符 {"oldPassword":"old","newPassword":"Pas1234"} code=400, "密码长度不能少于8个字符" 输入校验 P0 len<8
TC-0058 POST /api/auth/changePassword 新密码恰好8字符(含大小写+数字) {"oldPassword":"old","newPassword":"Abcdef1x"} code=0 边界 P1 len==8,含大小写+数字
TC-0059 POST /api/auth/changePassword 新密码空字符串 {"oldPassword":"old","newPassword":""} code=400 边界 P0 len("")=0<8
TC-0060 POST /api/auth/changePassword 新密码超过72字符 {"oldPassword":"old","newPassword":"a*73"} code=400, "密码长度不能超过72个字符" 输入校验 P0 len>72
TC-0061 POST /api/auth/changePassword 新密码恰好72字符 {"oldPassword":"old","newPassword":"a*72"} code=0 边界 P1 len==72
TC-0062 POST /api/auth/changePassword 新旧密码相同 {"oldPassword":"123456","newPassword":"123456"} code=400, "新密码不能与原密码相同" 输入校验 P0 OldPassword==NewPassword
TC-0063 POST /api/auth/changePassword 用户不存在 token中userId已删除 code=404 异常路径 P1 FindOne失败

2.6 创建产品 POST /api/product/create

TC编号 接口/方法 测试场景 输入参数 (JSON) 预期结果 测试类型 优先级 覆盖说明
TC-0064 POST /api/product/create 正常创建 {"code":"new","name":"新产品"} code=0, id/appKey/appSecret/adminUser/adminPassword 正常路径 P0 TransactCtx全路径
TC-0065 POST /api/product/create 事务回滚-用户创建失败 模拟InsertWithTx User失败 返回错误, DB无新产品 事务验证 P0 TransactCtx回滚
TC-0066 POST /api/product/create 事务回滚-成员创建失败 模拟InsertWithTx Member失败 产品和用户均回滚 事务验证 P0 TransactCtx回滚
TC-0067 POST /api/product/create 编码已存在 {"code":"existing","name":"x"} code=409 异常路径 P0 FindOneByCode成功
TC-0068 POST /api/product/create 并发创建同编码 两请求同时 一成功一冲突 并发 P1 uk_code
TC-0069 POST /api/product/create createProduct 含空格被拒绝 code="abc def" 400 "产品编码格式不合法" 输入校验 P0 productCodeRegexp
TC-0070 POST /api/product/create createProduct 含特殊字符被拒绝 code="abc@def" 400 输入校验 P0 productCodeRegexp
TC-0071 POST /api/product/create createProduct 全中文被拒绝 code="产品一" 400 输入校验 P0 productCodeRegexp
TC-0072 POST /api/product/create createProduct 纯数字开头被拒绝 code="1abc" 400 输入校验 P0 productCodeRegexp 首字符限定
TC-0073 POST /api/product/create createProduct 空字符串被拒绝 code="" 400 边界 P0
TC-0074 POST /api/product/create createProduct 长度>64 被拒绝 code="a"*65 400 "产品编码长度不能超过64个字符" 边界 P0 len>64
TC-0075 POST /api/product/create createProduct 合法编码(含下划线/中划线/数字) code="pc_01-test" 创建成功 正常路径 P0 Regexp 正向匹配

2.7 产品更新/列表/详情

TC编号 接口/方法 测试场景 输入参数 (JSON) 预期结果 测试类型 优先级 覆盖说明
TC-0076 POST /api/product/update 正常更新 {"id":1,"name":"新名","status":1} code=0 正常路径 P0 updateProductLogic
TC-0077 POST /api/product/update 不存在 {"id":9999,"name":"x"} code=404 异常路径 P0 FindOne失败
TC-0078 POST /api/product/update 不传status {"id":1,"name":"x"} status不变 分支覆盖 P1 Status>0
TC-0079~0083 POST /api/product/list 分页边界 正常/默认/超限/0/负值 已删除:分页边界由 util.TestNormalizePage 单元测试覆盖;列表语义被 M-2 拆分为"超管走 FindList / 非超管只看自己"两条独立契约(TC-0850、TC-0871)
TC-0084~0085 POST /api/product/detail 正常/不存在 已删除:被 M-2 契约合并改写为 TC-0852(他人产品 → 404)、TC-0853(自己产品 AppKey 脱敏)、TC-0872(FindOne 错误 → 404 无差别响应)
TC-0086 / TC-0088 非超管 AppKey 隐藏 已删除:由 TC-0850(list)+ TC-0853(detail)覆盖
TC-0087 / TC-0089 超管可见 AppKey 已删除:由 TC-0854(detail)+ TC-0871(list)覆盖
TC-0090 POST /api/product/update updateProduct 非法状态值被拒绝 status=99 400 "产品状态值无效" 输入校验 P0 H-4: 仅允许 1/2

2.8 创建部门 POST /api/dept/create

TC编号 接口/方法 测试场景 输入参数 (JSON) 预期结果 测试类型 优先级 覆盖说明
TC-0091 POST /api/dept/create 创建顶级部门 {"parentId":0,"name":"总部"} code=0, path="/{id}/" 正常路径 P0 TransactCtx, parentPath="/"
TC-0092 POST /api/dept/create 创建子部门 {"parentId":1,"name":"技术部"} code=0, path=parent.path+id+"/" 正常路径 P0 parentId>0分支
TC-0093 POST /api/dept/create 父部门不存在 {"parentId":9999,"name":"x"} code=404, "父部门不存在" 异常路径 P0 FindOneWithTx失败
TC-0094 POST /api/dept/create 不传DeptType默认NORMAL {"parentId":0,"name":"x"} DB deptType="NORMAL" 分支覆盖 P0 deptType=""→DeptTypeNormal
TC-0095 POST /api/dept/create 传DeptType=DEV {"parentId":0,"name":"x","deptType":"DEV"} DB deptType="DEV" 正常路径 P0 req.DeptType赋值
TC-0096 POST /api/dept/create 事务内FindOneWithTx可见性 TransactCtx内InsertWithTx后FindOneWithTx 事务内可读到未提交数据 事务验证 P0 FindOneWithTx(session)
TC-0097 POST /api/dept/create 事务回滚-Insert失败 模拟InsertWithTx失败 DB无新记录 事务验证 P0 TransactCtx回滚
TC-0098 POST /api/dept/create 事务回滚-UpdateWithTx失败 模拟UpdateWithTx失败 Insert也回滚 事务验证 P1 TransactCtx回滚
TC-0099 POST /api/dept/create 多层嵌套(5层) 递归创建5层 path正确拼接 深度测试 P2 path逻辑
TC-0100 POST /api/dept/create 通过Logic创建+验证Path CreateDeptLogic.CreateDept→FindOne path包含/{id}/ 集成验证 P0 FindOneWithTx修复后端到端

2.9 部门更新/删除/树

TC编号 接口/方法 测试场景 输入参数 (JSON) 预期结果 测试类型 优先级 覆盖说明
TC-0101 POST /api/dept/update 正常更新 {"id":1,"name":"新名","sort":5} code=0 正常路径 P0 updateDeptLogic
TC-0102 POST /api/dept/update 不存在 {"id":9999,"name":"x"} code=404 异常路径 P0 FindOne失败
TC-0103 POST /api/dept/update DeptType NORMAL→DEV {"id":1,"deptType":"DEV"} DB deptType="DEV" 正常路径 P0 DeptType合法值更新
TC-0104 POST /api/dept/update DeptType无效值返回错误 {"id":1,"deptType":"INVALID"} code=400, "部门类型无效", DB deptType不变 输入校验 P0 DeptType校验,仅NORMAL/DEV
TC-0105 POST /api/dept/update DeptType变更时级联清除子部门用户缓存 部门从NORMAL改为DEV,有子部门含用户 code=0, 子部门下用户缓存被清除 缓存验证 P0 M-10修复: 级联缓存失效
TC-0106 POST /api/dept/delete 正常删除(无子部门) {"id":5} code=0 正常路径 P0 deleteDeptLogic
TC-0107 POST /api/dept/delete 有子部门 {"id":1} code=400, "存在子部门" 业务约束 P0 len(children)>0
TC-0108 POST /api/dept/delete 不存在的部门 {"id":9999} code=0(Delete对不存在行不报错) 边界 P1 FindByParentId空+Delete
TC-0109 POST /api/dept/delete 部门下有关联用户 部门id指向含用户的部门 code=400, "该部门下仍有关联用户,无法删除" 业务约束 P0 H-07修复: 检查关联用户
TC-0110~0112 POST /api/dept/tree 正常/空/孤儿 已删除:M-2 后 DeptTree 按 caller 身份剪枝,旧测试假定任何身份可拿全树已不成立;新契约由 TC-0855/0856/0857(deptTreeAccessControl_audit_test.go)覆盖,"孤儿→根"行为隐含在 fullAccess 路径中

2.10 权限列表 POST /api/perm/list

TC编号 接口/方法 测试场景 输入参数 (JSON) 预期结果 测试类型 优先级 覆盖说明
TC-0113 POST /api/perm/list 正常查询 {"productCode":"p1","page":1,"pageSize":10} code=0, total/list 正常路径 P0 permListLogic
TC-0114 POST /api/perm/list 默认分页 {"productCode":"p1"} page=1, pageSize=20 分支覆盖 P1 NormalizePage
TC-0115 POST /api/perm/list pageSize超过上限 {"productCode":"p1","pageSize":200} 实际pageSize=100 边界 P0 NormalizePage cap
TC-0116 POST /api/perm/list 不存在的productCode {"productCode":"notexist"} total=0, list=[] 边界 P1 空结果

2.11 角色管理

TC编号 接口/方法 测试场景 输入参数 (JSON) 预期结果 测试类型 优先级 覆盖说明
TC-0117 POST /api/role/create 正常创建 {"productCode":"p1","name":"管理员","permsLevel":1} code=0, id>0 正常路径 P0 createRoleLogic
TC-0118 POST /api/role/create 重复角色名 同产品同名 code=409, "该产品下角色名已存在" 业务约束 P0 Duplicate entry→ErrConflict
TC-0119 POST /api/role/create 并发同名创建 两请求同时 一成功一冲突409 并发 P1 唯一索引+1062捕获
TC-0120 POST /api/role/update 正常更新 {"id":1,"name":"新名","permsLevel":2} code=0 正常路径 P0 updateRoleLogic
TC-0121 POST /api/role/update 不存在 {"id":9999,...} code=404 异常路径 P0 FindOne失败
TC-0122 POST /api/role/list 正常查询 {"productCode":"p1","page":1,"pageSize":10} code=0 正常路径 P0 roleListLogic
TC-0123 POST /api/role/list pageSize超过上限 {"productCode":"p1","pageSize":200} 实际pageSize=100 边界 P0 NormalizePage cap
TC-0124 POST /api/role/detail 正常查询 {"id":1} code=0, 含permIds 正常路径 P0 roleDetailLogic
TC-0125 POST /api/role/detail 不存在 {"id":9999} code=404 异常路径 P0 FindOne失败

2.12 删除角色 POST /api/role/delete

TC编号 接口/方法 测试场景 输入参数 (JSON) 预期结果 测试类型 优先级 覆盖说明
TC-0126 POST /api/role/delete 正常删除+级联 {"id":5} (含权限/用户绑定) code=0, role_perm/user_role同步清理 正常+事务 P0 TransactCtx全路径
TC-0127 POST /api/role/delete 事务回滚 模拟DeleteWithTx失败 级联删除回滚 事务验证 P0 TransactCtx
TC-0128 POST /api/role/delete 无关联数据 新角色无绑定 code=0 分支覆盖 P1 删0条

2.13 绑定角色权限 POST /api/role/bindPerms

TC编号 接口/方法 测试场景 输入参数 (JSON) 预期结果 测试类型 优先级 覆盖说明
TC-0129 POST /api/role/bindPerms 正常绑定 {"roleId":1,"permIds":[1,2,3]} code=0 正常路径 P0 TransactCtx
TC-0130 POST /api/role/bindPerms 角色不存在 {"roleId":9999,"permIds":[1]} code=404, "角色不存在" 存在性校验 P0 FindOne预检
TC-0131 POST /api/role/bindPerms 清空权限 {"roleId":1,"permIds":[]} code=0, 全清空 分支覆盖 P1 len==0→return
TC-0132 POST /api/role/bindPerms 重复permId {"roleId":1,"permIds":[1,1]} DB唯一索引→事务回滚 边界 P1 uk_role_perm
TC-0133 POST /api/role/bindPerms 事务回滚 模拟BatchInsertWithTx失败 旧数据回滚还原 事务验证 P0 TransactCtx

2.14 创建用户 POST /api/user/create

TC编号 接口/方法 测试场景 输入参数 (JSON) 预期结果 测试类型 优先级 覆盖说明
TC-0134 POST /api/user/create 正常创建 {"username":"new","password":"123456"} code=0, id>0 正常路径 P0 createUserLogic
TC-0135 POST /api/user/create 用户名已存在(预检) {"username":"existing","password":"x"} code=409, "用户名已存在" 异常路径 P0 FindOneByUsername成功
TC-0136 POST /api/user/create 带完整可选字段 含nickname/email/phone/remark/deptId code=0 正常路径 P1 各字段赋值
TC-0137 POST /api/user/create 非法email格式 {"...","email":"not-an-email"} code=400, "邮箱格式不正确" 输入校验 P0 util.IsValidEmail
TC-0138 POST /api/user/create 合法email {"...","email":"[email protected]"} code=0 正常路径 P1 IsValidEmail通过
TC-0139 POST /api/user/create email为空(可选) {"...","email":""} code=0, 跳过校验 分支覆盖 P1 email!=""判断
TC-0140 POST /api/user/create 非法phone格式 {"...","phone":"abc"} code=400, "手机号格式不正确" 输入校验 P0 util.IsValidPhone
TC-0141 POST /api/user/create 合法phone(国际) {"...","phone":"+8613800138000"} code=0 正常路径 P1 IsValidPhone通过
TC-0142 POST /api/user/create phone为空(可选) {"...","phone":""} code=0, 跳过校验 分支覆盖 P1 phone!=""判断
TC-0143 POST /api/user/create 并发同username(TOCTOU) 两请求同时 一成功一冲突(1062) 并发 P0 Duplicate entry→ErrConflict
TC-0144 POST /api/user/create 唯一索引冲突消息 预检通过后DB冲突 code=409, "用户名已存在" 异常路径 P0 strings.Contains "1062"
TC-0145 POST /api/user/create 密码少于8字符 {"username":"x","password":"Pas1234"} code=400, "密码长度不能少于8个字符" 输入校验 P0 H-10修复: 密码强度校验(8+字符,含大小写+数字)
TC-0146 POST /api/user/create 密码缺少大写字母 {"username":"x","password":"pass123456"} code=400, "密码必须包含大写字母、小写字母和数字" 输入校验 P0 密码复杂性: 无大写
TC-0147 POST /api/user/create 密码缺少小写字母 {"username":"x","password":"PASS123456"} code=400, "密码必须包含大写字母、小写字母和数字" 输入校验 P0 密码复杂性: 无小写
TC-0148 POST /api/user/create 密码缺少数字 {"username":"x","password":"Passpasspass"} code=400, "密码必须包含大写字母、小写字母和数字" 输入校验 P0 密码复杂性: 无数字
TC-0149 POST /api/user/create 密码超过72字符 {"username":"x","password":"a*73"} code=400, "密码长度不能超过72个字符" 输入校验 P0 H-10修复: 密码强度校验
TC-0150 POST /api/user/create 用户名含特殊字符被拒绝 {"username":"user@name!","password":"pass123456"} 400 "用户名只能包含字母、数字和下划线,长度2-64个字符" 输入校验 P0 usernameRegexp不匹配
TC-0151 POST /api/user/create 用户名太短(1字符)被拒绝 {"username":"a","password":"pass123456"} 400 "用户名只能包含字母、数字和下划线,长度2-64个字符" 边界值 P0 最小长度2
TC-0152 POST /api/user/create 用户名太长(65字符)被拒绝 {"username":"a*65","password":"pass123456"} 400 "用户名只能包含字母、数字和下划线,长度2-64个字符" 边界值 P0 最大长度64
TC-0153 POST /api/user/create 部门不存在被拒绝 {"username":"x","password":"pass123456","deptId":999999999} 400 "部门不存在" 异常路径 P1 DeptId>0时校验FindOne
TC-0154 POST /api/user/create 昵称超过64字符被拒绝 {"username":"x","password":"pass123456","nickname":"n*65"} 400 "昵称长度不能超过64个字符" 边界值 P1 len(Nickname)>64
TC-0155 POST /api/user/create 备注超过255字符被拒绝 {"username":"x","password":"pass123456","remark":"r*256"} 400 "备注长度不能超过255个字符" 边界值 P1 len(Remark)>255

2.15 用户更新 POST /api/user/update (指针类型+DeptId可清零)

TC编号 接口/方法 测试场景 输入参数 (JSON) 预期结果 测试类型 优先级 覆盖说明
TC-0156 POST /api/user/update 正常更新 {"id":1,"nickname":"n","email":"[email protected]"} code=0 正常路径 P0 updateUserLogic
TC-0157 POST /api/user/update 不存在 {"id":9999} code=404 异常路径 P0 FindOne失败
TC-0158 POST /api/user/update 仅传id {"id":1} 仅updateTime变 分支覆盖 P1 所有指针nil
TC-0159 POST /api/user/update 清空nickname {"id":1,"nickname":""} DB nickname→空字符串 功能 P0 *string非nil+空值
TC-0160 POST /api/user/update 清空email {"id":1,"email":""} DB email→空字符串(跳过校验) 功能 P0 *email!=""判断
TC-0161 POST /api/user/update 清空remark {"id":1,"remark":""} DB remark清空 功能 P1 *string→""
TC-0162 POST /api/user/update 非法email格式 {"id":1,"email":"bad-email"} code=400, "邮箱格式不正确" 输入校验 P0 util.IsValidEmail
TC-0163 POST /api/user/update 非法phone格式 {"id":1,"phone":"12345"} code=400, "手机号格式不正确" 输入校验 P0 util.IsValidPhone
TC-0164 POST /api/user/update 合法phone {"id":1,"phone":"+8613800138000"} code=0 正常路径 P1 IsValidPhone通过
TC-0165 POST /api/user/update 不传email(nil) {"id":1,"nickname":"x"} email不变 分支覆盖 P1 req.Email==nil
TC-0166 POST /api/user/update DeptId设为0(取消部门) {"id":1,"deptId":0} DB deptId→0 功能 P0 *int64, *req.DeptId=0
TC-0167 POST /api/user/update DeptId设为正值 {"id":1,"deptId":5} DB deptId→5 正常路径 P0 *int64指针
TC-0168 POST /api/user/update DeptId不传(nil) {"id":1,"nickname":"x"} deptId不变 分支覆盖 P1 req.DeptId==nil
TC-0169 POST /api/user/update 超管不能冻结另一超管 caller=超管A, target=超管B, status=2 403 "不能通过此接口修改超级管理员的状态" 安全 P0 H-2: IsSuperAdmin==Yes 保护
TC-0170 POST /api/user/update updateUser-产品管理员可管理范围内用户 ctx=ADMIN, target在管理范围内 更新成功 正常路径 P0 Audit#4修复: CheckManageAccess允许产品管理员
TC-0171 POST /api/user/update updateUser-昵称超长拒绝 nickname=65字符 400 "昵称长度不能超过64个字符" 边界 P1 输入校验
TC-0172 POST /api/user/update updateUser-部门不存在 deptId=999999 400 "部门不存在" 异常路径 P1 关联对象不存在校验
TC-0173 POST /api/user/update updateUser 修改状态时递增 tokenVersion req.Status=Disabled, 原Status=Enabled 更新成功, tokenVersion+1 正常路径 P0 H-1: 状态变更强制下线
TC-0174 POST /api/user/update updateUser 仅改 profile 不递增 tokenVersion req.Nickname+Email 更新成功, tokenVersion不变 正常路径 P0 H-1: 非状态字段不影响会话
TC-0175 POST /api/user/update updateUser 乐观锁冲突 -> 409 基于过期 updateTime 更新 返回 CodeError(409, "数据已被其他操作修改...") 并发/异常 P0 H-1: ErrUpdateConflict 透传

2.16 用户列表/详情/状态 及其他用户操作

TC编号 接口/方法 测试场景 输入参数 (JSON) 预期结果 测试类型 优先级 覆盖说明
TC-0176 POST /api/user/list 含productCode {"productCode":"p1","page":1,"pageSize":10} 每用户含memberType(批量查) 正常路径 P0 FindMapByProductCodeUserIds
TC-0177 POST /api/user/list 不含productCode {"page":1} memberType全空,不调批量查 分支覆盖 P1 productCode=""
TC-0178 POST /api/user/list pageSize超过上限 {"pageSize":500} 实际pageSize=100 边界 P0 NormalizePage cap
TC-0179 POST /api/user/list 用户不在产品中 productCode指定,部分用户不是成员 memberType为空 分支覆盖 P1 memberMap无对应key
TC-0180 POST /api/user/list 批量查询DB异常 FindMapByProductCodeUserIds失败 code=500 异常路径 P1 err→透传
TC-0181 POST /api/user/detail 正常查询 {"id":1} 含roleIds 正常路径 P0 userDetailLogic
TC-0182 POST /api/user/detail 正常查询-含Avatar 有Avatar用户 avatar字段非空 分支覆盖 P1 Avatar.Valid=true
TC-0183 POST /api/user/detail 不存在 {"id":9999} code=404 异常路径 P0 FindOne失败
TC-0184 POST /api/user/bindRoles 正常绑定 {"userId":1,"roleIds":[1,2]} code=0 正常路径 P0 TransactCtx
TC-0185 POST /api/user/bindRoles 用户不存在 {"userId":9999,"roleIds":[1]} code=404, "用户不存在" 存在性校验 P0 FindOne预检
TC-0186 POST /api/user/bindRoles 清空角色 {"userId":1,"roleIds":[]} code=0 分支覆盖 P1 len==0
TC-0187 POST /api/user/bindRoles 事务回滚 模拟失败 旧数据还原 事务验证 P0 TransactCtx
TC-0188 POST /api/user/bindRoles 角色不属于当前产品 roleId属于其他产品 code=400, "角色不属于当前产品" 安全 P0 H-03修复: 校验角色归属
TC-0189 POST /api/user/bindRoles 角色已禁用 roleId状态为禁用 code=400, "角色已禁用" 安全 P0 H-03修复: 校验角色状态
TC-0190 POST /api/user/bindRoles 角色不存在 roleId不存在 code=400, "角色不存在" 安全 P0 H-03修复: 校验角色存在
TC-0191 POST /api/user/bindRoles 非产品成员绑定角色被拒绝 目标用户非当前产品成员 400 "不是当前产品的成员" 安全 P0 L-4: BindRoles
TC-0192 POST /api/user/setPerms 正常ALLOW {"userId":1,"perms":[{"permId":1,"effect":"ALLOW"}]} code=0 正常路径 P0 TransactCtx
TC-0193 POST /api/user/setPerms 用户不存在 {"userId":9999,"perms":[...]} code=404, "用户不存在" 存在性校验 P0 FindOne预检
TC-0194 POST /api/user/setPerms DENY权限 effect="DENY" code=0 正常路径 P0 effect="DENY"
TC-0195 POST /api/user/setPerms 清空权限 {"userId":1,"perms":[]} code=0 分支覆盖 P1 len==0
TC-0196 POST /api/user/setPerms 无效Effect值 effect="INVALID" code=400, "无效的权限效果" 安全 P0 H-04修复: Effect白名单
TC-0197 POST /api/user/setPerms PermId不存在 permId=99999 code=400, "权限不存在" 安全 P0 H-04修复: 校验PermId
TC-0198 POST /api/user/setPerms 权限不属于当前产品 permId属于其他产品 code=400, "权限不属于当前产品" 安全 P0 H-04修复: 校验权限归属
TC-0199 POST /api/user/setPerms 非产品成员设置权限被拒绝 目标用户非当前产品成员 400 "不是当前产品的成员" 安全 P0 L-5: SetUserPerms
TC-0200 POST /api/user/updateStatus 正常冻结 {"id":普通用户,"status":2} code=0 正常路径 P0 updateUserStatusLogic
TC-0201 POST /api/user/updateStatus 正常解冻 {"id":普通用户,"status":1} code=0 正常路径 P0 status=1
TC-0202 POST /api/user/updateStatus 非法status(0) {"id":1,"status":0} code=400, "状态值无效" 输入校验 P0 status!=1&&!=2
TC-0203 POST /api/user/updateStatus 冻结自己 id=当前登录userId code=400, "不能修改自己的状态" 自我保护 P0 callerId==req.Id
TC-0204 POST /api/user/updateStatus 冻结超管 id=超管 code=403, "不能修改超级管理员的状态" 超管保护 P0 IsSuperAdmin==1
TC-0205 POST /api/user/list userList-非超管仅可见产品成员 ctx=ADMIN(非超管), productCode指定 仅返回该产品成员, 不返回非成员 安全 P0 Audit#1修复: FindListByProductMembers数据隔离
TC-0206 POST /api/user/list userList-非超管未指定productCode被拒绝 ctx=ADMIN(非超管), productCode="" 403 "非超管用户必须指定产品编码" 安全 P0 Audit#1修复: 强制productCode
TC-0207 POST /api/user/list userList-非超管使用错误productCode被拒绝 ctx=ADMIN, productCode!=ctx.ProductCode 403 安全 P0 Audit#1修复: productCode一致性校验
TC-0208 POST /api/user/bindRoles bindRoles-permsLevel越权拒绝 ctx=ADMIN(MinPermsLevel=50), role.permsLevel=1 403 "不能分配权限级别高于自身的角色" 安全 P0 Audit#2修复: 角色权限级别越权防护
TC-0209 POST /api/user/bindRoles bindRoles-超管可分配任意级别角色 ctx=SuperAdmin, role.permsLevel=1 绑定成功 正常路径 P0 Audit#2修复: 超管无permsLevel限制
TC-0210 POST /api/user/setPerms 同一权限ID冲突Effect被拒绝 perms含[{permId:1,effect:"ALLOW"},{permId:1,effect:"DENY"}] 400 "同一权限ID不能同时为 ALLOW 和 DENY" 业务约束 P0 seen[permId]冲突检测
TC-0211 POST /api/user/setPerms 重复权限ID相同Effect去重 perms含[{permId:1,effect:"ALLOW"},{permId:1,effect:"ALLOW"}] 成功, DB仅1条记录 数据鲁棒性 P1 seen去重,uniquePerms
TC-0212 POST /api/user/setPerms 已禁用权限不能被设置 perm.Status=2(Disabled) 400 "权限 xxx 已被禁用,无法设置" 业务约束 P0 p.Status != StatusEnabled

2.17 成员管理

TC编号 接口/方法 测试场景 输入参数 (JSON) 预期结果 测试类型 优先级 覆盖说明
TC-0213 POST /api/member/add 正常添加 {"productCode":"p1","userId":1,"memberType":"MEMBER"} code=0, id>0 正常路径 P0 addMemberLogic
TC-0214 POST /api/member/add 产品不存在 {"productCode":"notexist",...} code=404, "产品不存在" 存在性校验 P0 FindOneByCode预检
TC-0215 POST /api/member/add 用户不存在 {"userId":9999,...} code=404, "用户不存在" 存在性校验 P0 FindOne预检
TC-0216 POST /api/member/add 已是成员 重复添加 code=409, "已是成员" 异常路径 P0 FindOneByProductCodeUserId成功
TC-0217 POST /api/member/add 并发添加 两请求同时 一成功一冲突 并发 P1 uk_product_user
TC-0218 POST /api/member/add 无效MemberType {"memberType":"INVALID"} code=400, "无效的成员类型" 输入校验 P0 M-06修复: MemberType白名单
TC-0219 POST /api/member/update 正常更新 {"id":1,"memberType":"ADMIN"} code=0 正常路径 P0 updateMemberLogic
TC-0220 POST /api/member/update 不存在 {"id":9999,...} code=404 异常路径 P0 FindOne失败
TC-0221 POST /api/member/update 无效MemberType {"id":1,"memberType":"INVALID"} code=400, "无效的成员类型" 输入校验 P0 M-06修复: MemberType白名单
TC-0222 POST /api/member/list 正常查询(批量查用户) {"productCode":"p1","page":1,"pageSize":10} 含username/nickname 正常路径 P0 FindByIds批量
TC-0223 POST /api/member/list 成员用户已删除 userId不存在于FindByIds结果 username/nickname为空 分支覆盖 P1 userMap无对应key
TC-0224 POST /api/member/list pageSize超过上限 {"productCode":"p1","pageSize":200} 实际pageSize=100 边界 P0 NormalizePage cap
TC-0225 POST /api/member/list 空成员列表 productCode下无成员 total=0, list=[], 不调FindByIds 分支覆盖 P1 userIds空
TC-0226 POST /api/member/remove 正常移除+级联(事务内) {"id":1} (含角色/权限) code=0, user_role+user_perm同步清理 正常+事务 P0 TransactCtx全路径
TC-0227 POST /api/member/remove 跨产品隔离 用户在多产品有角色 仅清理该产品的 深度业务 P0 ForProductTx子查询
TC-0228 POST /api/member/remove 成员不存在 {"id":9999} code=404, "成员不存在" 异常路径 P0 FindOne失败
TC-0229 POST /api/member/remove 事务回滚 模拟DeleteWithTx失败 级联删除全部回滚 事务验证 P0 TransactCtx

三、gRPC 接口测试用例

3.1 gRPC SyncPermissions

TC编号 接口/方法 测试场景 输入 预期结果 测试类型 优先级 覆盖说明
TC-0230 SyncPermissions 正常同步 valid req added/updated/disabled计数正确 正常路径 P0 permserver.go SyncPermissions
TC-0231 SyncPermissions appKey无效 invalid appKey codes.Unauthenticated 异常路径 P0 status.Error
TC-0232 SyncPermissions appSecret错误 wrong secret codes.Unauthenticated 异常路径 P0 status.Error
TC-0233 SyncPermissions 产品已禁用 disabled product codes.PermissionDenied 分支覆盖 P0 status.Error
TC-0234 SyncPermissions 验证disabled计数 DB有5条,perms含2条 disabled=3 功能验证 P0 RowsAffected

3.2 gRPC Login / RefreshToken / VerifyToken / GetUserPerms

TC编号 接口/方法 测试场景 输入 预期结果 测试类型 优先级 覆盖说明
TC-0235 Login 正常登录(普通用户+productCode) valid credentials + productCode token对+userInfo(含nickname) 正常路径 P0 permserver.go Login; resp.Nickname应返回用户昵称
TC-0236 Login 用户不存在 wrong username codes.Unauthenticated 异常路径 P0 status.Error
TC-0237 Login 密码错误 wrong password codes.Unauthenticated 异常路径 P0 status.Error
TC-0238 Login 账号冻结 frozen user codes.PermissionDenied 分支覆盖 P0 status.Error
TC-0239 Login 超管被拒绝 isSuperAdmin=1+productCode codes.PermissionDenied, "超级管理员不允许通过产品端登录" 安全 P0 IsSuperAdmin==1 → 拒绝
TC-0240 Login 普通用户+productCode 普通MEMBER+productCode perms含角色权限, memberType="MEMBER" 分支覆盖 P0 !isSuperAdmin && productCode!=""
TC-0241 Login 产品成员被禁用时拒绝登录 member.status=Disabled PermissionDenied 安全 P0 H-3: permserver Login
TC-0242 Login productCode为空 productCode="" codes.InvalidArgument, "productCode不能为空" 输入校验 P0 第一个校验点
TC-0243 RefreshToken 正常刷新 valid token 新token对 正常路径 P0 RefreshToken
TC-0244 RefreshToken token无效 invalid token codes.Unauthenticated 异常路径 P0 status.Error
TC-0245 RefreshToken 账号冻结 frozen codes.PermissionDenied 分支覆盖 P0 status.Error
TC-0246 RefreshToken productCode回退到claims req.ProductCode="", claims含productCode 使用claims.ProductCode 分支覆盖 P0 productCode==""回退
TC-0247 RefreshToken 超管+productCode isSuperAdmin=1+productCode memberType="SUPER_ADMIN", perms全量 分支覆盖 P0 isSuperAdmin && productCode!=""
TC-0248 RefreshToken 普通用户+productCode 普通MEMBER+productCode perms含角色权限 分支覆盖 P0 !isSuperAdmin && productCode!=""
TC-0249 VerifyToken 有效token valid valid=true, userId/perms/productCode正确 正常路径 P0 VerifyToken; resp.ProductCode应返回产品编码
TC-0250 VerifyToken 无效token invalid valid=false 异常路径 P0 err或!Valid
TC-0251 VerifyToken 缺少userId 伪造claims valid=false 安全 P0 !ok断言保护
TC-0252 VerifyToken 冻结用户token返回Invalid user.status=Disabled Valid=false 安全 P0 H-4: 实时查DB
TC-0253 VerifyToken 非成员token返回Invalid user非产品成员 Valid=false 安全 P0 H-4: 实时查成员状态
TC-0254 VerifyToken 返回实时MemberType和Perms DB中ADMIN+自定义权限 返回实时数据而非token中旧数据 安全 P0 H-4: 实时数据
TC-0255 GetUserPerms 用户不存在(需先通过AppKey/Secret认证) userId=9999, 合法AppKey/AppSecret codes.NotFound 异常路径 P0 status.Error; 先认证再查用户
TC-0256 GetUserPerms 超管(需先通过AppKey/Secret认证) isSuperAdmin, 合法AppKey/AppSecret perms全量, "SUPER_ADMIN" 正常路径 P0 GetUserPerms(true); AppKey认证前置
TC-0257 GetUserPerms MEMBER-DENY覆盖(需先通过AppKey/Secret认证) 角色有permA, DENY permA, 合法AppKey/AppSecret perms不含permA 深度业务 P0 denySet过滤; AppKey认证前置

四、JWT中间件 / 统一响应测试用例

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0258 正常Bearer token Authorization: Bearer {valid} 通过, ctx注入5个值 正常路径 P0 middleware全路径
TC-0259 无Authorization头 无Header code=401, "未登录" 异常 P0 authHeader==""
TC-0260 无Bearer前缀 Authorization: xxx code=401, "token格式错误" 异常 P0 TrimPrefix相等
TC-0261 token签名错误 错误secret code=401, "token无效或已过期" 异常 P0 !Valid
TC-0262 token过期 expired code=401 异常 P0 jwt过期
TC-0263 claims类型断言失败 非标准claims code=401, "token无效或类型错误" 异常 P1 !ok 防御性分支,jwt.ParseWithClaims(&Claims{}) 下不可达;TokenType 检查由 TC-0264 覆盖
TC-0264 refresh token被拒绝 用refresh token访问API code=401, "token无效或类型错误" 安全 P0 TokenType="refresh"时拒绝
TC-0265 业务错误(CodeError) 触发404等 {code:业务码, msg:业务消息} 正常 P0 errors.As成功
TC-0266 内部错误 DB异常 {code:500, msg:"服务器内部错误"} 安全 P0 logx.Errorf+兜底
TC-0267 成功(有data) 正常请求 {code:0, msg:"ok", data:{...}} 正常 P0 v!=nil
TC-0268 成功(无data) 返回nil {code:0, msg:"ok"} 正常 P0 v==nil

五、util 层测试用例

5.1 NormalizePage

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0269 正常值 page=2, pageSize=10 (2, 10) 正常路径 P0 无修正
TC-0270 page<=0 page=0, pageSize=10 (1, 10) 边界 P0 page<=0→1
TC-0271 page=-1 page=-1, pageSize=10 (1, 10) 边界 P0 page<=0→1
TC-0272 pageSize<=0 page=1, pageSize=0 (1, 20) 边界 P0 pageSize<=0→20
TC-0273 pageSize>100 page=1, pageSize=500 (1, 100) 边界-上限 P0 pageSize>100→100
TC-0274 pageSize=100 page=1, pageSize=100 (1, 100) 边界 P1 恰好不触发
TC-0275 pageSize=101 page=1, pageSize=101 (1, 100) 边界 P1 恰好触发
TC-0276 双零 page=0, pageSize=0 (1, 20) 边界 P1 两条件同时

5.2 IsValidEmail

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0277 正常邮箱 [email protected] true 正常路径 P0 标准格式
TC-0278 含点号 [email protected] true 正常路径 P1 允许点号
TC-0279 含加号 [email protected] true 正常路径 P1 允许加号
TC-0280 缺少@ userexample.com false 异常路径 P0 无@
TC-0281 缺少域名 user@ false 异常路径 P0 无域名
TC-0282 缺少TLD user@example false 异常路径 P0 TLD<2字符
TC-0283 空字符串 "" false 边界 P1

5.3 IsValidPhone

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0284 国内手机号 13800138000 true 正常路径 P0 11位数字
TC-0285 带+国际码 +8613800138000 true 正常路径 P0 +前缀
TC-0286 太短(6位) 123456 false 边界 P0 <7位
TC-0287 恰好7位 1234567 true 边界 P1 最小长度
TC-0288 最长15位 +123456789012345 true 边界 P1 最大长度
TC-0289 超长16位 1234567890123456 false 边界 P1 超限
TC-0290 包含字母 1380013abc false 异常路径 P0 非数字
TC-0291 空字符串 "" false 边界 P1

六、Logic 层单元测试用例

以下针对 Logic 层中的核心共享函数,使用 mock Model 接口进行纯单元测试。

6.1 auth/jwt.go — GenerateAccessToken

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0292 正常生成 secret="s", expire=3600, userId=1, username="u", productCode="p", memberType="M", perms=["a"] 返回非空token, err=nil 正常路径 P0 jwt.NewWithClaims(HS256)
TC-0293 解析token验证claims 上述token ParseWithClaims可解析出正确userId/username/productCode/memberType/perms 功能验证 P0 claims完整性
TC-0294 空secret secret="" 仍能生成token(空key签名) 边界 P2 HS256 允许空key
TC-0295 空perms perms=nil token生成成功, 解析后perms=nil 边界 P1 nil slice
TC-0296 过期时间验证 expireSeconds=1, sleep 2s ParseWithClaims返回过期错误 功能验证 P0 ExpiresAt

6.2 auth/jwt.go — GenerateRefreshToken

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0297 正常生成 secret="s", expire=86400, userId=1, productCode="p" 返回非空token 正常路径 P0 RefreshClaims
TC-0298 解析验证 上述token ParseRefreshToken解析出userId=1, productCode="p" 功能验证 P0 往返一致
TC-0299 productCode为空 productCode="" 生成成功, 解析后productCode="" 边界 P1 空字符串

6.3 auth/jwt.go — ParseRefreshToken

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0300 正常解析 有效token+正确secret 返回RefreshClaims, err=nil 正常路径 P0 token.Valid
TC-0301 错误secret 有效token+错误secret err!=nil 异常路径 P0 签名验证失败
TC-0302 无效token字符串 "invalid-token" err!=nil 异常路径 P0 解析失败
TC-0303 空token "" err!=nil 边界 P1 空字符串
TC-0304 过期token 已过期的token err!=nil (token expired) 异常路径 P0 ExpiresAt已过
TC-0305 AccessToken误用 用AccessToken当RefreshToken解析 err!=nil (TokenType="access"≠"refresh") 安全 P0 TokenType字段校验

6.4 middleware — 辅助函数单元测试

M-5/M-6重构GetUserPerms (auth/perms.go) 以及 GetUsername / GetMemberType / IsSuperAdmin 等 context helper 作为死代码被移除;统一使用 GetUserDetails 读取完整 UserDetails 后访问字段。

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0306 GetUserId-正常 ctx含userId=100 100 正常路径 P0 类型断言成功
TC-0307 GetUserId-空ctx 空ctx 0 边界 P0 断言失败→零值
TC-0308 GetProductCode-正常 ctx含productCode="p1" "p1" 正常路径 P0 类型断言
TC-0309 GetUserDetails 返回完整字段 ctx含UserDetails{UserId,Username,ProductCode,MemberType,IsSuperAdmin} 读出字段全部一致, 空ctx返回nil 正常路径 P0 替代已移除的 GetUsername/GetMemberType/IsSuperAdmin

七、Model 层 _gen.go 模板生成方法测试用例

所有 9 个 Model 的 _gen.go 均由自定义模板 (cli/goctl/model/) 生成,包含非标准方法(批量操作、事务变体、buildBatchUpdateQuery 等)。 以下以 通用测试模式 列出,适用于全部 9 个 Model(注明差异部分)。

7.1 通用 CRUD 方法 (每个 Model 均需测试)

适用: SysUser, SysProduct, SysPerm, SysDept, SysRole, SysRolePerm, SysUserPerm, SysUserRole, SysProductMember

TC编号 方法 测试场景 预期结果 类型 优先级 覆盖说明
TC-0310 Insert 正常插入 返回Result+nil, DB有新记录 正常路径 P0 ExecCtx+缓存key清理
TC-0311 Insert 正常插入含TokenVersion err=nil, DB中tokenVersion=0(默认) 正常路径 P0 验证Insert包含tokenVersion
TC-0312 Insert 唯一索引冲突 返回DB错误(1062) 异常路径 P0 MySQL uk
TC-0313 Insert 缓存key生成正确 验证清理的缓存key包含主键和唯一索引 功能验证 P0 cacheSys*Prefix
TC-0314 InsertWithTx 事务内插入 使用session执行, 返回Result 正常路径 P0 session.ExecCtx
TC-0315 InsertWithTx 事务内插入含TokenVersion err=nil, 事务内可读到正确tokenVersion 正常路径 P0 验证InsertWithTx包含tokenVersion
TC-0316 InsertWithTx 事务回滚后无数据 事务内Insert+外部回滚→DB无记录 事务验证 P0 TransactCtx
TC-0317 FindOne 正常查询(缓存未命中) 返回记录, 缓存已写入 正常路径 P0 QueryRowCtx→DB
TC-0318 FindOne 正常查询(缓存命中) 不触发DB查询, 返回缓存数据 正常路径 P0 QueryRowCtx→cache
TC-0319 FindOne 记录不存在 返回ErrNotFound 异常路径 P0 sqlc.ErrNotFound→ErrNotFound
TC-0320 FindOne DB异常(非ErrNotFound) 返回原始error 异常路径 P1 default分支
TC-0321 FindOneWithTx 事务内正常查询 使用session.QueryRowCtx, 返回记录 正常路径 P0 session直查无缓存
TC-0322 FindOneWithTx 事务内记录不存在 返回ErrNotFound 异常路径 P0 sqlx.ErrNotFound
TC-0323 FindOneWithTx 事务内可见性 InsertWithTx后FindOneWithTx可读到 事务验证 P0 同session内可见
TC-0324 Update 正常更新 旧缓存key+新缓存key均被清理 正常路径 P0 FindOne→ExecCtx
TC-0325 Update 正常更新含TokenVersion err=nil, DB中tokenVersion正确更新 正常路径 P0 验证Update包含tokenVersion
TC-0326 Update 记录不存在 FindOne失败→返回ErrNotFound 异常路径 P0 FindOne err
TC-0327 UpdateWithTx 事务内更新 使用session, 缓存被清理 正常路径 P0 session.ExecCtx
TC-0328 Delete 正常删除 记录被删, 缓存key被清理 正常路径 P0 FindOne→ExecCtx DELETE
TC-0329 Delete 记录不存在 FindOne失败→返回ErrNotFound 异常路径 P0 FindOne err
TC-0330 DeleteWithTx 事务内删除 使用session, 缓存被清理 正常路径 P0 session.ExecCtx
TC-0331 TransactCtx 正常事务 fn执行成功→提交 正常路径 P0 conn.TransactCtx
TC-0332 TransactCtx fn返回错误 自动回滚 异常路径 P0 回滚
TC-0333 TableName 获取表名 返回正确表名(如 `sys_user`) 正常路径 P0 m.table

7.2 批量插入方法

TC编号 方法 测试场景 预期结果 类型 优先级 覆盖说明
TC-0334 BatchInsert 空列表 直接返回nil, 不执行SQL 边界 P0 len==0 early return
TC-0335 BatchInsert 单条记录 生成1组VALUES, 执行成功 正常路径 P0 单条
TC-0336 BatchInsert 多条记录(3条) 生成3组VALUES, SQL正确, 缓存key全清理 正常路径 P0 多条+缓存
TC-0337 BatchInsert 批量插入含TokenVersion err=nil, 所有记录tokenVersion正确 正常路径 P0 验证BatchInsert包含tokenVersion
TC-0338 BatchInsert 唯一索引冲突 全部失败, 返回DB错误 异常路径 P0 MySQL uk
TC-0339 BatchInsert 大批量(1000条) SQL长度合理, 执行成功 性能 P2 拼接性能
TC-0340 BatchInsertWithTx 空列表 直接返回nil 边界 P0 len==0
TC-0341 BatchInsertWithTx 正常多条 使用session执行 正常路径 P0 session.ExecCtx
TC-0342 BatchInsertWithTx 事务回滚 外部回滚→无新记录 事务验证 P0 TransactCtx

7.3 批量更新方法

TC编号 方法 测试场景 预期结果 类型 优先级 覆盖说明
TC-0343 BatchUpdate 空列表 直接返回nil 边界 P0 len==0 early return
TC-0344 BatchUpdate 单条记录 CASE-WHEN SQL正确, 更新成功 正常路径 P0 buildBatchUpdateQuery 单条
TC-0345 BatchUpdate 多条记录(3条) CASE-WHEN生成3个WHEN子句, 旧缓存key全清理 正常路径 P0 buildBatchUpdateQuery 多条
TC-0346 BatchUpdate 批量更新不污染数据 err=nil, tokenVersion/createTime/updateTime均正确 正常路径 P0 验证buildBatchUpdateQuery值对齐
TC-0347 BatchUpdate 部分id不存在 findListByPrimaryKeys返回部分→仅清理存在的缓存 边界 P1 oldList可能少于dataList
TC-0348 BatchUpdateWithTx 空列表 直接返回nil 边界 P0 len==0
TC-0349 BatchUpdateWithTx 正常多条 使用session执行 正常路径 P0 session.ExecCtx
TC-0350 buildBatchUpdateQuery 单条 SQL: UPDATE SET field=CASE WHEN id=? THEN ? ELSE field END WHERE id IN (?) 功能验证 P0 SQL结构
TC-0351 buildBatchUpdateQuery 多条 每个字段均有多个WHEN子句, WHERE IN含全部id 功能验证 P0 SQL正确性
TC-0352 buildBatchUpdateQuery vals数量正确 vals = N*(fields*2) + N (WHERE IN) 功能验证 P0 参数计数

7.4 批量删除方法

TC编号 方法 测试场景 预期结果 类型 优先级 覆盖说明
TC-0353 BatchDelete 空ids 直接返回nil 边界 P0 len==0 early return
TC-0354 BatchDelete 单个id DELETE WHERE id IN (?), 缓存清理 正常路径 P0 单条
TC-0355 BatchDelete 多个id(3个) 3个占位符, 旧数据查询→缓存key全清理 正常路径 P0 findListByPrimaryKeys
TC-0356 BatchDelete 包含不存在id findListByPrimaryKeys返回部分, 不报错 边界 P1 部分存在
TC-0357 BatchDeleteWithTx 空ids 直接返回nil 边界 P0 len==0
TC-0358 BatchDeleteWithTx 正常多条 使用session执行 正常路径 P0 session.ExecCtx

7.5 唯一索引查询方法 (按 Model 差异)

TC编号 Model 方法 测试场景 预期结果 优先级 覆盖说明
TC-0359 SysUser FindOneByUsername 正常查询 返回用户, 缓存写入 (索引缓存→主键缓存双层) P0 QueryRowIndexCtx
TC-0360 SysUser FindOneByUsername 不存在 返回ErrNotFound P0 sqlc.ErrNotFound
TC-0361 SysUser FindOneByUsernameWithTx 事务内正常查询 返回用户, 使用session直查 P0 session.QueryRowCtx
TC-0362 SysUser FindOneByUsernameWithTx 事务内不存在 返回ErrNotFound P0 sqlx.ErrNotFound
TC-0363 SysProduct FindOneByAppKey 正常查询 返回产品 P0 appKey唯一索引
TC-0364 SysProduct FindOneByAppKey 不存在 返回ErrNotFound P0
TC-0365 SysProduct FindOneByAppKeyWithTx 事务内正常查询 返回产品 P0 session直查
TC-0366 SysProduct FindOneByAppKeyWithTx 事务内不存在 返回ErrNotFound P0
TC-0367 SysProduct FindOneByCode 正常查询 返回产品 P0 code唯一索引
TC-0368 SysProduct FindOneByCode 不存在 返回ErrNotFound P0
TC-0369 SysProduct FindOneByCodeWithTx 事务内正常查询 返回产品 P0 session直查
TC-0370 SysProduct FindOneByCodeWithTx 事务内不存在 返回ErrNotFound P0
TC-0371 SysPerm FindOneByProductCodeCode 正常查询 返回权限(复合唯一索引) P0 productCode+code
TC-0372 SysPerm FindOneByProductCodeCode 不存在 返回ErrNotFound P0
TC-0373 SysPerm FindOneByProductCodeCodeWithTx 事务内正常查询 返回权限 P0 session直查
TC-0374 SysPerm FindOneByProductCodeCodeWithTx 事务内不存在 返回ErrNotFound P0
TC-0375 SysRole FindOneByProductCodeName 正常查询 返回角色(复合唯一索引) P0 productCode+name
TC-0376 SysRole FindOneByProductCodeName 不存在 返回ErrNotFound P0
TC-0377 SysRole FindOneByProductCodeNameWithTx 事务内正常查询 返回角色 P0 session直查
TC-0378 SysRole FindOneByProductCodeNameWithTx 事务内不存在 返回ErrNotFound P0
TC-0379 SysRolePerm FindOneByRoleIdPermId 正常查询 返回关联记录 P0 roleId+permId
TC-0380 SysRolePerm FindOneByRoleIdPermId 不存在 返回ErrNotFound P0
TC-0381 SysRolePerm FindOneByRoleIdPermIdWithTx 事务内正常查询 返回关联记录 P0 session直查
TC-0382 SysRolePerm FindOneByRoleIdPermIdWithTx 事务内不存在 返回ErrNotFound P0
TC-0383 SysUserPerm FindOneByUserIdPermId 正常查询 返回关联记录 P0 userId+permId
TC-0384 SysUserPerm FindOneByUserIdPermId 不存在 返回ErrNotFound P0
TC-0385 SysUserPerm FindOneByUserIdPermIdWithTx 事务内正常查询 返回关联记录 P0 session直查
TC-0386 SysUserPerm FindOneByUserIdPermIdWithTx 事务内不存在 返回ErrNotFound P0
TC-0387 SysUserRole FindOneByUserIdRoleId 正常查询 返回关联记录 P0 userId+roleId
TC-0388 SysUserRole FindOneByUserIdRoleId 不存在 返回ErrNotFound P0
TC-0389 SysUserRole FindOneByUserIdRoleIdWithTx 事务内正常查询 返回关联记录 P0 session直查
TC-0390 SysUserRole FindOneByUserIdRoleIdWithTx 事务内不存在 返回ErrNotFound P0
TC-0391 SysProductMember FindOneByProductCodeUserId 正常查询 返回成员记录 P0 productCode+userId
TC-0392 SysProductMember FindOneByProductCodeUserId 不存在 返回ErrNotFound P0
TC-0393 SysProductMember FindOneByProductCodeUserIdWithTx 事务内正常查询 返回成员记录 P0 session直查
TC-0394 SysProductMember FindOneByProductCodeUserIdWithTx 事务内不存在 返回ErrNotFound P0

7.6 内部辅助方法

TC编号 方法 测试场景 预期结果 类型 优先级 覆盖说明
TC-0395 findListByPrimaryKeys 空ids 返回空slice, 不执行SQL 边界 P0 len==0
TC-0396 findListByPrimaryKeys 正常ids 返回匹配记录(无缓存) 正常路径 P0 QueryRowsNoCacheCtx
TC-0397 findListByPrimaryKeys 部分不存在 仅返回存在的记录 边界 P1 IN查询
TC-0398 findListByPrimaryKeys DB异常 返回nil, err 异常路径 P1 err透传
TC-0399 getPrimaryKeyValue 正常 返回data.Id 功能验证 P0 interface{}
TC-0400 formatPrimary 正常 返回 "cache:sysXxx:id:{id}" 功能验证 P0 缓存key格式
TC-0401 queryPrimary 正常 执行 SELECT WHERE id=? 功能验证 P0 SQL

7.7 缓存key与前缀初始化

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0402 cachePrefix为空 cachePrefix="" 使用默认前缀 (如 "cache:sysUser:id:") 分支覆盖 P0 if cachePrefix!="" 未进入
TC-0403 cachePrefix非空 cachePrefix="test" 前缀变为 "test:cache:sysUser:id:" 分支覆盖 P0 if cachePrefix!="" 进入
TC-0404 多唯一索引前缀(SysProduct) cachePrefix="test" 3个缓存前缀均更新: id/appKey/code 功能验证 P0 3个变量均修改

八、Model 层自定义方法测试用例

8.1 SysUserModel

TC编号 方法 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0405 FindListByPage 正常分页 page=1, pageSize=10, DB有20条 返回10条+total=20 正常路径 P0 count+limit offset
TC-0406 FindListByPage 第二页 page=2, pageSize=10 offset=10, 返回后10条 正常路径 P0 (page-1)*pageSize
TC-0407 FindListByPage 空表 无数据 total=0, list为空 边界 P0 count=0
TC-0408 FindListByPage count查询失败 DB异常 返回0,0,err 异常路径 P1 第一个err
TC-0409 FindListByPage list查询失败 DB异常 返回0,total,err 异常路径 P1 第二个err
TC-0410 FindListByProductMembers 正常查询 productCode="p1", page=1, pageSize=10 返回该产品所有成员用户, total正确 正常路径 P0 替代FindListByDeptIds: INNER JOIN sys_product_member
TC-0411 FindListByProductMembers productCode不存在 productCode="no_such_pc" total=0, list空 边界 P1 JOIN 无匹配
TC-0412 FindByIds 正常批量查询 ids=[1,2,3] 返回3条 正常路径 P0 IN查询
TC-0413 FindByIds 空ids ids=[] 返回nil,nil 边界 P0 len==0
TC-0414 FindByIds 部分id不存在 ids=[1,9999] 仅返回存在的 边界 P1 IN不报错
TC-0415 FindByIds DB异常 连接失败 返回nil,err 异常路径 P1 err透传
TC-0416 FindIdsByDeptId 有用户的部门 deptId=1(有用户) 返回id列表 正常路径 P0 WHERE deptId=?
TC-0417 FindIdsByDeptId 无用户部门 deptId=999 空slice 边界 P1
TC-0418 UpdateProfile 状态未变-不递增tokenVersion statusChanged=false 成功, tokenVersion不变 正常路径 P0 H-1修复: 非状态字段更新不影响会话
TC-0419 UpdateProfile 状态变更-tokenVersion+1 statusChanged=true 成功, tokenVersion+1 正常路径 P0 H-1修复: 状态变更使会话失效
TC-0420 UpdateProfile 乐观锁冲突 expectedUpdateTime 与DB不符 返回ErrUpdateConflict 异常路径 P0 H-1修复: WHERE updateTime=?
TC-0421 UpdateProfile 并发场景 两个 goroutine 基于同一 updateTime 并发更新 仅一方成功, 另一方得到 ErrUpdateConflict 并发 P0 H-1修复: 乐观锁仅允许一个成功
TC-0422 UpdateProfile userId不存在 id=9999999 返回 ErrUpdateConflict (affected=0) 异常路径 P1 WHERE 不匹配

8.2 SysProductModel

TC编号 方法 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0423 FindList 正常分页 page=1, pageSize=10 返回list+total 正常路径 P0 count+limit
TC-0424 FindList 空表 无数据 total=0, list空 边界 P0
TC-0425 FindList count失败 DB异常 返回err 异常路径 P1 第一个err

8.3 SysPermModel

TC编号 方法 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0426 FindListByProductCode 正常分页 productCode="p1", page=1, pageSize=10 list+total 正常路径 P0 WHERE productCode=?
TC-0427 FindListByProductCode 不存在的productCode "notexist" total=0, list空 边界 P1
TC-0428 FindAllCodesByProductCode 正常查询 DB有3条启用权限 返回3个code 正常路径 P0 SELECT code WHERE status=1
TC-0429 FindAllCodesByProductCode 空结果 无匹配 空slice 边界 P1
TC-0430 FindByIds 正常 ids=[1,2] 返回2条 正常路径 P0 IN查询
TC-0431 FindByIds 空ids [] 返回nil,nil 边界 P0 len==0
TC-0432 FindMapByProductCode 正常查询 productCode="p1" map[code]*SysPerm, key为code 正常路径 P0 result[p.Code]=p
TC-0433 FindMapByProductCode 空结果 无匹配 空map 边界 P1
TC-0434 FindMapByProductCode key唯一性 同productCode有3条 map长度=3 功能验证 P0 code唯一
TC-0435 DisableNotInCodesWithTx codes非空-正常 session+productCode="p1", codes=["a","b"], DB有a/b/c 禁用c, 返回affected=1 正常路径 P0 事务内 NOT IN
TC-0436 DisableNotInCodesWithTx codes为空-全部禁用 codes=[] 全部已启用的被禁用 分支覆盖 P0 len==0分支
TC-0437 DisableNotInCodesWithTx 无需禁用 codes包含所有已启用 affected=0 边界 P1 0行更新
TC-0438 DisableNotInCodesWithTx DB异常 session.Exec报错 返回0,err 异常路径 P1 err
TC-0439 FindAllCodesByProductCode 有权限产品 productCode="p1" 返回code列表(仅status=1) 正常路径 P0 WHERE status=1
TC-0440 FindAllCodesByProductCode 无权限产品 productCode="notexist" 空slice 边界 P1
TC-0441 FindAllCodesByProductCode 全部已禁用 所有perm.status=2 空slice 边界 P1 WHERE status=1 过滤, 全禁用时返回空

8.4 SysDeptModel

TC编号 方法 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0442 FindAll 正常查询 DB有5条 返回5条, 按sort asc排序 正常路径 P0 ORDER BY sort, id
TC-0443 FindAll 空表 无数据 空slice 边界 P0
TC-0444 FindByParentId 正常查询 parentId=1 返回子部门列表 正常路径 P0 WHERE parentId=?
TC-0445 FindByParentId 无子部门 parentId=999 空slice 边界 P1
TC-0446 FindByPathPrefix 正常查询 pathPrefix="/1/" 返回路径以/1/开头的部门 正常路径 P0 LIKE pathPrefix%
TC-0447 FindByPathPrefix LIKE注入已阻止 pathPrefix含% 空slice(%和_已转义,不作为通配符) 安全 P1 NewReplacer转义
TC-0448 FindByPathPrefix 无匹配 "/999/" 空slice 边界 P1

8.5 SysRoleModel

TC编号 方法 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0449 FindListByProductCode 正常分页 productCode="p1" 按permsLevel asc排序 正常路径 P0 ORDER BY permsLevel, id
TC-0450 FindListByProductCode 空结果 无匹配 total=0 边界 P1
TC-0451 FindByIds 正常 ids=[1,2] 返回2条 正常路径 P0 IN查询
TC-0452 FindByIds 空ids [] 返回nil,nil 边界 P0 len==0
TC-0453 FindMinPermsLevelByUserIdAndProductCode 有角色用户 userId=1, productCode="p1" 返回最小permsLevel 正常路径 P0 MIN聚合
TC-0454 FindMinPermsLevelByUserIdAndProductCode 无角色用户 userId=无角色用户 error(ErrNotFound) 边界 P0 IFNULL返回-1→level<0→ErrNotFound

8.6 SysRolePermModel

TC编号 方法 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0455 FindPermIdsByRoleId 正常查询 roleId=1, DB有3条 返回3个permId 正常路径 P0 SELECT permId WHERE roleId=?
TC-0456 FindPermIdsByRoleId 无绑定 roleId=999 空slice 边界 P1
TC-0457 FindPermIdsByRoleIds 正常查询 roleIds=[1,2] 返回去重后的permId 正常路径 P0 DISTINCT + IN
TC-0458 FindPermIdsByRoleIds 空roleIds [] 返回nil,nil 边界 P0 len==0
TC-0459 FindPermIdsByRoleIds 去重验证 两角色有相同permId 结果中permId不重复 功能验证 P0 DISTINCT
TC-0460 DeleteByRoleIdTx 正常事务内删除 session+roleId 使用session执行 正常路径 P0 session.ExecCtx
TC-0461 DeleteByRoleIdTx 无绑定 session+roleId=999 删0行, 不报错 边界 P1 session.ExecCtx, affected=0 不报错

8.7 SysUserPermModel

TC编号 方法 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0462 FindPermIdsByUserIdAndEffectForProduct ALLOW-指定产品 userId=1, effect="ALLOW", productCode="p1" 返回该用户在该产品下ALLOW的permIds 正常路径 P0 H-1修复: 跨产品权限隔离
TC-0463 FindPermIdsByUserIdAndEffectForProduct DENY-指定产品 userId=1, effect="DENY", productCode="p1" 返回DENY的permIds 正常路径 P0 H-1修复: 跨产品权限隔离
TC-0464 FindPermIdsByUserIdAndEffectForProduct 无记录/其他产品 productCode不匹配 空slice 边界 P1 WHERE 子句过滤 productCode 后无匹配
TC-0465 DeleteByUserIdForProductTx 事务内跨产品删除 session+userId+productCode 使用session 正常路径 P0 session.ExecCtx, 仅删该产品下配置
TC-0466 DeleteByUserIdForProductTx 跨产品隔离 用户在多产品有配置 仅删目标产品的 深度业务 P0 WHERE userId=? AND productCode=? 隔离其他产品

8.8 SysUserRoleModel

TC编号 方法 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0467 FindRoleIdsByUserId 正常查询 userId=1, DB有3条 返回3个roleId 正常路径 P0 SELECT roleId
TC-0468 FindRoleIdsByUserId 无绑定 userId=999 空slice 边界 P1
TC-0469 DeleteByRoleIdTx 正常删除 session+roleId 删除该角色的所有用户绑定 正常路径 P0 session.ExecCtx
TC-0470 DeleteByUserIdForProductTx 事务内跨产品删除 session+userId+productCode 使用session 正常路径 P0 session.ExecCtx
TC-0471 DeleteByUserIdForProductTx 跨产品隔离 用户在多产品有角色 仅删目标产品的 深度业务 P0 WHERE userId=? AND productCode=? 子查询过滤
TC-0472 FindUserIdsByRoleId 有绑定的角色 roleId=1 返回userId列表 正常路径 P0 WHERE roleId=?
TC-0473 FindUserIdsByRoleId 无绑定角色 roleId=999 空slice 边界 P1
TC-0474 FindRoleIdsByUserIdForProduct 跨产品过滤 userId=1, productCode="p1" 仅返回该产品下绑定的roleId 深度业务 P0 差量更新依赖此接口

8.9 SysProductMemberModel

TC编号 方法 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0475 FindListByProductCode 正常分页 productCode="p1" list+total 正常路径 P0 WHERE productCode=?
TC-0476 FindListByProductCode 空结果 无匹配 total=0 边界 P1
TC-0477 FindMapByProductCodeUserIds 正常批量 productCode="p1", userIds=[1,2] map key=userId 正常路径 P0 IN+productCode
TC-0478 FindMapByProductCodeUserIds 空userIds [] 返回空map 边界 P0 len==0
TC-0479 FindMapByProductCodeUserIds 部分不是成员 userIds含非成员 map仅含成员 边界 P1
TC-0480 FindMapByProductCodeUserIds map key正确 查询结果 key=userId, val=*SysProductMember 功能验证 P0 result[pm.UserId]

九、访问控制 (auth/access.go)

9.1 RequireSuperAdmin

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0481 超管通过 ctx含SuperAdmin UserDetails nil (允许) 正常路径 P0 caller.IsSuperAdmin
TC-0482 非超管拒绝 ctx含ADMIN UserDetails 403 "仅超级管理员" 异常路径 P0 !IsSuperAdmin
TC-0483 MEMBER拒绝 ctx含MEMBER UserDetails 403 "仅超级管理员" 异常路径 P0
TC-0484 未登录 ctx无UserDetails 401 "未登录" 边界 P0 caller==nil

9.2 RequireProductAdminFor(ctx, targetProductCode)

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0485 超管通过 ctx含SuperAdmin, productCode="p1" nil 正常路径 P0 IsSuperAdmin
TC-0486 ADMIN通过(同产品) ctx含ADMIN(p1), productCode="p1" nil 正常路径 P0 MemberType==ADMIN且productCode匹配
TC-0487 DEVELOPER拒绝 ctx含DEVELOPER(p1), productCode="p1" 403 异常路径 P0 非Admin
TC-0488 MEMBER拒绝 ctx含MEMBER(p1), productCode="p1" 403 异常路径 P0 非Admin
TC-0489 未登录 ctx无UserDetails, productCode="p1" 401 边界 P0 caller==nil
TC-0490 ADMIN跨产品拒绝 ctx含ADMIN(p1), productCode="other" 403 安全 P0 productCode不匹配→拒绝

9.3 CheckMemberTypeAssignment

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0491 超管可分配任何类型 caller=SuperAdmin, assigned=ADMIN nil 正常路径 P0 IsSuperAdmin豁免
TC-0492 ADMIN分配DEVELOPER caller=ADMIN, assigned=DEVELOPER nil 正常路径 P0 callerPri(1) < assignPri(2)
TC-0493 ADMIN分配ADMIN(同级拒绝) caller=ADMIN, assigned=ADMIN 403 深度业务 P0 callerPri >= assignPri
TC-0494 DEVELOPER分配ADMIN(越级拒绝) caller=DEVELOPER, assigned=ADMIN 403 深度业务 P0 callerPri > assignPri
TC-0495 MEMBER分配MEMBER(同级拒绝) caller=MEMBER, assigned=MEMBER 403 深度业务 P0
TC-0496 未登录 ctx无UserDetails 401 边界 P0

9.4 CheckManageAccess

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0497 超管可管理任何人 caller=SuperAdmin nil 正常路径 P0 IsSuperAdmin豁免
TC-0498 操作自己 caller.UserId==targetUserId nil 正常路径 P0 self豁免
TC-0499 ADMIN跳过部门检查 caller=ADMIN nil (直接比级别) 深度业务 P0 checkDeptHierarchy ADMIN豁免
TC-0500 非ADMIN无部门拒绝 caller.DeptId=0 403 "未归属部门" 边界 P0 caller.DeptId==0
TC-0501 目标用户无部门 target.DeptId=0 403 "目标用户未归属部门" 边界 P0 target.DeptId==0
TC-0502 目标在不同部门 目标不在caller子部门 403 "无权管理其他部门" 深度业务 P0 !HasPrefix
TC-0503 未登录 ctx无UserDetails 401 边界 P0
TC-0504 caller.DeptPath为空时拒绝 caller有DeptId但DeptPath="" 403 "无权管理" 安全 P0 H-08修复: DeptPath空串保护

9.5 memberTypePriority

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0505 各类型优先级正确 全部4种+未知 SA=0,A=1,D=2,M=3,unknown=MaxInt32 白盒 P0 switch分支

十、UserDetailsLoader (loaders/userDetailsLoader.go)

10.1 Load / 缓存

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0506 DB加载(缓存miss) 有效userId+productCode 返回完整UserDetails 正常路径 P0 loadFromDB全链路
TC-0507 缓存命中 第二次Load同key 从Redis返回,不查DB 正常路径 P0 GetCtx hit
TC-0508 用户不存在 userId=999999 返回零值UserDetails(Status=0) 边界 P0 loadUser失败
TC-0509 productCode为空 productCode="" 跳过产品/成员/角色/权限加载 边界 P1 各方法guard

10.2 缓存失效

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0510 Del删除指定缓存 Del(uid, pc) 缓存被删除,下次Load查DB 正常路径 P0 DelCtx
TC-0511 Clean清除用户所有产品缓存 Clean(uid) 该用户所有key被删 正常路径 P0 KEYS pattern
TC-0512 CleanByProduct清除产品所有用户 CleanByProduct(pc) 该产品所有key被删 正常路径 P0 KEYS pattern
TC-0513 BatchDel批量删除 BatchDel([uid1,uid2], pc) 多个key被删 正常路径 P0 DelCtx多key
TC-0514 BatchDel空数组 BatchDel([], pc) 无操作 边界 P1 len==0 guard

10.3 loadPerms权限计算

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0515 超管全量权限 IsSuperAdmin=true Perms=全部启用的权限码 正常路径 P0 超管分支
TC-0516 ADMIN全量权限 MemberType=ADMIN Perms=全量 正常路径 P0 ADMIN分支
TC-0517 DEVELOPER全量权限 MemberType=DEVELOPER Perms=全量 正常路径 P0 DEVELOPER分支
TC-0518 DEV部门全量权限 DeptType=DEV Perms=全量 正常路径 P0 DeptTypeDev分支
TC-0519 MEMBER角色权限+ALLOW-DENY 有角色+ALLOW+DENY 正确计算 深度业务 P0 denySet过滤
TC-0520 用户ALLOW权限不跨产品泄漏 用户在产品A/B各有ALLOW权限 加载产品A时仅含A权限,不含B权限 安全 P0 H-1: FindPermIdsByUserIdAndEffectForProduct
TC-0521 禁用DEV部门成员无全量权限 dept.type=DEV, dept.status=Disabled ud.Perms为空 安全 P0 M-3: DeptStatus检查

10.4 loadRoles + MinPermsLevel

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0522 多角色取最小permsLevel 用户有level=10和level=5的角色 MinPermsLevel=5 正常路径 P0 min计算
TC-0523 无角色 用户无角色 MinPermsLevel=MaxInt64 边界 P0 默认值
TC-0524 角色跨产品过滤 角色在不同产品 仅加载当前产品角色 深度业务 P0 productCode过滤
TC-0525 禁用角色不计入 角色status=2 不在Roles列表中 深度业务 P0 Status==Enabled

10.5 loadMembership

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0526 超管自动设置SUPER_ADMIN IsSuperAdmin=true MemberType=SUPER_ADMIN, 不查DB 正常路径 P0 早期return
TC-0527 非成员MemberType为空 用户非该产品成员 MemberType="" 边界 P0 ErrNotFound
TC-0528 禁用成员MemberType为空 member.status=Disabled ud.MemberType="" 安全 P0 H-3: loadMembership

十一、中间件 — 冻结账号拦截

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0529 冻结用户被403 有效token但Status=2 code=403 "账号已被冻结" 安全 P0 ud.Status!=Enabled
TC-0530 用户不存在(Status=0) token中userId不存在 code=403 "账号已被冻结" 安全 P0 loadUser失败→Status=0
TC-0531 UserDetails注入context 正常请求 GetUserDetails(ctx)非nil 正常路径 P0 WithUserDetails

十二、Logic层 — 访问控制负面测试

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0532 createDept非超管拒绝 ctx=ADMIN 403 "仅超级管理员" 安全 P0 RequireSuperAdmin
TC-0533 updateDept非超管拒绝 ctx=ADMIN 403 "仅超级管理员" 安全 P0 RequireSuperAdmin
TC-0534 deleteDept非超管拒绝 ctx=ADMIN 403 "仅超级管理员" 安全 P0 RequireSuperAdmin
TC-0535 createProduct非超管拒绝 ctx=ADMIN 403 "仅超级管理员" 安全 P0 RequireSuperAdmin
TC-0536 updateProduct非超管拒绝 ctx=ADMIN 403 "仅超级管理员" 安全 P0 RequireSuperAdmin
TC-0537 createUser非产品管理员拒绝 ctx=MEMBER 403 "仅超级管理员或产品管理员" 安全 P0 RequireProductAdminFor(ctx, productCode)
TC-0538 createRole非产品管理员拒绝 ctx=MEMBER 403 安全 P0 RequireProductAdminFor(ctx, productCode)
TC-0539 updateRole非产品管理员拒绝 ctx=MEMBER 403 安全 P0 RequireProductAdminFor(ctx, productCode)
TC-0540 deleteRole非产品管理员拒绝 ctx=MEMBER 403 安全 P0 RequireProductAdminFor(ctx, productCode)
TC-0541 bindRolePerms非产品管理员拒绝 ctx=MEMBER 403 安全 P0 RequireProductAdminFor(ctx, productCode)
TC-0542 updateUser-MEMBER不能管理他人 ctx=MEMBER, id!=self 403 (CheckManageAccess拒绝) 安全 P0 Audit#4修复: CheckManageAccess权限校验
TC-0543 updateUser自己修改DeptId被拒绝 ctx含userId=X, req.Id=X, req.DeptId!=nil 403 "不允许修改自己的部门和状态" 安全 P0 H-01修复: 自编辑限制DeptId
TC-0544 updateUser自己修改Status被拒绝 ctx含userId=X, req.Id=X, req.Status!=0 403 "不允许修改自己的部门和状态" 安全 P0 H-01修复: 自编辑限制Status
TC-0545 updateUser未登录被拒绝 ctx无UserDetails 401 "未登录" 安全 P0 H-01修复: caller==nil

十三、限流中间件 (middleware/ratelimitMiddleware.go)

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0546 正常请求(未超限) 首次请求 请求正常通过, next被调用 正常路径 P0 code!=OverQuota→next
TC-0547 超限请求被拒绝 超出配额后的请求 code=429, "请求过于频繁,请稍后再试" 异常路径 P0 code==OverQuota→ErrTooManyRequests
TC-0548 behindProxy=false时XFF被忽略 behindProxy=false, 不同XFF头+相同RemoteAddr 仍被限流, nextCount保持为1 安全 P0 behindProxy=false: 仅用RemoteAddr
TC-0549 behindProxy=false时X-Real-IP被忽略 behindProxy=false, 不同XRI头+相同RemoteAddr 仍被限流, nextCount保持为1 安全 P0 behindProxy=false: 仅用RemoteAddr
TC-0550 IP从RemoteAddr解析 无代理头, RemoteAddr="ip:port" 使用SplitHostPort解析host作为限流key 分支覆盖 P0 SplitHostPort解析host
TC-0551 不同IP独立限流 两个不同IP 各自独立计数, 互不影响 功能验证 P0 key隔离
TC-0552 behindProxy=true时信任X-Real-IP behindProxy=true, 不同X-Real-IP头 按X-Real-IP独立限流 正常路径 P0 behindProxy=true: X-Real-IP优先
TC-0553 behindProxy=true时无X-Real-IP回退RemoteAddr behindProxy=true, 无X-Real-IP头 使用RemoteAddr作为限流key 分支覆盖 P0 X-Real-IP为空→fallback RemoteAddr
TC-0554 behindProxy=true 时 XFF 仍被忽略 已删除:M-6 显式反转契约 —— behindProxy=true 时 XFF 首段合法应优先;新契约由 TC-0862~0866(ratelimitMiddlewareXff_audit_test.go)覆盖
TC-0555 RemoteAddr无端口格式 RemoteAddr="1.2.3.4"(无端口) 返回原始RemoteAddr "1.2.3.4" 边界 P1 SplitHostPort失败→r.RemoteAddr

十四、审计修复回归测试 (audit-report.md 2026-04 修复集)

新增测试用于验证近期针对 audit-report.md 的高/中/低风险项所提交的修复,确保修复行为严格生效且不回归。

H-1 BindRoles permsLevel 仅对 MEMBER 生效

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0208 MEMBER 调用者不能分配权限级别高于自身的角色 caller=MEMBER, MinPermsLevel=50, 目标角色 permsLevel=100 403 "不能分配权限级别高于自身的角色" 越权/安全 P0 BindRoles H-1 修复:permsLevel 校验仍作用于 MEMBER
TC-0711 ADMIN 调用者豁免 permsLevel 校验 caller=ADMIN, MinPermsLevel=math.MaxInt64, 目标角色任意 permsLevel 成功绑定 正常路径 P0 H-1 修复:ADMIN/DEVELOPER 不再受 permsLevel 约束
TC-0712 DEVELOPER 调用者豁免 permsLevel 校验 caller=DEVELOPER, 目标角色任意 permsLevel 成功绑定 正常路径 P0 H-1 修复:DEVELOPER 豁免
TC-0713 MinPermsLevel=MaxInt64 的 MEMBER 不被误阻断 caller=MEMBER, MinPermsLevel=math.MaxInt64(未持角色) 不触发 "不能分配权限级别高于自身" 错误 分支覆盖 P0 H-1 修复:sentinel 值语义

H-2/H-3 gRPC GetUserPerms 状态+成员校验

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0700 冻结用户 (Status=Disabled) GetUserPerms 请求冻结账号 gRPC PermissionDenied,msg 含"冻结" 安全 P0 H-2 修复:对齐 VerifyToken 的 StatusEnabled 判定
TC-0701 非产品成员 启用用户但非目标产品成员 gRPC PermissionDenied,msg 含"成员" 安全 P0 H-2 修复:MemberType=="" 拒绝
TC-0702 DEV 部门但产品成员被禁用 dept.DeptType=DEV & member.Status=Disabled gRPC PermissionDenied 安全 P0 H-2+H-3 修复:DEV 部门不再旁路已禁用成员校验
TC-0703 启用 ADMIN 成员(正向回归) 正常启用成员,产品存在权限 成功,返回 MemberType=ADMIN 且 Perms 含已配置项 正常路径 P0 H-2 修复后正常路径未被误伤
TC-0704 Loader 层:DEV 部门 + 产品成员禁用 DEV 启用,member.Status=Disabled UserDetails.MemberType="",Perms=[] 安全 P0 H-3 修复:禁用成员走入 MemberType 清空分支后不再命中全量权限

M-2 批量删除回归

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0707 DeleteByUserIdAndRoleIdsTx 批量删除 插入 3 条 (user, roleX),批量删除其中 2 条 仅保留未被删除的 1 条 正常路径 P0 M-2:循环 DELETE → 批量 IN
TC-0708 批量删除空列表为 no-op roleIds=[] 无任何删除,原记录保留 边界 P0 M-2:空集合保护
TC-0709 批量删除仅作用于指定 userId 同 roleId 下两个 user,仅删 user1 user2 的绑定不受影响 约束 P0 M-2:WHERE userId 严格约束

M-3 SuperAdmin 在产品上下文只返回该产品角色

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0181 超管在产品上下文查 userDetail 用户同时持有 test_product 与 other_product 角色 resp.RoleIds 只含 test_product 的 roleIds 越权/隔离 P0 M-3 修复:不再跨产品返回

M-4 FindRoleIdsByUserIdForProduct 过滤 r.status=1

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0706 同一产品下同时存在启用/禁用角色 user 绑定启用+禁用 2 个角色 仅返回启用角色的 id 安全/过滤 P0 M-4 修复:SQL 加入 r.status=1

M-5 UpdateDept 乐观锁 + 精准缓存清理

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0105 DeptType/Status 变更仅清自己的成员缓存 变更部门类型 UpdateWithOptLock 被调用,不再对子部门做 FindIdsByDeptId 级联清理 正常路径 P1 M-5 修复:不再级联
TC-0714 DeptType/Status 未变更时不清缓存 只改 name 无 Clean 调用 分支覆盖 P1 M-5:unchanged 分支
TC-0715 乐观锁冲突返回 ErrConflict UpdateWithOptLock 返回 0 行 返回 409/Conflict 并发 P0 M-5:版本号冲突

M-6 JWT Claims.Perms 已移除

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0716 access token payload 中不得含 perms 生成 access token 后 base64 解码 payload JSON 中不存在 "perms" key 安全 P0 M-6:Dead field 清理

M-11 DeleteDept TOCTOU 修复

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0108 删除不存在的部门 任意不存在的 deptId 返回 404 "部门不存在" 错误路径 P0 M-11:事务内 SELECT FOR UPDATE + 不存在显式报错

L-2 产品/管后登录限流独立桶

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0710 同 IP,产品登录用尽配额后管后登录仍放行 productLoginRL 配额=1 打满,再打 adminLoginRL adminLoginRL 正常放行 1 次 安全 P0 L-2 修复:keyPrefix 区分 product/admin

L-5 Loader 不缓存不存在用户

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0705 Load 不存在用户 userId=999999999 返回空 Username;第二次 Load 行为与首次一致(无缓存污染) 边界/缓存 P1 L-5 修复:!ok 分支不缓存零值

十一、 本轮新增对抗性用例(审计修复回归)

针对 audit-report.md 中 H-4 / M-1 / M-14 / L-3 / L-5 五个关键修复点补充的"攻击性"测试,覆盖:

  • 最后 ADMIN 保护(移除 & 降级、活跃/禁用 ADMIN 计数差异)
  • Logout 接口 tokenVersion 递增 + loader 缓存清理
  • setUserPerms 对产品禁用的拦截
  • updateRole 非超管降低 PermsLevel 的禁止 + 超管例外
  • addMember 对已禁用产品的拦截

M-1 Logout 接口:tokenVersion 递增 + 缓存失效

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0720 正常登出 已登录用户 ctx + WithUserDetails 用户 tokenVersion=0 → 1;重新 Load 时 TokenVersion 已递增 正常路径 P0 M-1:IncrementTokenVersion + UserDetailsLoader.Del
TC-0721 未登录调用 /auth/logout ctx 中无 userDetails 返回 401 "未登录" 错误路径 P0 M-1:未登录兜底
TC-0722 同一用户连续两次 logout 登出两次 tokenVersion 累加至 2 幂等/累加 P1 M-1:每次自增,不被覆盖

H-4 产品最后一个 ADMIN 保护

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0723 移除产品唯一 ADMIN 1 个启用 ADMIN 400 "不能移除该产品的最后一个管理员",ADMIN 仍存在 安全 P0 H-4:CountActiveAdmins<=1 拒绝
TC-0724 有 2 个 ADMIN 时移除其一 2 个 ADMIN 成功删除 1 个,另一个保留 正常路径 P0 H-4:非 last-admin 场景放行
TC-0725 降级产品唯一 ADMIN 为 MEMBER 1 个启用 ADMIN 400 "不能降级该产品的最后一个管理员",MemberType 不变 安全 P0 H-4:updateMember 同逻辑
TC-0726 有 2 个 ADMIN 时降级其一 2 个 ADMIN 成功降级为 MEMBER 正常路径 P0 H-4:非 last-admin 允许
TC-0727 2 个 ADMIN 但只有 1 个启用,降级该启用 ADMIN ADMIN(status=1)+ADMIN(status=2) 400 "不能降级该产品的最后一个管理员" 安全/边界 P0 H-4:CountActiveAdmins 只计 status=1
TC-0728 移除非 ADMIN(MEMBER) 1 个 MEMBER 成功删除,不受 last-admin 保护 正常路径 P1 H-4:仅 ADMIN 触发校验
TC-0729 对禁用产品 addMember product.status=2 400 "产品已被禁用,无法添加成员" 安全 P0 L-5:addMemberLogic 新增 product.Status 校验

L-3 非超管不得降低 PermsLevel

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0730 产品 admin 把 roleA.PermsLevel 从 100 降到 10 AdminCtx,PermsLevel 100→10 403 "非超管不能降低角色的权限级别",DB 保持 100 安全 P0 L-3:caller.IsSuperAdmin=false && new<old
TC-0731 产品 admin 保持或提升 PermsLevel 100→100、100→500 均允许;DB 最终 PermsLevel=500 正常路径 P0 L-3:new>=old 放行
TC-0732 超管降低 PermsLevel SuperAdminCtx,500→10 成功 正常路径 P0 L-3:IsSuperAdmin 绕开
TC-0733 PermsLevel 越界(0/-1/1000/10000) 任意非法 PermsLevel 400 "权限级别必须在 1-999 之间" 边界 P0 L-3 前置校验

M-14 setUserPerms 产品禁用拦截

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0734 产品已禁用 product.status=2 400 "产品已被禁用,无法设置权限" 安全 P0 M-14:新增 product.Status 校验
TC-0735 产品不存在 虚构 productCode 404 "产品不存在" 错误路径 P0 M-14:FindOneByCode ErrNotFound

十二、 本轮新增对抗性用例(审计修复回归 · 第二批)

针对 audit-report.md 中 H-A / H-B / M-B / M-C / L-B / L-C / L-F 七个关键修复点补充的"攻击性"测试,覆盖:

  • SetUserPerms 自我提权前置拦截(RequireProductAdminFor)
  • IncrementTokenVersion 模型层原子递增 + 返回值 = DB 持久化值 + 缓存强制失效 + 并发唯一性
  • /auth/refreshToken/auth/logout 接入 TokenOpLimiter,限流后不得再递增 tokenVersion,且按 userId 隔离
  • jwtauthMiddleware 校验顺序:TokenVersion 失效优先于 ProductStatus / MemberType
  • 产品端登录用户名枚举防护(dummy bcrypt + 统一错误文案 + ip:username 限流 key)
  • UpdateUser 跨部门转移 DeptPath 前缀校验(DEVELOPER 不得把目标挪出自身子树)

H-A SetUserPerms 调用者必须是同产品 ADMIN(或超管)

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0743 普通 MEMBER 给自己授权 MemberCtx, targetUserId=self 403 "需要产品管理员权限",DB 中 userperm 无任何写入 安全 P0 H-A:RequireProductAdminFor 前置拦截 self-escalation
TC-0744 DEVELOPER 调用者(非 ADMIN)操作他人 DeveloperCtx, targetUserId=other 403 "需要产品管理员权限",DB 无写入 安全 P0 H-A:DEVELOPER 也必须被拦截(非仅 self 场景)
TC-0745 同产品 ADMIN 操作合法 MEMBER 目标 AdminCtx, targetUserId=member 200 OK,userperm 插入成功 正常路径 P0 H-A:修复后 admin 正向通路仍通畅

H-B IncrementTokenVersion 原子递增 + 缓存失效 + 并发唯一

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0736 单次 Increment 返回值 == DB 持久值 初始 tokenVersion=v0 返回值 = v0+1 == DB 读取值 正常路径 P0 H-B:LAST_INSERT_ID(tokenVersion+1) 原子自增,不依赖缓存旧值
TC-0737 Increment 后缓存必须被主动清理 先 Load 预热缓存,再 Increment 再次 Load 读到的 TokenVersion 为自增后值(非 stale) 缓存一致性 P0 H-B:事务成功后 DelCacheCtx
TC-0738 10 goroutine 并发自增同一用户 起始 v0,并发 Increment×10 10 次返回值互不重复,最终 DB = v0+10 并发/竞态 P0 H-B:原子 UPDATE,不会丢失更新

L-C /auth/logout 接入 TokenOpLimiter

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0739 quota=2 的 limiter,第 3 次 logout 同一 userId 连续 logout 3 次 前 2 次成功 + tokenVersion+2;第 3 次 429 "请求过于频繁",tokenVersion 不再递增 安全/限流 P0 L-C:超限请求不得进入业务层,否则攻击者可反复"自增搅乱缓存"
TC-0740 限流按 userId 隔离 userA 打满配额后 userB 登出 userB 正常 logout,不受 userA 限流影响 安全/隔离 P0 L-C:限流 key 必须按 userId 分桶

M-B /auth/refreshToken 接入 TokenOpLimiter

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0741 quota=1 limiter,burst 第 2 次 同一 userId 刷新 2 次 第 1 次 200;第 2 次 429,tokenVersion 不得递增(避免攻击者持续废除 refresh token) 安全/限流 P0 M-B:refresh 同样走 TokenOpLimiter
TC-0742 限流按 userId 隔离(productCode 无关) userA(p1) 满额 → userB(p1) 刷新 userB 正常 200 安全/隔离 P0 M-B:key 不含 productCode,不会跨用户误伤

L-B jwtauthMiddleware 校验顺序:TokenVersion 优先

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0749 token.tokenVersion != DB & 产品被禁用 同时具备两个失败条件 返回 401 "令牌已失效"(而非 403 "产品已禁用") 安全/顺序 P0 L-B:TokenVersion 比 ProductStatus 先判,避免客户端看到"用户被踢出"后误以为是产品被禁用
TC-0750 token.tokenVersion == DB,产品被禁用 TokenVersion 对齐,product.status=2 403 "该产品已被禁用" 安全 P0 L-B:TokenVersion 通过后才看 ProductStatus

M-C 产品端登录用户名枚举防护

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0751 用户名不存在 + 任意密码 username="__no_such_user__" 返回与"存在用户但密码错"完全一致的错误文案("用户名或密码错误"),仍会走 dummy bcrypt 耗时 安全/枚举 P0 M-C:用 dummy hash 比对,防止通过响应差异枚举用户名
TC-0752 用户名存在但密码错 存在用户 + 错误密码 同 TC-0751 相同 code、相同文案 安全/对照 P0 M-C:与 TC-0751 联动,断言两条分支对外表现一致
TC-0753 登录限流 key 必须基于 ip:username 同 IP 攻击 userA 耗尽配额 userA 被限流 429,但同 IP 登录 userB 仍放行 安全/限流 P0 M-C:UsernameLoginLimit key = ip:username,避免单 IP 单用户暴力破解拖垮同一 IP 其他用户登录

L-F UpdateUser 跨部门转移的 DeptPath 前缀校验

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0746 DEVELOPER 把成员挪出自己子树 Caller.DeptPath=/100/,targetNewDept.DeptPath=/999/ 403 "无权将用户转移到该部门",DB 保持原 deptId 安全 P0 L-F:strings.HasPrefix(newDept.DeptPath, caller.DeptPath) 前缀校验
TC-0747 DEVELOPER 在自己子树内移动成员 Caller=/100/,newDept=/100/200/ 200 OK,DB 更新 deptId 正常路径 P0 L-F:子树内放行
TC-0748 产品 ADMIN 不受子树限制 AdminCtx 跨任意部门 200 OK 正常路径 P1 L-F:ADMIN 只过 CheckManageAccess,不走子树约束

十三、审计修复回归 — 第四轮 (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)。

H-1 UpdateMember 禁用最后 ADMIN 绕过修复

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0760 保持 memberType=ADMIN 但 status 改为 Disabled 产品唯一 ADMIN,{"memberType":"ADMIN","status":2} 400 "不能降级或禁用该产品的最后一个管理员" 安全 P0 H-1:wasActiveAdmin && !willBeActiveAdmin 覆盖 status 变化
TC-0761 同时降级 + 禁用唯一 ADMIN {"memberType":"MEMBER","status":2} 400 同上 安全 P0 H-1:memberType + status 同时变化
TC-0762 有 2 个 ADMIN 时禁用其一 2 个启用 ADMIN 成功,目标 status=2 正常路径 P0 H-1:非 last-admin 场景放行

H-2 RemoveMember 事务内视图脱钩修复

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0763 移除活跃 ADMIN(事务内用 locked 数据判断) 唯一 ADMIN 400 "不能移除该产品的最后一个管理员" 安全 P0 H-2:locked.MemberType 替代事务外 member.MemberType
TC-0764 移除非 ADMIN 不触发 last-admin 校验 MEMBER 身份 成功移除 正常路径 P0 H-2:locked.MemberType != ADMIN 跳过检查

H-3 CountActiveAdminsTx FOR UPDATE 锁定修复

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0765 CountActiveAdminsTx 返回正确计数 产品下 2 个启用 ADMIN + 1 个禁用 ADMIN 返回 2 功能验证 P0 H-3:SELECT id ... FOR UPDATE 仅计活跃行

H-4 DeleteDept 子部门/用户 TOCTOU 修复

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0766 删除有子部门的部门(FOR UPDATE 锁定读) parentId 指向目标部门的子部门存在 400 "该部门下存在子部门,无法删除" 业务约束 P0 H-4:SELECT id ... FOR UPDATE 子部门存在性锁定读
TC-0767 删除有关联用户的部门(FOR UPDATE 锁定读) deptId 指向目标部门的用户存在 400 "该部门下仍有关联用户,无法删除" 业务约束 P0 H-4:SELECT id ... FOR UPDATE 用户存在性锁定读
TC-0768 CreateDept 父部门 FOR SHARE 锁生效 parentId > 0 事务内对父部门 SELECT FOR SHARE;父不存在则 404 安全 P0 H-4:CreateDept 防并发删除父部门

H-5 + M-15 ChangePassword 限流 + 冻结检查

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0769 ChangePassword 超过 TokenOpLimiter 配额 同一 userId 连续调用超限 429 "操作过于频繁,请稍后再试" 安全/限流 P0 H-5:TokenOpLimiter.Take("chpwd:%d")
TC-0770 冻结用户调用 ChangePassword user.Status=Disabled 403 "账号已被冻结" 安全 P0 M-15:bcrypt 前检查 user.Status
TC-0771 原密码错误时记录日志 错误密码 400 "原密码错误" + 日志含 change-password old-password mismatch 可观测 P1 H-5:失败日志可审计

M-1 UpdateUserStatus 无变化不递增 tokenVersion

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0772 状态无实际变化(已启用→再启用) user.Status=1, req.Status=1 返回成功,tokenVersion 不变,用户不被踢下线 功能 P0 M-1:user.Status == req.Status 时跳过写操作
TC-0773 状态实际变化(启用→冻结) user.Status=1, req.Status=2 成功,tokenVersion+1,用户被踢下线 正常路径 P0 M-1:真实变更时正常递增

M-2 generateRandomHex 熵修复

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0774 appKey 长度=32 hex字符 (16字节) CreateProduct appKey 长度 = 32 功能 P0 M-2:generateRandomHex(16) → 32 hex chars
TC-0775 appSecret 长度=64 hex字符 (32字节) CreateProduct appSecret 长度 = 64 功能 P0 M-2:generateRandomHex(32) → 64 hex chars
TC-0776 初始管理员密码长度=24 hex字符 (12字节) CreateProduct adminPassword 长度 = 24 功能 P0 M-2:generateRandomHex(12) → 24 hex chars,96 bit 熵

M-5 UpdateRole/BindRolePerms 错误处理

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0777 FindUserIdsByRoleId 失败时返回 500 mock DB 错误 500 "角色已更新但缓存刷新失败" 容错 P0 M-5:不再忽略错误

M-6 乐观锁 — UpdateRole / UpdateProduct / UpdateMember

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0778 UpdateRole 乐观锁冲突 并发修改同一角色 409 "数据已被其他操作修改,请刷新后重试" 并发安全 P0 M-6:UpdateWithOptLock WHERE updateTime=?
TC-0779 UpdateProduct 乐观锁冲突 并发修改同一产品 409 同上 并发安全 P0 M-6:UpdateWithOptLock WHERE updateTime=?
TC-0780 UpdateMember 基于事务内 locked 数据更新 正常更新 成功,使用 locked 行数据组装 UPDATE 数据一致 P0 M-6:事务内 FindOneForUpdateTx 结果作为更新基础

M-7 AdminLogin 防用户枚举

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0016 普通用户(非超管)登录管理后台 正确密码 code=401, "用户名或密码错误" 安全 P0 M-7:统一错误消息,不暴露账号状态
TC-0021 冻结用户登录管理后台 正确密码 code=401, "用户名或密码错误" 安全 P0 M-7:冻结状态不暴露
TC-0781 不存在用户登录管理后台响应时间恒定 不存在的用户名 code=401, "用户名或密码错误",执行 dummy bcrypt 安全 P0 M-7:dummy bcrypt 恒时对齐

M-10 VerifyToken 失败日志

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0782 VerifyToken 各失败分支记录日志 invalid token / disabled user / version mismatch 等 日志含 verifyToken fail reason= 可观测 P1 M-10:每个失败分支有 logx.Infof

M-11 gRPC Login 缺 peer 时不跳过限流

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0783 peer.FromContext 失败时仍限流 无 peer 的 ctx 限流 key 使用 "unknown",超限仍返回 ResourceExhausted 安全 P0 M-11:fail-closed 不跳过限流

M-14 IsDuplicateEntryErr 类型断言

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0784 MySQL 1062 错误正确识别 mysql.MySQLError{Number: 1062} IsDuplicateEntryErr 返回 true 功能 P0 M-14:类型断言替代字符串匹配
TC-0785 非 1062 错误不误判 其他 MySQL 错误或普通 error 返回 false 功能 P0 M-14:只匹配 1062

L-1 去重不修改 req 入参

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0786 SetUserPerms 调用后 req.Perms 不变 带重复 permId 的请求 调用后 req.Perms 长度与调用前一致 代码质量 P1 L-1:使用局部变量去重
TC-0787 BindRoles 调用后 req.RoleIds 不变 带重复 roleId 的请求 调用后 req.RoleIds 长度与调用前一致 代码质量 P1 L-1:使用局部变量去重
TC-0788 BindRolePerms 调用后 req.PermIds 不变 带重复 permId 的请求 调用后 req.PermIds 长度与调用前一致 代码质量 P1 L-1:使用局部变量去重

L-2 空 memberType 显式拒绝

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0789 caller.MemberType="" 调用 CheckMemberTypeAssignment 空 memberType 的 caller 403 "缺少产品成员上下文" 安全 P0 L-2:显式分支替代 sentinel 值

十三、 本轮新增对抗性用例(QA 主动补齐 · 第三批)

响应"后续测试建议不应由用户手写"的要求, QA 自主完成以下 7 类共 18 个测试:

  • JWT 鉴权优先级完整矩阵(L-B 延伸, 覆盖 UserDeleted / Frozen / NonMember / SuperAdmin bypass 四维顺序)
  • UpdateDept 真实并发乐观锁(M-5 延伸, 用 goroutine 直打 MySQL, 断言恰好 1 胜 9 冲突)
  • TokenOpLimiter 滚动窗口恢复 + Redis fail-open(L-C 延伸, 冻结时间窗语义与宕机容错契约)
  • Loader singleflight 合并并发 + 缓存命中(L-5 延伸, 用计数包装拦截 FindOne
  • gRPC VerifyToken / GetUserPerms 契约层 fuzz(新增, 覆盖 13+6 个攻击性 payload, 断言 never-panic + 错误码 taxonomy 稳定)
  • handler 薄层 HTTP 契约(新增, logout / refreshToken / changePassword, 冻结参数解析 + 透传协议)

JWT 鉴权优先级完整矩阵(jwtauth_checkorder_audit_test.go

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0754 用户已被删除 + TokenVersion 失配 Username="" + tokenVersion mismatch 401 "用户不存在或已被删除" 安全/顺序 P0 L-B 矩阵:Username empty 必须先于 TokenVersion 裁决, 否则软删除语义泄漏成"登录已失效"
TC-0755 账号冻结 + TokenVersion 失配 + 产品禁用 三重 failing 同时命中 403 "账号已被冻结"(而非 401/ProductDisabled) 安全/顺序 P0 L-B 矩阵:账号级 > 会话级 > 产品级 的优先级契约
TC-0756 TokenVersion OK + 产品启用 + 非超管 + MemberType="" 曾是成员后被移除 403 "您已不是该产品的有效成员" 安全 P0 L-B 矩阵:成员移除后 old token 必须被识别
TC-0757 SuperAdmin + ProductCode + MemberType="" 超管 claim 携带 productCode 200 放行到下游 handler 正常路径 P0 L-B 矩阵:超管 bypass 成员校验不可被移除
TC-0758 Frozen 用户 + TokenVersion 失配(无 ProductCode) 冻结账号 + stale token 403 "账号已被冻结" 安全 P0 L-B 矩阵:不走产品分支时 Status 仍先于 TokenVersion

UpdateDept 真实并发乐观锁(internal/model/dept/updateWithOptLock_concurrent_audit_test.go

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0759 10 goroutine 同时 UpdateWithOptLock 同一行 共享 expectedUpdateTime=t0 恰好 1 成功 + 9 ErrUpdateConflict; DB 里 UpdateTime 被推进, Remark 非初值 并发/竞态 P0 M-5:WHERE updateTime=? + RowsAffected 判定, 挡"无声覆盖"退化

TokenOpLimiter 滚动窗口恢复 + Redis fail-open(internal/logic/auth/logoutRateLimit_audit_test.go

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0790 period=1s quota=1, 打满后 sleep 1.2s 再调 同一 userId 连续 logout 第 1 次 200; 第 2 次 429; sleep 后第 3 次 200 且 tokenVersion=2 安全/限流 P0 L-C:限流必须是滚动窗口, 不能退化成永久 deny
TC-0791 Redis 不可达(127.0.0.1:1 + NonBlock) limit.Take 返回 err logout 仍成功(fail-OPEN), tokenVersion=1 容错契约 P0 L-C:code, _ := 的工程取舍被冻结, 未来改 fail-close 需 code review

Loader singleflight 合并并发(internal/loaders/userDetailsLoader_singleflight_audit_test.go

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0792 50 goroutine barrier 同时 Load 同 userId 缓存已清空 每个 goroutine 都拿到完整数据; FindOne 调用次数 ≤ workers/5, >0 并发/缓存 P0 L-5:singleflight.Group.Do 合并冷启动击穿
TC-0793 首次 Load 后再 20 次 Load 缓存已预热 首次 DB 命中 1 次, 后续 0 次(全部走 Redis cache) 缓存命中 P0 L-5:写 Redis 成功后应走 fast-path 不再打 DB

gRPC 契约层 Fuzz(internal/server/permserver_fuzz_audit_test.go

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0794 VerifyToken 对任意畸形 AccessToken 13 条种子(空串/alg=none/长串/Unicode/控制符)+ 运行期可扩 -fuzz=... never-panic, (resp,nil), resp.Valid=false 协议健壮性 P0 gRPC 契约:畸形令牌不得使服务端返回 err != nil 或崩溃
TC-0795 GetUserPerms 任意 (appKey,appSecret,productCode,userId) 6 条种子(空/不存在/SQL 注入样本/Unicode) 错误码只能在 Unauthenticated/PermissionDenied/InvalidArgument/NotFound/Internal 中 协议健壮性 P0 错误码 taxonomy 冻结, 产品侧权限网关依赖此集合

Handler 薄层契约(internal/handler/auth/*internal/handler/pub/*

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0796 LogoutHandler 无 userDetails ctx 空 ctx 401 "未登录" 契约 P0 handler 正确透传 logic 错误, 不吞成 200/5xx
TC-0797 LogoutHandler 携带合法 ctx 注入 UserDetails 200 + DB 中 tokenVersion=1 契约 P0 handler 必须真正触达 logic, 不能 stub 式伪装成功
TC-0798 ChangePasswordHandler 非法 JSON body {not-json 400, 文案不含业务词"原密码" 契约 P0 httpx.Parse 错误 → 400 而非 500; 不泄露业务语义
TC-0799 ChangePasswordHandler 缺必填字段 {} 400, 文案点名 oldPassword/newPassword 契约 P0 goctl required/optional 标注防退化
TC-0800 RefreshTokenHandler 缺 Authorization 无 header 401 或 400, 文案不含 sql/redis 契约 P0 handler 错误文案不得泄露实现细节
TC-0801 RefreshTokenHandler 非法 bearer Bearer garbage.token.value 401(绝不 500/200/panic) 契约 P0 refresh token 畸形时等价于未登录

十四、 本轮新增对抗性用例(QA 主动补齐 · 第四批 / 审计报告修复回归)

本批与最新的 audit-report.md 逐项对齐。每条修复的高/中/低风险点都挂一条或一组独立 TC, 断言 修复后的预期行为(不是源码当前的观测),一旦未来有人把修复改回旧路径,本批用例立刻红。

覆盖域:

  • H-1 IncrementTokenVersionIfMatch 原子 CAS + logic 层并发刷新胜负 + 缓存一致性
  • H-2 / M-7 gRPC Refresh / Verify IP 级限流 + extractClientIP 端口剥离契约
  • H-3 BindRoles 等级 >= 护栏(等级平行时亦拒绝赋权)
  • H-4 UpdateUserdeptId 设为 0 需 ADMIN / 超管权限
  • L-1 CreateUser 未显式指定时 mustChangePassword 默认为 1(强制首次改密)
  • L-4 CheckManageAccess / checkPermLevel 面对 DB 瞬时错误必须 fail-close → 500,而非 "没有角色 → 403"
  • M-3 UserDetailsLoader 负缓存 sentinel,保证大量携带"已删除用户 token"的请求不再反复击穿 DB
  • M-5 CreateProduct 并发冲突(1062 唯一键)必须映射为 409,而非 500 + 脆弱字符串匹配
  • M-6 SyncPerms 事务内锁产品 + 事务内读 perm map + 入参去重 + 1062 → 409
  • M-B HTTP 路由 /api/auth/refreshToken 必须挂载 RefreshTokenRateLimit(路由静态 wiring + 行为双重验证)

H-1 CAS 会话劫持防线(internal/model/user/incrementTokenVersionIfMatch_audit_test.go + internal/logic/pub/refreshTokenCas_audit_test.go

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0802 expected == DB.tokenVersion expected=5, DB=5 返回 6;DB 落盘 6 正常路径 P0 CAS 成功分支
TC-0803 expected != DB.tokenVersion expected=9, DB=10 ErrTokenVersionMismatch;DB 零副作用 安全/并发 P0 会话劫持窗口拦截
TC-0804 用户不存在 已删除:M-8 取消模型内 FindOne 预检,"CAS 未命中 = ErrTokenVersionMismatch"为最新契约;用户状态分支改由上游 UserDetailsLoader.Load 的 status 字段分流
TC-0805 8 goroutine 同时 CAS 同 expected N=8 恰好 1 成功 + 7 ErrTokenVersionMismatch;DB tokenVersion 只递增 1 并发/竞态 P0 原子性外部可观察证据
TC-0806 成功后 id-key / username-key 缓存一致性 CAS→1 再读两路都看到 1(非 stale 0) 缓存 P0 防 middleware 读 stale tokenVersion 放行旧 token
TC-0812 logic 层 6 goroutine 并发 RefreshToken 同一旧 rt N=6 1 成功 + 5 × 401 "登录状态已失效";DB 递增 1 并发/协议 P0 H-1 纵深,覆盖 logic 层分支到 CodeError

H-2 + M-7 gRPC 限流 + client IP 剥端口(internal/server/grpc_rate_limit_audit_test.go

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0828 同 IP 两次 gRPC RefreshToken,quota=1 peer 10.1.2.3:1111110.1.2.3:22222 第 1 次 Unauthenticated(业务放行);第 2 次 ResourceExhausted + "过于频繁" 安全/限流 P0 H-2:端口变化不得绕过限流
TC-0829 同 IP 两次 gRPC VerifyToken,quota=1 peer 10.9.8.7:3000110.9.8.7:30002 第 1 次 Valid=false + nil err;第 2 次 ResourceExhausted 安全/限流 P0 VerifyToken 作为 token oracle 必须受限流保护
TC-0830 extractClientIP 对 "host:port" 剥离 192.168.0.1:54321 返回 192.168.0.1;无 peer 时 error 契约 P0 M-7:剥端口契约不得回退
TC-0831 gRPC refresh 成功后重放旧 rt(换端口) quota 宽松 第 2 次 Unauthenticated + "登录状态已失效" 安全/并发 P0 H-1 + M-7 纵深交叉

H-3 BindRoles 等级 >= 护栏(internal/logic/user/bindRolesEqualLevel_audit_test.go

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0813 MEMBER 调用者给他人赋予 "与自己 permsLevel 相同" 的角色 caller level=50, role level=50 403(等级不允许);DB 的 sys_user_role 关系无变化 安全/越权 P0 GuardRoleLevelAssignable>= 防自等升权

H-4 UpdateUser deptId=0 门禁(internal/logic/user/updateUserDeptZero_audit_test.go

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0814 DEVELOPER 将他人 deptId 置 0 caller=DEVELOPER 403;目标 deptId 不变 安全/越权 P0 防"把用户挪出部门树以逃出管理视野"
TC-0815 MEMBER 将他人 deptId 置 0 caller=MEMBER 403;目标 deptId 不变 安全/越权 P0 同上
TC-0816 产品 ADMIN 将他人 deptId 置 0 caller=ADMIN 200;目标 deptId=0 正常路径 P1 合法操作不被误伤
TC-0817 SuperAdmin 将他人 deptId 置 0 caller=SuperAdmin 200;目标 deptId=0 正常路径 P1 顶级权限链路通畅

L-1 CreateUser 默认强制首次改密(internal/logic/user/createUserMustChangePwd_audit_test.go

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0818 SuperAdmin 创建用户未显式指定 mustChangePassword req 缺字段 DB 落盘 MustChangePassword=1 默认值/安全 P1 默认 Yes 才能保证账号发出后立刻被改密

L-4 checkPermLevel fail-close(internal/logic/auth/checkPermLevelFailClose_audit_test.go

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0819 DB 层瞬时错误(mock errors.New("boom") mock 抛通用 err CodeError.Code == 500,不是 403 鲁棒性/安全 P0 禁止把 DB 故障曲解为"没角色 → 403"
TC-0820 DB 返回 sqlx.ErrNotFound mock sqlx.ErrNotFound 403(保留原业务语义) 分支区分 P0 只有真正"无角色"才 403,保证可审计

M-3 UserDetailsLoader 负缓存(internal/loaders/userDetailsLoader_negativeCache_audit_test.go

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0821 同一"不存在 userId" 第 2 次 Load 首次写入 sentinel 后再 Load 第 2 次 0 次 DB 调用;sentinel 持有 negativeCacheTTL 防 DoS/缓存 P0 携带已删除用户 token 的请求不再反复击穿 DB
TC-0822 负缓存不登记到 userIndex/productIndex 查不存在用户后观察集合 sentinel 不进 Clean 索引,避免误伤 缓存一致性 P0 防 Clean 误删合法 key 或污染统计
TC-0823 50 并发 Load 同一不存在 userId singleflight + 负缓存协同 最终 Redis key = 负缓存 sentinel;无 panic 并发/缓存 P0 防并发惊群 + 负缓存收敛

M-5 CreateProduct 并发唯一键冲突(internal/logic/product/createProductConflict_audit_test.go

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0827 mock InsertWithTx 返回 mysql error 1062,message 不含 "uk_code" &mysql.MySQLError{Number:1062,Message:"generic"} 返回 response.ErrConflict(409) 错误映射 P0 去掉脆弱 strings.Contains 依赖,靠 mysql.MySQLError.Number 判定

M-6 SyncPerms 事务一致性 + 409(internal/logic/pub/syncPermsConflict_audit_test.go + 基础设施 internal/model/perm/findMapByProductCodeWithTx_audit_test.go + internal/model/product/lockByCodeTx_audit_test.go

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0807 FindMapByProductCodeWithTx 与非事务版返回等价 Tx + 产品有启/禁用权限 两次调用数据一致;仅 status=1 被返回 一致性 P0 基础设施:事务内读 perm map
TC-0808 产品无权限时返回 空集 非 nil 空 map(防 upstream NPE) 健壮性 P1 空产品语义固定
TC-0809 LockByCodeTx 对存在 code 返回完整行 Tx 内 SELECT ... FOR UPDATE 行数据完整 正常路径 P0 基础设施:事务内锁产品行
TC-0810 对不存在 code Tx sqlx.ErrNotFound 分支 P0 让 logic 层分辨"产品不存在" vs "DB 错误"
TC-0811 两个事务同时锁同一行 并发 LockByCodeTx 后者被阻塞,前者 commit 后才继续 并发/锁 P0 实证 FOR UPDATE 的行级锁语义
TC-0824 / TC-0825 1062 映射成 409 / logic 映射 409→HTTP 409 已删除:H-3 的 LockByCodeTx FOR UPDATE 已经串行化并发同步,1062 在新架构下不可达;tx 内任何未命名错误统一为 SyncPermsError{500},409 重试契约随之取消
TC-0826 同一 perm code 在 req 中重复 perms = [A, A] 落盘仅 1 条(入参内部去重) 防自伤 P0 入参级去重,避免 tx 内自撞 UNIQUE

M-B HTTP /refreshToken 中间件挂载(internal/handler/refreshTokenRouteWiring_audit_test.go

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0832 静态 wiring 检查:routes.go 中 /auth/refreshToken 所在 rest.WithMiddlewares(...) 块必须包含 serverCtx.RefreshTokenRateLimit 正则匹配 命中 架构/wiring P0 防有人把中间件从路由剥离而忘了通知 QA
TC-0833 行为验证:构造等价中间件链(quota=1),同 IP 连打 2 次;再换 IP 打 1 次 RemoteAddr 三个样本 首次放行(业务层 401);同 IP 第 2 次 Code=429 "过于频繁";不同 IP 不受影响 安全/限流 P0 与 wiring 正交交叉验证,限流真实生效且按 IP 隔离

十五、 本轮新增对抗性用例(QA 主动补齐 · 第五批 / 审计报告第 6 轮修复回归)

本批与 2026-04-19 第 6 轮 audit-report.md 逐项对齐。每条修复均挂一条或一组独立 TC, 断言 修复后的预期行为(不是源码当前的观测)。任何人只要把修复改回旧路径,相应用例立刻 FAIL。

覆盖域:

  • H-1 AdminLogin 限流 key 使用 admin:<clientIP>:<username> 双维,换 IP 不得被同一用户名上一次的配额连带锁死
  • H-2 ValidateProductLogin 无条件 bcrypt + 冻结/超管状态只在密码正确后才披露(消除账号存在性 & 冻结状态 oracle)
  • H-3 SyncPermsService 事务内 LockByCodeTx + FindMapByProductCodeWithTx 串行化同 product 并发同步
  • M-1 UpdateDeptLogic 改用 CleanByUserIds 批量清缓存;FindIdsByDeptId 失败仅 Errorf 不再吞错且不返回 500
  • M-2 ProductList / ProductDetail / DeptTree 三个接口对非超管实施行/资源级访问控制
  • M-4 BindRolePerms / UpdateRole post-commit 缓存获取失败不再映射 500(degraded 成功)
  • M-5 CheckManageAccess 支持 WithPrefetchedTarget option,避免单次请求内重复 FindOne
  • M-6 ExtractClientIP 解析 X-Forwarded-For 首段 + net.ParseIP 合法性校验 + behindProxy=false 忽略请求头
  • M-8 IncrementTokenVersionIfMatch 新签名 (ctx, id, username, expected),调用方透传 username 省掉多一次 FindOne
  • L-5 CountOtherActiveAdminsTx 新方法:排除目标自己后计数,正/反向用例均匹配
  • L-4 (复盘第 5 轮) checkPermLevel 对 DB 瞬时错误 fail-close 500(已验证)
  • 新增负面契约 H-1/H-2 回归:frozen + wrong password 不得返回 403、远端换 IP 不得被上一次 per-username 计数锁死

H-1 AdminLogin 限流按 IP+username 双维(internal/logic/pub/adminLoginIpLimit_audit_test.go

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0834 相同 IP + 相同 username 连续打满 quota clientIP=1.2.3.4, username=superA,连打 >quota 次错误密码 超限后返回 429 "登录尝试过于频繁,请5分钟后再试",同 IP 下同 username 不再放行 安全/限流 P0 H-1:修复后 key=admin:<ip>:<u>;按 quota 阈值打入,需命中 429
TC-0835 同 username 但换远端 IP clientIP=1.2.3.4 打满后,换 clientIP=5.6.7.8 继续 不同 IP 分别计数,换 IP 仍应触达 bcrypt → 进入下游业务断言(此处为密码错误 401),而非继承上一桶 429 安全/限流 P0 H-1:确认 key 含 IP 维度,远端"任何 IP"都能永久锁死的攻击路径被阻断
TC-0836 clientIP 缺失(未挂 RateLimit 中间件) clientIP 未注入 ctx key 退化为 admin:unknown:<u>,仍能正常限流到共享桶,不得直接 panic 或跳过限流 安全/鲁棒 P0 H-1:fail-closed 兜底;未来删除中间件只会退化 key 不会绕过
TC-0837 managementKey 无效路径不得消耗 username 配额 任意密码,managementKey 错误 401 "managementKey无效";同 username 立刻换 managementKey 正确再来仍有完整 quota 安全 P1 H-1:managementKey 校验在限流 Take 之前,防匿名攻击者只靠错 key 就把配额打满

H-2 ValidateProductLogin 恒时 bcrypt & 延迟披露状态(internal/logic/pub/loginServiceConstantTime_audit_test.go

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0838 用户冻结 + 错误密码 status=2 的用户,密码错误 401 "用户名或密码错误"(不能是 403 "账号已被冻结") 安全/侧信道 P0 H-2:账号存在性 + 冻结状态必须在密码正确之前完全不可观察
TC-0839 用户冻结 + 正确密码 status=2 的用户,密码正确 403 "账号已被冻结"(奖励性披露) 正常 P0 H-2:只有拿到密码后才披露状态,仍保留业务可见性
TC-0840 超管走产品端登录 + 错误密码 IsSuperAdmin=1,密码错误 401 "用户名或密码错误"(不得提前暴露"超管"身份) 安全/侧信道 P0 H-2:超管状态同样延迟披露
TC-0841 超管走产品端登录 + 正确密码 IsSuperAdmin=1,密码正确 403 "超级管理员不允许通过产品端登录,请使用管理后台" 正常 P0 H-2:披露顺序反转后的正面路径仍保持原有业务语义
TC-0842 用户名不存在 不存在用户名 + 任意密码 401 "用户名或密码错误",走 dummy bcrypt 恒时对齐 安全/枚举 P0 H-2:沿用 dummy hash 路径,不被 H-2 新顺序破坏

H-3 SyncPermsService 事务内锁产品 & 事务内读 perm map(internal/logic/pub/syncPermsTxLock_audit_test.go

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0843 mock 断言事务内必调用 LockByCodeTx 1 次正常 sync LockByCodeTx 在 tx 内被调用过且先于 FindMapByProductCodeWithTx 架构 P0 H-3:锁必须落在 tx 内,顺序固定
TC-0844 mock LockByCodeTx 返回 sqlx.ErrNotFound 在 tx 内返回 NotFound SyncPermsError.Code == 404,文案 "产品不存在" 分支 P0 H-3:tx 内识别产品被删 → 404
TC-0845 mock LockByCodeTx 返回通用 error boom SyncPermsError/500 包裹;不泄露原始 driver 错误 容错 P1 H-3:非 NotFound 错误必须回滚为 500

M-1 UpdateDept 批量 Clean + 错误不再吞掉(internal/logic/dept/updateDeptCleanBatch_audit_test.go + internal/loaders/userDetailsLoaderCleanByUserIds_audit_test.go

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0846 CleanByUserIds 批量清理多用户缓存 预埋 3 用户各 2 产品缓存 Redis 中 6 条 ud:userId:productCode + 3 条 ud:idx:u:* 均被删除 正确性 P0 M-1 基础设施:SUNION + 批 DEL 必须覆盖所有索引
TC-0847 CleanByUserIds 空 ids 切片 [] 立即返回,不 panic,不调用 Redis 边界 P1 M-1:防未来调用方传空列表打空 RTT
TC-0848 UpdateDept 改 deptType 时调 CleanByUserIds mock: FindIdsByDeptId → [100,101],断言 CleanByUserIds 路径(通过 mock 的 FindIdsByDeptId 期望 +真实 loader 执行) 无错误返回;FindIdsByDeptId 被调用恰好 1 次 行为 P0 M-1:UpdateDept 在变更时才触达用户列表
TC-0849 UpdateDeptFindIdsByDeptId 失败 mock 返回 err 返回 nil(不是 500);旧权限缓存 TTL 兜底 容错 P0 M-1:修复后的 degraded 成功语义

M-2 ProductList / ProductDetail / DeptTree 访问控制(internal/logic/product/productListAccessControl_audit_test.gointernal/logic/product/productDetailAccessControl_audit_test.gointernal/logic/dept/deptTreeAccessControl_audit_test.go

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0850 MEMBER 调 ProductList caller.ProductCode=pA 仅返回 pA 一条(即使 DB 内有 pB、pC) 安全/访问控制 P0 M-2:非超管只见自己产品
TC-0851 MEMBER 调 ProductList 且 ProductCode=="" 游离 MEMBER 返回空列表 Total=0, List=[] 边界 P0 M-2:无 productCode 时降级为 0 条
TC-0852 MEMBER 调 ProductDetail 查他产品 目标 id 属于 pB 404 "产品不存在"(不暴露存在性) 安全/枚举 P0 M-2:区分开"存在但无权"会被当 oracle
TC-0853 MEMBER 调 ProductDetail 查自己产品 目标 id 属于 pA 200 OK,AppKey 字段为空(保持原 AppKey-hidden 语义) 正常路径 P0 M-2:字段级脱敏不被取消
TC-0854 超管调 ProductDetail 任意 id 200 OK + AppKey 可见 正常路径 P1 M-2:超管路径不受访问控制影响
TC-0855 MEMBER 调 DeptTree DeptPath="/1/2/" 返回树中 Path 前缀匹配的子树;父部门/兄弟部门不可见 安全 P0 M-2:按 DeptPath 剪枝
TC-0856 MEMBER 调 DeptTree 且 DeptPath="" 游离成员 返回空切片 [] 边界 P0 M-2:无 DeptPath 降级空树
TC-0857 ADMIN 调 DeptTree 产品 ADMIN 返回完整树(ADMIN fullAccess) 正常路径 P1 M-2:ADMIN 保留组织视图

M-4 BindRolePerms/UpdateRole 缓存获取失败降级(internal/logic/role/postCommitCacheDegrade_audit_test.go

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0858 BindRolePerms:事务 OK,FindUserIdsByRoleId 返回 err mock 返回 nil(200)——不再映射 500 错误映射 P0 M-4:degraded 成功;客户端不应重试
TC-0859 UpdateRole:事务 OK,FindUserIdsByRoleId 返回 err mock 返回 nil(200) 错误映射 P0 M-4:同上

M-5 CheckManageAccess Prefetched(internal/logic/auth/checkManageAccessPrefetched_audit_test.go

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0860 传入 prefetched target,不再查 FindOne caller=MEMBER,prefetched.DeptId 合法 SysUserModel.FindOne 次数 = 0;业务结果同无 option 版本 性能/契约 P1 M-5:避免重复 FindOne
TC-0861 prefetched target.Id 与参数 targetUserId 不一致 被 defensive 忽略 依然触发一次 FindOne 真实查询(option 失效) 安全 P1 M-5:prefetched 自洽校验,不让调用方传错 id 绕过访问控制

M-6 ExtractClientIP XFF/合法性/behindProxy(internal/middleware/ratelimitMiddlewareXff_audit_test.go

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0862 behindProxy=true + X-Forwarded-For: 1.1.1.1, 2.2.2.2 首段合法 返回 1.1.1.1 契约 P0 M-6:XFF 首段优先
TC-0863 behindProxy=true + X-Forwarded-For 全非法 + X-Real-IP: 10.0.0.1 XFF=garbage, abc,XRI 合法 fallthrough 到 10.0.0.1 契约 P0 M-6:非法段跳过后走 XRI
TC-0864 behindProxy=true + 两头均空 无 XFF / 无 XRI 回落到 RemoteAddr 剥端口后的 host 容错 P1 M-6:降级路径
TC-0865 behindProxy=true + X-Forwarded-For: " 3.3.3.3 " (空白) 首段带空白 返回 3.3.3.3(trim 后合法) 边界 P1 M-6:trim 后再解析
TC-0866 behindProxy=false + XFF=1.1.1.1 RemoteAddr=5.5.5.5:8080 忽略 XFF,返回 5.5.5.5 安全/伪造 P0 M-6:不信任客户端注入的头部

M-8 IncrementTokenVersionIfMatch 签名(已落地·契约冻结)

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0867 透传 username 与 DB 一致 CAS 正常 成功且 username-key 缓存也失效(沿用 TC-0806 验证) 契约 P0 M-8:签名扩展不破坏既有 CAS 语义

L-5 CountOtherActiveAdminsTx(internal/model/productmember/countOtherActiveAdminsTx_audit_test.go

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0868 产品内 3 个 active admin,排除其中 1 excludeId=第二个 admin 返回 2 计数 P0 L-5:排除目标后正确计数
TC-0869 唯一 active admin,排除他自己 excludeId=唯一 admin 返回 0 → 上层识别为"最后一个" 语义 P0 L-5:removeMember/updateMember 据此防"降级/移除最后一个 admin"
TC-0870 存在 1 个 active + 1 个 disabled admin excludeId=active 返回 0(disabled 不计入) 语义 P0 L-5:仍需状态=enabled 才算

4.14 第 6 轮过时用例按新契约重写(2026-04-19 · QA 清理补充)

以下 TC 是在清理阶段按 新契约 重写,用于替代被删除的旧用例(见 §4.12、§4.13 各行"已删除"备注)。

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0871 超管调 ProductList 走分页路径 ctx=SuperAdmin, page/pageSize=1/20 调用 FindList(1,20),返回列表且每条 AppKey 原样可见 正常路径 P0 M-2:取代旧 TC-0079/0087,钉死"超管路径 != 非超管路径"分叉
TC-0872 ProductDetail FindOne 失败 DB 返回 err 统一返回 CodeError{404,"产品不存在"},文案与"他人产品 → 404"完全一致 异常路径 P0 M-2:取代旧 TC-0085,钉死"无差别 404"避免被用作存在性 oracle

五、第 7 轮审计驱动测试(2026-04-19 · QA 新增)

本轮围绕 audit-report.md 中 H-1 / H-3 / H-4 / M-1 / M-2 / M-3 / M-4 / L-3 / L-5 / L-6 的修复逐条建立回归锚点。 所有新用例均在"真实 DB + Redis + 原子语义"的一等场景下验证,严禁仅靠 mock 对实现做同义复写。 对既有用例的兼容性调整(两处 bindRoles 测试 + RefreshToken_UserDeleted)见 §5.9。

5.1 M-4 FetchInitialCredentials 一次性凭据取回(internal/logic/product/fetchInitialCredentialsLogic_audit_test.go

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0901 happy path:超管用有效 ticket 取回初始凭据 superAdmin ctx + 合法 ticket 返回 AppKey/AppSecret/AdminUser/AdminPassword;AppSecret 与 DB 中 bcrypt 匹配 正常路径 P0 M-4:凭据落地 Redis 后端,响应体不再明文外泄
TC-0902 相同 ticket 二次消费 同上 ticket 第二次取 第二次 404 "凭据票据不存在或已被消费" 一次性 P0 M-4:Redis.GetDelCtx 原子 GET+DEL
TC-0903 ticket 为空 ticket="" 400 "ticket 不能为空" 异常路径 P0 M-4:入参校验
TC-0904 ticket 未知 随机 64 字符 ticket 404 "凭据票据不存在或已被消费" 异常路径 P0 M-4:无存在性差异化,避免枚举 oracle
TC-0905 非超管调用 ADMIN ctx 403 权限 P0 M-4:RequireSuperAdmin 生效
TC-0906 未登录调用 context.Background() 401/403 权限 P0 M-4:无 UserDetails 时拒绝
TC-0907 Redis 中 payload 被人为破坏 手工写入非 JSON 字符串 500 "凭据数据异常" 并删除该 key 健壮性 P1 M-4:防腐败数据长留
TC-0908 Redis key 结构正确 观察实际 key pm:initcred:{ticket};TTL ≤ 300s 契约 P1 M-4:运维可定位
TC-0909 TTL 与响应 expiresAt 一致 观察返回的 credentialsExpiresAt Redis TTL == Unix(expiresAt)-now (±5s) 契约 P1 M-4:客户端过期提示与后端一致
TC-0910 并发消费同一 ticket 32 goroutine 同时 Fetch 仅 1 个成功,其余 31 个返回 404 并发 P0 M-4:GetDelCtx 原子性抗竞态
TC-0911 CreateProductResp JSON 不含 appSecret/adminPassword marshal resp → json 字段 appSecret/adminPassword 不出现 契约 P0 M-4:响应体永不明文
TC-0912 CreateProductResp 必含 credentialsTicket + credentialsExpiresAt marshal resp 两字段均非空/正数 契约 P0 M-4:新的获取链路必备字段

5.2 M-1 / H-1 / L-3 / L-6 Loader 契约(internal/loaders/userDetailsLoader_contract_audit_test.go

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0913 用户不存在 → (ud, nil)ud.Username == "" userId=999999999 无 err,Username 空,caller 自行映射 401 契约 P0 M-1:401 vs 503 区分
TC-0914 新建用户刚落地 CreateUser 后立刻 Load 新 username 读到真实 user,不命中负缓存哨兵 契约 P0 L-6:CreateUser 反向清除负缓存
TC-0915 partial load 失败(幽灵 deptId) 人为把 user.deptId 改成不存在 返回 ud(含错误子加载),但不写 5 分钟正缓存 契约 P0 M-1/L-3:局部失败不污染主缓存
TC-0916 全绿 MEMBER 正路径 正常 user + product + 无角色 loadOk=true,命中 5 分钟正缓存 契约 P0 H-1:保证好路径仍能缓存

5.3 M-2 UpdatePassword/UpdateStatus RowsAffected(internal/model/user/updatePasswordStatus_rowsaffected_audit_test.go

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0924 UpdatePassword:FindOne 填缓存后行被并发删除 直接 SQL 删行绕过缓存失效 ErrUpdateConflict,不得静默成功 并发/TOCTOU P0 M-2:RowsAffected=0 必须升格
TC-0925 UpdatePassword:正常写入 存在且未并发 持久化 + tokenVersion 递增 正常路径 P0 M-2:hot path 不回归
TC-0926 UpdatePassword:user 不存在 id=非法 ErrNotFound(FindOne 先挂) 异常路径 P0 M-2:直接失败
TC-0927 UpdateStatus:行被并发删除 同 TC-0924 手法 ErrUpdateConflict 并发/TOCTOU P0 M-2:对称覆盖
TC-0928 UpdateStatus:正常禁用 status=2 持久化 + tokenVersion 递增(用户被踢) 正常路径 P0 M-2:禁用副作用
TC-0929 UpdateStatus:user 不存在 id=非法 ErrNotFound 异常路径 P0 M-2

5.4 M-3 GuardRoleLevelAssignable Fresh Read(internal/logic/auth/guardRoleLevelAssignable_freshRead_audit_test.go

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0930 caller 的 MinPermsLevel 缓存过期(偏高) mock 返回 DB=100,caller 缓存=5,role=50 403(以 DB=100 判,50 ≤ 100 越级) TOCTOU P0 M-3:不得信任缓存高权值
TC-0931 同级分配 DB=50,role=50 403 "不能分配权限级别高于自身的角色(含同级)" 契约 P0 M-3:含同级
TC-0932 严格低一级分配 DB=50,role=51 放行 正常路径 P0 M-3:正值域
TC-0933 SuperAdmin 绕过 DB superAdmin caller 不查 DB,直接放行 正常路径 P0 M-3:短路
TC-0934 Product ADMIN 绕过 DB memberType=ADMIN 不查 DB,直接放行 正常路径 P0 M-3:短路
TC-0935 DEVELOPER 绕过 DB memberType=DEVELOPER 不查 DB,直接放行 正常路径 P0 M-3:短路
TC-0936 caller 在 DB 没有任何角色 FindMin... 返回 ErrNotFound 403 "您没有可分配的角色等级" 异常路径 P0 M-3:fail-close
TC-0937 caller 查询 DB 遇到一般错误 非 ErrNotFound 500(fail-close,不透传原文) 容错 P0 M-3:不泄细节
TC-0938 caller 为 nil(无 UserDetails) ctx 未带 401(未授权) 异常路径 P0 M-3:nil 保护

5.5 H-3 CheckAddMemberAccess 部门链 + 超管防御(internal/logic/auth/checkAddMemberAccess_audit_test.go + internal/logic/member/auditFixes_test.go

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0940 Product ADMIN 拉跨部门树的人 caller path=/100/,target path=/200/201/ 403,错误含 "其他部门" 权限 P0 H-3:ADMIN 不再绕过部门树
TC-0941 Product ADMIN 在自己部门树内 target 在 /100/101/ 放行 正常路径 P0 H-3:正值域
TC-0942 SuperAdmin 跨一切部门 caller superAdmin 放行,不查 dept 正常路径 P0 H-3:短路
TC-0943 自己加自己 target.Id == caller.UserId 放行 正常路径 P0 H-3:self-bypass
TC-0944 caller 没有部门(DeptId=0 或 DeptPath="") 非超管 403 异常路径 P0 H-3:无部门 caller 必须拒绝
TC-0945 target 无部门(DeptId=0) 非自己 403 异常路径 P0 H-3:目标无部门视为不可纳管
TC-0946 ctx 无 caller context.Background() 401 权限 P0 H-3
TC-0947 dept.FindOne 报错 mock 返回 err 403(fail-close,文案不泄细节) 容错 P0 H-3
TC-0948 target 为 nil pass nil 400 BadRequest 契约 P0 H-3
TC-0949 AddMember 集成:跨部门被拒 真实 DB,Product ADMIN 拉树外 403 + 不写 sys_product_member 集成 P0 H-3 端到端
TC-0950 AddMember 集成:target=SuperAdmin superAdmin 被作为 MEMBER 加入 403 "超级管理员" + 不写 sys_product_member 安全 P0 H-3:超管防混入

5.6 H-4 JWT 签名算法断言(internal/logic/auth/parseWithHMAC_audit_test.go

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0951 合法 HS256 + 正确 secret 正常 token 解析成功,claims 可读 正常路径 P0 H-4:good path
TC-0952 alg=none 伪造 手工拼无签名段 拒绝,错误含 "unexpected signing method" 安全 P0 H-4:alg=none 零信任
TC-0953 alg=RS256 但用 HS secret 签名 RSA→HMAC 混淆攻击 拒绝 安全 P0 H-4:算法混淆防御
TC-0954 alg=ES256 header 非 HMAC 族 拒绝 安全 P0 H-4:非 HMAC 一律拒
TC-0955 HS256 但 secret 错 同算法错密钥 拒绝(签名校验失败) 安全 P0 H-4:基础用例
TC-0956 ParseRefreshToken 复用 ParseWithHMAC 伪造 alg=none 拒绝 安全 P0 H-4:上层也防
TC-0957 乱码 token "abc.def" 拒绝(malformed) 容错 P1 H-4
TC-0958 ParseRefreshToken 的 tokenType 校验 AccessToken 类型 拒绝 TokenTypeMismatch 契约 P0 H-4:不被 H-4 修复破坏
TC-0959 合法 HS256 + 非标 typ header typ=JWT-X 解析成功(只严格校验 alg) 兼容 P1 H-4:不过度收紧
TC-0960 回放过期 token exp 已过 拒绝(解析器底层校验) 安全 P0 H-4

5.7 L-5 清理:删除僵尸方法

删除对象 受影响测试 处理
SysPermModel.FindMapByProductCode(非事务) sysPermModel_test.go 改用 FindMapByProductCodeWithTx(在事务上下文中调用);findMapByProductCodeWithTx_audit_test.go 独立验证事务版契约
SysProductMemberModel.FindMapByProductCodeUserIds sysProductMemberModel_test.go 删除 3 个关联测试(TC-xxx 已标记为已删除)
SysProductMemberModel.CountActiveAdmins 无外部调用者,直接删除

5.8 M-1 RefreshToken 用户不存在 vs 账号冻结契约分离

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0029(重写) refreshToken 合法但 userId 不存在 nonExistentUserId 401 "用户不存在或已被删除" 契约 P0 M-1:取代旧 "403 账号已被冻结",前端可正确走"清本地会话+回登录页"

5.9 M-4 Handler 薄层 + 路由 Wiring(internal/handler/product/fetchInitialCredentialsHandler_audit_test.go + internal/handler/fetchInitialCredentialsRouteWiring_audit_test.go

填补 §10.4 中"handler/wiring 未测场景"空白:此前 TC-0901 ~ TC-0912 只在 logic 层覆盖 M-4,但 handler 的解析契约、鉴权中间件挂载、前缀归属都未被钉死。 本组 TC 双通道验证:一端走真实 http.HandlerFunc + Redis 发 HTTP 请求,一端 static-scan routes.go 源码断言 middleware 绑定,互为保险。

TC编号 测试场景 输入 预期结果 类型 优先级 覆盖说明
TC-0961 body 非法 JSON {not-valid-json + superAdmin ctx 400,且文案不含 sql/redis/ticket 关键字 契约/健壮性 P0 M-4:httpx.Parse 错误透传;不泄字段与实现细节
TC-0962 无登录上下文 不注入 UserDetails 401 "未登录" 权限 P0 M-4:handler 自身也必须 fail-close,不依赖 JwtAuth 中间件
TC-0963 非超管 ctx MemberType=ADMIN 403 "仅超级管理员可执行此操作",文案不含 "ticket" 权限/信息泄漏 P0 M-4:RequireSuperAdmin 透传;防 ticket 存在性 oracle
TC-0964 超管 + 空 ticket {"ticket":""} 400,文案含 "ticket" 契约 P0 M-4:入参必填校验
TC-0965 超管 + 未知 ticket 随机字符串 400 "凭证票据无效或已过期",与"过期"共用文案 安全 P0 M-4:防枚举 oracle,与 logic TC-0904 同契约
TC-0966 超管 + 已落地 ticket 手工 SETEX 合法 JSON payload HTTP 200 + 4 字段完整映射;Redis key 被 GetDel 消费 正常路径/一致性 P0 M-4:字段映射正确 + 一次性消费
TC-0967 静态 wiring:JwtAuth 绑定 读取 routes.go 源码 /fetchInitialCredentials 所在 rest.WithMiddlewares 列表含 serverCtx.JwtAuth;prefix=/api/product 回归/静态 P0 M-4:防未来 goctl 覆写丢失中间件
TC-0968 静态反证:不得挂到限流组 读取 routes.go 源码 /fetchInitialCredentials 绝不出现在 AdminLoginRateLimit / ProductLoginRateLimit / RefreshTokenRateLimit / SyncRateLimit 的中间件块内 回归/静态 P0 M-4:防被错迁到无鉴权组

5.10 既有用例兼容性调整(M-3 fresh DB 读)

用例 文件 调整说明
TC-0813 bindRolesEqualLevel_audit_test.go 新增 seedCallerWithRoleLevel 辅助,在 DB 落地真实 caller user + role + user_role;不再仅依赖 UserDetails.MinPermsLevel 直出
TC-0208 bindRolesLogic_test.go 同上,seed caller permsLevel=50 后触发"越级分配 permsLevel=1"断言

上述调整的根因:M-3 修复后 GuardRoleLevelAssignable 强一致从 DB 读取 caller 的 MinPermsLevel,原测试用假 UserId 会命中 ErrNotFound → 403 "您没有可分配的角色等级",与真正要验的"含同级"/"越级"语义错位。