测试范围: API (go-zero REST, 全 POST) + gRPC (status codes) + Model 层 (_gen.go 模板生成 + 自定义方法) + Logic 单元测试 + util 层 + 访问控制 + UserDetailsLoader 测试报告与代码审计详见 test-report.md
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
共 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 | 手写 |
输入: 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
注意: 所有路由统一为 POST 方法,请求参数均通过 JSON Body 传递。
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 |
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=403, "仅超级管理员可通过管理后台登录" | 安全 | P0 | 审计H1修复: 仅超管可登录管理后台 |
| 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=403, "账号已被冻结" | 分支覆盖 | P0 | u.Status!=1 |
| 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: 防用户名枚举爆破 |
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 |
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,"同步权限事务失败") | 事务验证 | P0 | H-05修复: TransactCtx, 错误包装不透传DB错误 |
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 |
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失败 |
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 正向匹配 |
| 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 | POST /api/product/list | 正常分页 | {"page":1,"pageSize":10} |
code=0, total/list | 正常路径 | P0 | productListLogic |
| TC-0080 | POST /api/product/list | 默认分页 | {} |
page=1, pageSize=20 | 分支覆盖 | P1 | NormalizePage默认值 |
| TC-0081 | POST /api/product/list | pageSize超过上限 | {"page":1,"pageSize":500} |
实际pageSize=100 | 边界 | P0 | NormalizePage cap 100 |
| TC-0082 | POST /api/product/list | pageSize=0 | {"page":1,"pageSize":0} |
实际pageSize=20 | 边界 | P1 | NormalizePage<=0→20 |
| TC-0083 | POST /api/product/list | page负值 | {"page":-1,"pageSize":10} |
实际page=1 | 边界 | P1 | NormalizePage<=0→1 |
| TC-0084 | POST /api/product/detail | 正常查询 | {"id":1} |
code=0, ProductItem | 正常路径 | P0 | productDetailLogic |
| TC-0085 | POST /api/product/detail | 不存在 | {"id":9999} |
code=404 | 异常路径 | P0 | FindOne失败 |
| TC-0086 | POST /api/product/list | 非超管AppKey隐藏 | ctx=MEMBER | code=0, 列表中AppKey为空 | 安全 | P0 | H-11修复: AppKey仅超管可见 |
| TC-0087 | POST /api/product/list | 超管可见AppKey | ctx=SuperAdmin | code=0, 列表中AppKey不为空 | 安全 | P0 | H-11修复: 超管可见AppKey |
| TC-0088 | POST /api/product/detail | 非超管AppKey隐藏 | ctx=MEMBER | code=0, AppKey为空 | 安全 | P0 | H-11修复: AppKey仅超管可见 |
| TC-0089 | POST /api/product/detail | 超管可见AppKey | ctx=SuperAdmin | code=0, AppKey不为空 | 安全 | P0 | H-11修复: 超管可见AppKey |
| TC-0090 | POST /api/product/update | updateProduct 非法状态值被拒绝 | status=99 | 400 "产品状态值无效" | 输入校验 | P0 | H-4: 仅允许 1/2 |
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修复后端到端 |
| 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 | POST /api/dept/tree | 正常获取 | {} |
code=0, 树形结构, 含DeptType字段 | 正常路径 | P0 | deptTreeLogic, DeptType映射 |
| TC-0111 | POST /api/dept/tree | 空数据 | 无数据 | code=0, data=[] | 边界 | P1 | 空列表 |
| TC-0112 | POST /api/dept/tree | 孤儿节点 | parentId指向不存在 | 升级为根节点 | 分支覆盖 | P2 | parent不存在 |
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 | 空结果 |
| 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失败 |
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条 |
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 |
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 |
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 透传 |
| 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 |
| 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 |
| 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 |
| 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认证前置 |
| 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 |
| 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 | 两条件同时 |
| 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 | 空 |
| 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 层中的核心共享函数,使用 mock Model 接口进行纯单元测试。
| 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 |
| 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 | 空字符串 |
| 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字段校验 |
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 |
所有 9 个 Model 的
_gen.go均由自定义模板 (cli/goctl/model/) 生成,包含非标准方法(批量操作、事务变体、buildBatchUpdateQuery等)。 以下以 通用测试模式 列出,适用于全部 9 个 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 |
| 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 |
| 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 | 参数计数 |
| 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 |
| 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 |
| 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 |
| 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个变量均修改 |
| 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 不匹配 |
| 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 |
| 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 过滤, 全禁用时返回空 |
| 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 |
| 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 |
| 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 不报错 |
| 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=? 隔离其他产品 |
| 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 | 差量更新依赖此接口 |
| 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] |
| 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 |
| 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不匹配→拒绝 |
| 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 |
| 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空串保护 |
| TC编号 | 测试场景 | 输入 | 预期结果 | 类型 | 优先级 | 覆盖说明 |
|---|---|---|---|---|---|---|
| TC-0505 | 各类型优先级正确 | 全部4种+未知 | SA=0,A=1,D=2,M=3,unknown=MaxInt32 | 白盒 | P0 | switch分支 |
| 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 |
| 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 |
| 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检查 |
| 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 |
| 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 |
| 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 |
| 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仍被忽略 | behindProxy=true, XFF头+无X-Real-IP | 按RemoteAddr限流, XFF不影响 | 安全 | P0 | 仅信任X-Real-IP, 不信任XFF |
| TC-0555 | RemoteAddr无端口格式 | RemoteAddr="1.2.3.4"(无端口) | 返回原始RemoteAddr "1.2.3.4" | 边界 | P1 | SplitHostPort失败→r.RemoteAddr |
新增测试用于验证近期针对
audit-report.md的高/中/低风险项所提交的修复,确保修复行为严格生效且不回归。
| 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 值语义 |
| 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 清空分支后不再命中全量权限 |
| 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 严格约束 |
| TC编号 | 测试场景 | 输入 | 预期结果 | 类型 | 优先级 | 覆盖说明 |
|---|---|---|---|---|---|---|
| TC-0181 | 超管在产品上下文查 userDetail | 用户同时持有 test_product 与 other_product 角色 | resp.RoleIds 只含 test_product 的 roleIds | 越权/隔离 | P0 | M-3 修复:不再跨产品返回 |
| TC编号 | 测试场景 | 输入 | 预期结果 | 类型 | 优先级 | 覆盖说明 |
|---|---|---|---|---|---|---|
| TC-0706 | 同一产品下同时存在启用/禁用角色 | user 绑定启用+禁用 2 个角色 | 仅返回启用角色的 id | 安全/过滤 | P0 | M-4 修复:SQL 加入 r.status=1 |
| 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:版本号冲突 |
| TC编号 | 测试场景 | 输入 | 预期结果 | 类型 | 优先级 | 覆盖说明 |
|---|---|---|---|---|---|---|
| TC-0716 | access token payload 中不得含 perms | 生成 access token 后 base64 解码 payload | JSON 中不存在 "perms" key | 安全 | P0 | M-6:Dead field 清理 |
| TC编号 | 测试场景 | 输入 | 预期结果 | 类型 | 优先级 | 覆盖说明 |
|---|---|---|---|---|---|---|
| TC-0108 | 删除不存在的部门 | 任意不存在的 deptId | 返回 404 "部门不存在" | 错误路径 | P0 | M-11:事务内 SELECT FOR UPDATE + 不存在显式报错 |
| TC编号 | 测试场景 | 输入 | 预期结果 | 类型 | 优先级 | 覆盖说明 |
|---|---|---|---|---|---|---|
| TC-0710 | 同 IP,产品登录用尽配额后管后登录仍放行 | productLoginRL 配额=1 打满,再打 adminLoginRL | adminLoginRL 正常放行 1 次 | 安全 | P0 | L-2 修复:keyPrefix 区分 product/admin |
| 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 对已禁用产品的拦截
| 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:每次自增,不被覆盖 |
| 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 校验 |
| 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 前置校验 |
| TC编号 | 测试场景 | 输入 | 预期结果 | 类型 | 优先级 | 覆盖说明 |
|---|---|---|---|---|---|---|
| TC-0734 | 产品已禁用 | product.status=2 | 400 "产品已被禁用,无法设置权限" | 安全 | P0 | M-14:新增 product.Status 校验 |
| TC-0735 | 产品不存在 | 虚构 productCode | 404 "产品不存在" | 错误路径 | P0 | M-14:FindOneByCode ErrNotFound |