# 权限管理系统 (perms-system-server) — 全路径覆盖测试设计 > 测试范围: API (go-zero REST, 全 POST) + gRPC (status codes) + Model 层 (_gen.go 模板生成 + 自定义方法) + Logic 单元测试 + util 层 + 访问控制 + UserDetailsLoader > 测试报告与代码审计详见 [test-report.md](./test-report.md) --- ## 一、系统架构与逻辑链路 ### 1.1 整体调用链路 ```text 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 权限计算逻辑链路 ```text 输入: 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=0, perms=[], memberType="" | 分支覆盖 | P1 | ErrNotFound→nil | | 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现为必填) | ### 2.1b 管理后台登录 `POST /api/auth/adminLogin` | TC编号 | 接口/方法 | 测试场景 | 输入参数 (JSON) | 预期结果 | 测试类型 | 优先级 | 覆盖说明 | | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | | TC-0500 | POST /api/auth/adminLogin | 超管正常登录 | `{"username":"super","password":"x","managementKey":"valid"}` | code=0, accessToken/refreshToken/userInfo, isSuperAdmin=1, memberType="SUPER_ADMIN", perms为空 | 正常路径 | P0 | adminLoginLogic全路径 | | TC-0501 | POST /api/auth/adminLogin | 普通用户正常登录 | `{"username":"user1","password":"x","managementKey":"valid"}` | code=0, accessToken/refreshToken, perms为空 | 正常路径 | P0 | 非超管也可通过管理后台 | | TC-0502 | POST /api/auth/adminLogin | managementKey无效 | `{"username":"user1","password":"x","managementKey":"wrong"}` | code=401, "managementKey无效" | 安全 | P0 | 第一个校验点 | | TC-0503 | POST /api/auth/adminLogin | managementKey为空 | `{"username":"user1","password":"x","managementKey":""}` | code=401, "managementKey无效" | 安全 | P0 | 空字符串≠config值 | | TC-0504 | POST /api/auth/adminLogin | 用户不存在 | `{"username":"notexist","password":"x","managementKey":"valid"}` | code=401, "用户名或密码错误" | 异常路径 | P0 | ErrNotFound分支 | | TC-0505 | POST /api/auth/adminLogin | 密码错误 | `{"username":"user1","password":"wrong","managementKey":"valid"}` | code=401, "用户名或密码错误" | 异常路径 | P0 | bcrypt比对失败 | | TC-0506 | POST /api/auth/adminLogin | 账号冻结 | status=2用户 | code=403, "账号已被冻结" | 分支覆盖 | P0 | u.Status!=1 | | TC-0507 | POST /api/auth/adminLogin | 不带productCode时perms为空 | 管理后台登录超管 | userInfo.perms为空, memberType="SUPER_ADMIN"(超管标记由Loader自动填充) | 功能验证 | P0 | Load(ctx, uid, "") | | TC-0508 | POST /api/auth/adminLogin | 缺少必填字段 | `{}` | HTTP 400 | 边界 | P1 | httpx.Parse校验 | | TC-0509 | POST /api/auth/adminLogin | SQL注入username | `{"username":"' OR 1=1 --","password":"x","managementKey":"valid"}` | code=401 | 安全 | P0 | 参数化查询 | ### 2.2 刷新Token `POST /api/auth/refreshToken` | TC编号 | 接口/方法 | 测试场景 | 输入参数 (JSON) | 预期结果 | 测试类型 | 优先级 | 覆盖说明 | | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | | TC-0013 | POST /api/auth/refreshToken | 正常刷新 | Header `Authorization: Bearer ` | code=0, 新accessToken, refreshToken原样返回(不重新生成) | 正常路径 | P0 | refreshTokenLogic全路径 | | TC-0014 | POST /api/auth/refreshToken | 不带productCode(回退) | Header Authorization, 无productCode | 使用claims.ProductCode | 分支覆盖 | P1 | productCode=""回退 | | TC-0015 | POST /api/auth/refreshToken | token无效 | Header `Authorization: Bearer invalid` | code=401 | 异常路径 | P0 | ParseRefreshToken失败 | | TC-0016 | POST /api/auth/refreshToken | 用户已删除 | token中userId不存在 | code=403, "账号已被冻结" | 异常路径 | P1 | UserDetailsLoader返回Status=0 | | TC-0017 | POST /api/auth/refreshToken | 账号冻结 | 冻结用户 | code=403 | 分支覆盖 | P0 | Status!=1 | | TC-0018 | POST /api/auth/refreshToken | 超管+productCode(token中已含相同pc) | isSuperAdmin=1, token中productCode=pc, req.ProductCode=pc | refreshToken原样返回, SUPER_ADMIN权限 | 分支覆盖 | P1 | isSuperAdmin分支+productCode不变 | | TC-0514 | POST /api/auth/refreshToken | 尝试切换产品被拒绝 | token中productCode="p1", req.ProductCode="p2" | code=400, "刷新令牌不允许切换产品" | 安全 | P0 | H-02修复: 禁止跨产品切换 | ### 2.3 同步权限 `POST /api/perm/sync` | TC编号 | 接口/方法 | 测试场景 | 输入参数 (JSON) | 预期结果 | 测试类型 | 优先级 | 覆盖说明 | | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | | TC-0019 | 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-0020 | POST /api/perm/sync | 更新已有(名称变更) | 已存在code但name不同 | updated=1 | 正常路径 | P0 | toUpdate→BatchUpdate | | TC-0021 | POST /api/perm/sync | 无变化 | 已存在且name/remark/status均相同 | added=0, updated=0 | 分支覆盖 | P1 | 跳过更新 | | TC-0022 | POST /api/perm/sync | 禁用权限重启 | 已status=2的权限在列表中 | updated=1, status恢复1 | 分支覆盖 | P1 | Status!=1条件 | | TC-0023 | POST /api/perm/sync | 移除不在列表的权限 | DB有多余权限 | disabled>0 | 正常路径 | P0 | DisableNotInCodes | | TC-0024 | POST /api/perm/sync | 空perms数组 | `{"...","perms":[]}` | disabled=全部现有权限数 | 分支覆盖 | P1 | codes空→全部disable | | TC-0025 | POST /api/perm/sync | 验证disabled返回值 | 已知DB有5条,perms仅含2条 | disabled=3 | 功能验证 | P0 | RowsAffected() | | TC-0026 | POST /api/perm/sync | appKey无效 | `{"appKey":"invalid"}` | code=401 | 异常路径 | P0 | FindOneByAppKey失败 | | TC-0027 | POST /api/perm/sync | appSecret错误 | secret不匹配 | code=401 | 异常路径 | P0 | AppSecret比对 | | TC-0028 | POST /api/perm/sync | 产品已禁用 | product.Status!=1 | code=403 | 分支覆盖 | P0 | Status!=1 | | TC-0029 | POST /api/perm/sync | 大批量(1000条) | 1000条perms | added=1000 | 性能 | P2 | BatchInsert性能 | | TC-0532 | POST /api/perm/sync | 重复code去重 | perms中包含两个相同code | 仅处理一次, added=1(而非2) | 分支覆盖 | P0 | M-09修复: seen去重 | | TC-0535 | POST /api/perm/sync | 事务保护-中途失败回滚 | 模拟BatchUpdate失败 | 全部操作回滚, DB无变化 | 事务验证 | P0 | H-05修复: TransactCtx | ### 2.4 获取用户信息 `POST /api/auth/userInfo` | TC编号 | 接口/方法 | 测试场景 | 输入参数 (JSON) | 预期结果 | 测试类型 | 优先级 | 覆盖说明 | | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | | TC-0030 | POST /api/auth/userInfo | 正常获取-含productCode | Bearer token (含productCode) | code=0, 完整UserInfo+实时perms | 正常路径 | P0 | userInfoLogic全路径 | | TC-0031 | POST /api/auth/userInfo | 不含productCode | Bearer token (无productCode) | perms为空 | 分支覆盖 | P1 | productCode="" | | TC-0032 | POST /api/auth/userInfo | 未登录 | 无Authorization头 | code=401, "未登录" | 异常路径 | P0 | middleware拦截 | | TC-0033 | POST /api/auth/userInfo | token过期 | 过期token | code=401 | 异常路径 | P0 | middleware | | TC-0034 | POST /api/auth/userInfo | userId=0 | 伪造claims | code=401, "未登录" | 分支覆盖 | P1 | userId==0 | ### 2.5 修改密码 `POST /api/auth/changePassword` | TC编号 | 接口/方法 | 测试场景 | 输入参数 (JSON) | 预期结果 | 测试类型 | 优先级 | 覆盖说明 | | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | | TC-0035 | POST /api/auth/changePassword | 正常修改 | `{"oldPassword":"123456","newPassword":"654321"}` | code=0 | 正常路径 | P0 | changePasswordLogic全路径 | | TC-0036 | POST /api/auth/changePassword | mustChangePassword重置 | 正常修改后 | DB中mustChangePassword=2 | 功能验证 | P0 | user.MustChangePassword=2 | | TC-0037 | POST /api/auth/changePassword | 原密码错误 | `{"oldPassword":"wrong","newPassword":"newpwd"}` | code=400, "原密码错误" | 异常路径 | P0 | bcrypt失败 | | TC-0038 | POST /api/auth/changePassword | 新密码少于6字符 | `{"oldPassword":"old","newPassword":"12345"}` | code=400, "密码长度不能少于6个字符" | 输入校验 | P0 | len<6 | | TC-0039 | POST /api/auth/changePassword | 新密码恰好6字符 | `{"oldPassword":"old","newPassword":"123456"}` | code=0 | 边界 | P1 | len==6 | | TC-0040 | POST /api/auth/changePassword | 新密码空字符串 | `{"oldPassword":"old","newPassword":""}` | code=400 | 边界 | P0 | len("")=0<6 | | TC-0041 | POST /api/auth/changePassword | 新密码超过72字符 | `{"oldPassword":"old","newPassword":"a*73"}` | code=400, "密码长度不能超过72个字符" | 输入校验 | P0 | len>72 | | TC-0042 | POST /api/auth/changePassword | 新密码恰好72字符 | `{"oldPassword":"old","newPassword":"a*72"}` | code=0 | 边界 | P1 | len==72 | | TC-0043 | POST /api/auth/changePassword | 新旧密码相同 | `{"oldPassword":"123456","newPassword":"123456"}` | code=400, "新密码不能与原密码相同" | 输入校验 | P0 | OldPassword==NewPassword | | TC-0044 | POST /api/auth/changePassword | 用户不存在 | token中userId已删除 | code=404 | 异常路径 | P1 | FindOne失败 | ### 2.6 创建产品 `POST /api/product/create` | TC编号 | 接口/方法 | 测试场景 | 输入参数 (JSON) | 预期结果 | 测试类型 | 优先级 | 覆盖说明 | | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | | TC-0045 | POST /api/product/create | 正常创建 | `{"code":"new","name":"新产品"}` | code=0, id/appKey/appSecret/adminUser/adminPassword | 正常路径 | P0 | TransactCtx全路径 | | TC-0046 | POST /api/product/create | 事务回滚-用户创建失败 | 模拟InsertWithTx User失败 | 返回错误, DB无新产品 | 事务验证 | P0 | TransactCtx回滚 | | TC-0047 | POST /api/product/create | 事务回滚-成员创建失败 | 模拟InsertWithTx Member失败 | 产品和用户均回滚 | 事务验证 | P0 | TransactCtx回滚 | | TC-0048 | POST /api/product/create | 编码已存在 | `{"code":"existing","name":"x"}` | code=409 | 异常路径 | P0 | FindOneByCode成功 | | TC-0049 | POST /api/product/create | 并发创建同编码 | 两请求同时 | 一成功一冲突 | 并发 | P1 | uk_code | ### 2.7 产品更新/列表/详情 | TC编号 | 接口/方法 | 测试场景 | 输入参数 (JSON) | 预期结果 | 测试类型 | 优先级 | 覆盖说明 | | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | | TC-0050 | POST /api/product/update | 正常更新 | `{"id":1,"name":"新名","status":1}` | code=0 | 正常路径 | P0 | updateProductLogic | | TC-0051 | POST /api/product/update | 不存在 | `{"id":9999,"name":"x"}` | code=404 | 异常路径 | P0 | FindOne失败 | | TC-0052 | POST /api/product/update | 不传status | `{"id":1,"name":"x"}` | status不变 | 分支覆盖 | P1 | Status>0 | | TC-0053 | POST /api/product/list | 正常分页 | `{"page":1,"pageSize":10}` | code=0, total/list | 正常路径 | P0 | productListLogic | | TC-0054 | POST /api/product/list | 默认分页 | `{}` | page=1, pageSize=20 | 分支覆盖 | P1 | NormalizePage默认值 | | TC-0055 | POST /api/product/list | pageSize超过上限 | `{"page":1,"pageSize":500}` | 实际pageSize=100 | 边界 | P0 | NormalizePage cap 100 | | TC-0056 | POST /api/product/list | pageSize=0 | `{"page":1,"pageSize":0}` | 实际pageSize=20 | 边界 | P1 | NormalizePage<=0→20 | | TC-0057 | POST /api/product/list | page负值 | `{"page":-1,"pageSize":10}` | 实际page=1 | 边界 | P1 | NormalizePage<=0→1 | | TC-0058 | POST /api/product/detail | 正常查询 | `{"id":1}` | code=0, ProductItem | 正常路径 | P0 | productDetailLogic | | TC-0059 | POST /api/product/detail | 不存在 | `{"id":9999}` | code=404 | 异常路径 | P0 | FindOne失败 | | TC-0526 | POST /api/product/list | 非超管AppKey隐藏 | ctx=MEMBER | code=0, 列表中AppKey为空 | 安全 | P0 | H-11修复: AppKey仅超管可见 | | TC-0527 | POST /api/product/list | 超管可见AppKey | ctx=SuperAdmin | code=0, 列表中AppKey不为空 | 安全 | P0 | H-11修复: 超管可见AppKey | | TC-0528 | POST /api/product/detail | 非超管AppKey隐藏 | ctx=MEMBER | code=0, AppKey为空 | 安全 | P0 | H-11修复: AppKey仅超管可见 | | TC-0529 | POST /api/product/detail | 超管可见AppKey | ctx=SuperAdmin | code=0, AppKey不为空 | 安全 | P0 | H-11修复: 超管可见AppKey | ### 2.8 创建部门 `POST /api/dept/create` | TC编号 | 接口/方法 | 测试场景 | 输入参数 (JSON) | 预期结果 | 测试类型 | 优先级 | 覆盖说明 | | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | | TC-0060 | POST /api/dept/create | 创建顶级部门 | `{"parentId":0,"name":"总部"}` | code=0, path="/{id}/" | 正常路径 | P0 | TransactCtx, parentPath="/" | | TC-0061 | POST /api/dept/create | 创建子部门 | `{"parentId":1,"name":"技术部"}` | code=0, path=parent.path+id+"/" | 正常路径 | P0 | parentId>0分支 | | TC-0062 | POST /api/dept/create | 父部门不存在 | `{"parentId":9999,"name":"x"}` | code=404, "父部门不存在" | 异常路径 | P0 | FindOneWithTx失败 | | TC-0063 | POST /api/dept/create | 不传DeptType默认NORMAL | `{"parentId":0,"name":"x"}` | DB deptType="NORMAL" | 分支覆盖 | P0 | deptType=""→DeptTypeNormal | | TC-0064 | POST /api/dept/create | 传DeptType=DEV | `{"parentId":0,"name":"x","deptType":"DEV"}` | DB deptType="DEV" | 正常路径 | P0 | req.DeptType赋值 | | TC-0065 | POST /api/dept/create | 事务内FindOneWithTx可见性 | TransactCtx内InsertWithTx后FindOneWithTx | 事务内可读到未提交数据 | 事务验证 | P0 | FindOneWithTx(session) | | TC-0066 | POST /api/dept/create | 事务回滚-Insert失败 | 模拟InsertWithTx失败 | DB无新记录 | 事务验证 | P0 | TransactCtx回滚 | | TC-0067 | POST /api/dept/create | 事务回滚-UpdateWithTx失败 | 模拟UpdateWithTx失败 | Insert也回滚 | 事务验证 | P1 | TransactCtx回滚 | | TC-0068 | POST /api/dept/create | 多层嵌套(5层) | 递归创建5层 | path正确拼接 | 深度测试 | P2 | path逻辑 | | TC-0069 | POST /api/dept/create | 通过Logic创建+验证Path | CreateDeptLogic.CreateDept→FindOne | path包含/{id}/ | 集成验证 | P0 | FindOneWithTx修复后端到端 | ### 2.9 部门更新/删除/树 | TC编号 | 接口/方法 | 测试场景 | 输入参数 (JSON) | 预期结果 | 测试类型 | 优先级 | 覆盖说明 | | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | | TC-0070 | POST /api/dept/update | 正常更新 | `{"id":1,"name":"新名","sort":5}` | code=0 | 正常路径 | P0 | updateDeptLogic | | TC-0071 | POST /api/dept/update | 不存在 | `{"id":9999,"name":"x"}` | code=404 | 异常路径 | P0 | FindOne失败 | | TC-0072 | POST /api/dept/update | DeptType NORMAL→DEV | `{"id":1,"deptType":"DEV"}` | DB deptType="DEV" | 正常路径 | P0 | DeptType合法值更新 | | TC-0073 | POST /api/dept/update | DeptType无效值忽略 | `{"id":1,"deptType":"INVALID"}` | DB deptType不变 | 分支覆盖 | P0 | DeptType非NORMAL/DEV | | TC-0533 | POST /api/dept/update | DeptType变更时级联清除子部门用户缓存 | 部门从NORMAL改为DEV,有子部门含用户 | code=0, 子部门下用户缓存被清除 | 缓存验证 | P0 | M-10修复: 级联缓存失效 | | TC-0074 | POST /api/dept/delete | 正常删除(无子部门) | `{"id":5}` | code=0 | 正常路径 | P0 | deleteDeptLogic | | TC-0075 | POST /api/dept/delete | 有子部门 | `{"id":1}` | code=400, "存在子部门" | 业务约束 | P0 | len(children)>0 | | TC-0076 | POST /api/dept/delete | 不存在的部门 | `{"id":9999}` | code=0(Delete对不存在行不报错) | 边界 | P1 | FindByParentId空+Delete | | TC-0522 | POST /api/dept/delete | 部门下有关联用户 | 部门id指向含用户的部门 | code=400, "该部门下仍有关联用户,无法删除" | 业务约束 | P0 | H-07修复: 检查关联用户 | | TC-0077 | POST /api/dept/tree | 正常获取 | `{}` | code=0, 树形结构, 含DeptType字段 | 正常路径 | P0 | deptTreeLogic, DeptType映射 | | TC-0078 | POST /api/dept/tree | 空数据 | 无数据 | code=0, data=[] | 边界 | P1 | 空列表 | | TC-0079 | POST /api/dept/tree | 孤儿节点 | parentId指向不存在 | 升级为根节点 | 分支覆盖 | P2 | parent不存在 | ### 2.10 权限列表 `POST /api/perm/list` | TC编号 | 接口/方法 | 测试场景 | 输入参数 (JSON) | 预期结果 | 测试类型 | 优先级 | 覆盖说明 | | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | | TC-0080 | POST /api/perm/list | 正常查询 | `{"productCode":"p1","page":1,"pageSize":10}` | code=0, total/list | 正常路径 | P0 | permListLogic | | TC-0081 | POST /api/perm/list | 默认分页 | `{"productCode":"p1"}` | page=1, pageSize=20 | 分支覆盖 | P1 | NormalizePage | | TC-0082 | POST /api/perm/list | pageSize超过上限 | `{"productCode":"p1","pageSize":200}` | 实际pageSize=100 | 边界 | P0 | NormalizePage cap | | TC-0083 | POST /api/perm/list | 不存在的productCode | `{"productCode":"notexist"}` | total=0, list=[] | 边界 | P1 | 空结果 | ### 2.11 角色管理 | TC编号 | 接口/方法 | 测试场景 | 输入参数 (JSON) | 预期结果 | 测试类型 | 优先级 | 覆盖说明 | | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | | TC-0084 | POST /api/role/create | 正常创建 | `{"productCode":"p1","name":"管理员","permsLevel":1}` | code=0, id>0 | 正常路径 | P0 | createRoleLogic | | TC-0085 | POST /api/role/create | 重复角色名 | 同产品同名 | code=409, "该产品下角色名已存在" | 业务约束 | P0 | Duplicate entry→ErrConflict | | TC-0086 | POST /api/role/create | 并发同名创建 | 两请求同时 | 一成功一冲突409 | 并发 | P1 | 唯一索引+1062捕获 | | TC-0087 | POST /api/role/update | 正常更新 | `{"id":1,"name":"新名","permsLevel":2}` | code=0 | 正常路径 | P0 | updateRoleLogic | | TC-0088 | POST /api/role/update | 不存在 | `{"id":9999,...}` | code=404 | 异常路径 | P0 | FindOne失败 | | TC-0089 | POST /api/role/list | 正常查询 | `{"productCode":"p1","page":1,"pageSize":10}` | code=0 | 正常路径 | P0 | roleListLogic | | TC-0090 | POST /api/role/list | pageSize超过上限 | `{"productCode":"p1","pageSize":200}` | 实际pageSize=100 | 边界 | P0 | NormalizePage cap | | TC-0091 | POST /api/role/detail | 正常查询 | `{"id":1}` | code=0, 含permIds | 正常路径 | P0 | roleDetailLogic | | TC-0092 | POST /api/role/detail | 不存在 | `{"id":9999}` | code=404 | 异常路径 | P0 | FindOne失败 | ### 2.12 删除角色 `POST /api/role/delete` | TC编号 | 接口/方法 | 测试场景 | 输入参数 (JSON) | 预期结果 | 测试类型 | 优先级 | 覆盖说明 | | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | | TC-0093 | POST /api/role/delete | 正常删除+级联 | `{"id":5}` (含权限/用户绑定) | code=0, role_perm/user_role同步清理 | 正常+事务 | P0 | TransactCtx全路径 | | TC-0094 | POST /api/role/delete | 事务回滚 | 模拟DeleteWithTx失败 | 级联删除回滚 | 事务验证 | P0 | TransactCtx | | TC-0095 | POST /api/role/delete | 无关联数据 | 新角色无绑定 | code=0 | 分支覆盖 | P1 | 删0条 | ### 2.13 绑定角色权限 `POST /api/role/bindPerms` | TC编号 | 接口/方法 | 测试场景 | 输入参数 (JSON) | 预期结果 | 测试类型 | 优先级 | 覆盖说明 | | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | | TC-0096 | POST /api/role/bindPerms | 正常绑定 | `{"roleId":1,"permIds":[1,2,3]}` | code=0 | 正常路径 | P0 | TransactCtx | | TC-0097 | POST /api/role/bindPerms | 角色不存在 | `{"roleId":9999,"permIds":[1]}` | code=404, "角色不存在" | 存在性校验 | P0 | FindOne预检 | | TC-0098 | POST /api/role/bindPerms | 清空权限 | `{"roleId":1,"permIds":[]}` | code=0, 全清空 | 分支覆盖 | P1 | len==0→return | | TC-0099 | POST /api/role/bindPerms | 重复permId | `{"roleId":1,"permIds":[1,1]}` | DB唯一索引→事务回滚 | 边界 | P1 | uk_role_perm | | TC-0100 | POST /api/role/bindPerms | 事务回滚 | 模拟BatchInsertWithTx失败 | 旧数据回滚还原 | 事务验证 | P0 | TransactCtx | ### 2.14 创建用户 `POST /api/user/create` | TC编号 | 接口/方法 | 测试场景 | 输入参数 (JSON) | 预期结果 | 测试类型 | 优先级 | 覆盖说明 | | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | | TC-0101 | POST /api/user/create | 正常创建 | `{"username":"new","password":"123456"}` | code=0, id>0 | 正常路径 | P0 | createUserLogic | | TC-0102 | POST /api/user/create | 用户名已存在(预检) | `{"username":"existing","password":"x"}` | code=409, "用户名已存在" | 异常路径 | P0 | FindOneByUsername成功 | | TC-0103 | POST /api/user/create | 带完整可选字段 | 含nickname/email/phone/remark/deptId | code=0 | 正常路径 | P1 | 各字段赋值 | | TC-0104 | POST /api/user/create | 非法email格式 | `{"...","email":"not-an-email"}` | code=400, "邮箱格式不正确" | 输入校验 | P0 | util.IsValidEmail | | TC-0105 | POST /api/user/create | 合法email | `{"...","email":"user@example.com"}` | code=0 | 正常路径 | P1 | IsValidEmail通过 | | TC-0106 | POST /api/user/create | email为空(可选) | `{"...","email":""}` | code=0, 跳过校验 | 分支覆盖 | P1 | email!=""判断 | | TC-0107 | POST /api/user/create | 非法phone格式 | `{"...","phone":"abc"}` | code=400, "手机号格式不正确" | 输入校验 | P0 | util.IsValidPhone | | TC-0108 | POST /api/user/create | 合法phone(国际) | `{"...","phone":"+8613800138000"}` | code=0 | 正常路径 | P1 | IsValidPhone通过 | | TC-0109 | POST /api/user/create | phone为空(可选) | `{"...","phone":""}` | code=0, 跳过校验 | 分支覆盖 | P1 | phone!=""判断 | | TC-0110 | POST /api/user/create | 并发同username(TOCTOU) | 两请求同时 | 一成功一冲突(1062) | 并发 | P0 | Duplicate entry→ErrConflict | | TC-0111 | POST /api/user/create | 唯一索引冲突消息 | 预检通过后DB冲突 | code=409, "用户名已存在" | 异常路径 | P0 | strings.Contains "1062" | | TC-0524 | POST /api/user/create | 密码少于6字符 | `{"username":"x","password":"12345"}` | code=400, "密码长度不能少于6个字符" | 输入校验 | P0 | H-10修复: 密码强度校验 | | TC-0525 | POST /api/user/create | 密码超过72字符 | `{"username":"x","password":"a*73"}` | code=400, "密码长度不能超过72个字符" | 输入校验 | P0 | H-10修复: 密码强度校验 | ### 2.15 用户更新 `POST /api/user/update` (指针类型+DeptId可清零) | TC编号 | 接口/方法 | 测试场景 | 输入参数 (JSON) | 预期结果 | 测试类型 | 优先级 | 覆盖说明 | | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | | TC-0112 | POST /api/user/update | 正常更新 | `{"id":1,"nickname":"n","email":"a@b.com"}` | code=0 | 正常路径 | P0 | updateUserLogic | | TC-0113 | POST /api/user/update | 不存在 | `{"id":9999}` | code=404 | 异常路径 | P0 | FindOne失败 | | TC-0114 | POST /api/user/update | 仅传id | `{"id":1}` | 仅updateTime变 | 分支覆盖 | P1 | 所有指针nil | | TC-0115 | POST /api/user/update | 清空nickname | `{"id":1,"nickname":""}` | DB nickname→空字符串 | 功能 | P0 | *string非nil+空值 | | TC-0116 | POST /api/user/update | 清空email | `{"id":1,"email":""}` | DB email→空字符串(跳过校验) | 功能 | P0 | *email!=""判断 | | TC-0117 | POST /api/user/update | 清空remark | `{"id":1,"remark":""}` | DB remark清空 | 功能 | P1 | *string→"" | | TC-0118 | POST /api/user/update | 非法email格式 | `{"id":1,"email":"bad-email"}` | code=400, "邮箱格式不正确" | 输入校验 | P0 | util.IsValidEmail | | TC-0119 | POST /api/user/update | 非法phone格式 | `{"id":1,"phone":"12345"}` | code=400, "手机号格式不正确" | 输入校验 | P0 | util.IsValidPhone | | TC-0120 | POST /api/user/update | 合法phone | `{"id":1,"phone":"+8613800138000"}` | code=0 | 正常路径 | P1 | IsValidPhone通过 | | TC-0121 | POST /api/user/update | 不传email(nil) | `{"id":1,"nickname":"x"}` | email不变 | 分支覆盖 | P1 | req.Email==nil | | TC-0122 | POST /api/user/update | DeptId设为0(取消部门) | `{"id":1,"deptId":0}` | DB deptId→0 | 功能 | P0 | \*int64, \*req.DeptId=0 | | TC-0123 | POST /api/user/update | DeptId设为正值 | `{"id":1,"deptId":5}` | DB deptId→5 | 正常路径 | P0 | *int64指针 | | TC-0124 | POST /api/user/update | DeptId不传(nil) | `{"id":1,"nickname":"x"}` | deptId不变 | 分支覆盖 | P1 | req.DeptId==nil | ### 2.16 用户列表/详情/状态 及其他用户操作 | TC编号 | 接口/方法 | 测试场景 | 输入参数 (JSON) | 预期结果 | 测试类型 | 优先级 | 覆盖说明 | | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | | TC-0125 | POST /api/user/list | 含productCode | `{"productCode":"p1","page":1,"pageSize":10}` | 每用户含memberType(批量查) | 正常路径 | P0 | FindMapByProductCodeUserIds | | TC-0126 | POST /api/user/list | 不含productCode | `{"page":1}` | memberType全空,不调批量查 | 分支覆盖 | P1 | productCode="" | | TC-0127 | POST /api/user/list | pageSize超过上限 | `{"pageSize":500}` | 实际pageSize=100 | 边界 | P0 | NormalizePage cap | | TC-0128 | POST /api/user/list | 用户不在产品中 | productCode指定,部分用户不是成员 | memberType为空 | 分支覆盖 | P1 | memberMap无对应key | | TC-0129 | POST /api/user/list | 批量查询DB异常 | FindMapByProductCodeUserIds失败 | code=500 | 异常路径 | P1 | err→透传 | | TC-0130 | POST /api/user/detail | 正常查询 | `{"id":1}` | 含roleIds | 正常路径 | P0 | userDetailLogic | | TC-0131 | POST /api/user/detail | 正常查询-含Avatar | 有Avatar用户 | avatar字段非空 | 分支覆盖 | P1 | Avatar.Valid=true | | TC-0132 | POST /api/user/detail | 不存在 | `{"id":9999}` | code=404 | 异常路径 | P0 | FindOne失败 | | TC-0133 | POST /api/user/bindRoles | 正常绑定 | `{"userId":1,"roleIds":[1,2]}` | code=0 | 正常路径 | P0 | TransactCtx | | TC-0134 | POST /api/user/bindRoles | 用户不存在 | `{"userId":9999,"roleIds":[1]}` | code=404, "用户不存在" | 存在性校验 | P0 | FindOne预检 | | TC-0135 | POST /api/user/bindRoles | 清空角色 | `{"userId":1,"roleIds":[]}` | code=0 | 分支覆盖 | P1 | len==0 | | TC-0136 | POST /api/user/bindRoles | 事务回滚 | 模拟失败 | 旧数据还原 | 事务验证 | P0 | TransactCtx | | TC-0515 | POST /api/user/bindRoles | 角色不属于当前产品 | roleId属于其他产品 | code=400, "角色不属于当前产品" | 安全 | P0 | H-03修复: 校验角色归属 | | TC-0516 | POST /api/user/bindRoles | 角色已禁用 | roleId状态为禁用 | code=400, "角色已禁用" | 安全 | P0 | H-03修复: 校验角色状态 | | TC-0517 | POST /api/user/bindRoles | 角色不存在 | roleId不存在 | code=400, "角色不存在" | 安全 | P0 | H-03修复: 校验角色存在 | | TC-0137 | POST /api/user/setPerms | 正常ALLOW | `{"userId":1,"perms":[{"permId":1,"effect":"ALLOW"}]}` | code=0 | 正常路径 | P0 | TransactCtx | | TC-0138 | POST /api/user/setPerms | 用户不存在 | `{"userId":9999,"perms":[...]}` | code=404, "用户不存在" | 存在性校验 | P0 | FindOne预检 | | TC-0139 | POST /api/user/setPerms | DENY权限 | effect="DENY" | code=0 | 正常路径 | P0 | effect="DENY" | | TC-0140 | POST /api/user/setPerms | 清空权限 | `{"userId":1,"perms":[]}` | code=0 | 分支覆盖 | P1 | len==0 | | TC-0518 | POST /api/user/setPerms | 无效Effect值 | effect="INVALID" | code=400, "无效的权限效果" | 安全 | P0 | H-04修复: Effect白名单 | | TC-0519 | POST /api/user/setPerms | PermId不存在 | permId=99999 | code=400, "权限不存在" | 安全 | P0 | H-04修复: 校验PermId | | TC-0520 | POST /api/user/setPerms | 权限不属于当前产品 | permId属于其他产品 | code=400, "权限不属于当前产品" | 安全 | P0 | H-04修复: 校验权限归属 | | TC-0141 | POST /api/user/updateStatus | 正常冻结 | `{"id":普通用户,"status":2}` | code=0 | 正常路径 | P0 | updateUserStatusLogic | | TC-0142 | POST /api/user/updateStatus | 正常解冻 | `{"id":普通用户,"status":1}` | code=0 | 正常路径 | P0 | status=1 | | TC-0143 | POST /api/user/updateStatus | 非法status(0) | `{"id":1,"status":0}` | code=400, "状态值无效" | 输入校验 | P0 | status!=1&&!=2 | | TC-0144 | POST /api/user/updateStatus | 冻结自己 | id=当前登录userId | code=400, "不能修改自己的状态" | 自我保护 | P0 | callerId==req.Id | | TC-0145 | POST /api/user/updateStatus | 冻结超管 | id=超管 | code=403, "不能修改超级管理员的状态" | 超管保护 | P0 | IsSuperAdmin==1 | ### 2.17 成员管理 | TC编号 | 接口/方法 | 测试场景 | 输入参数 (JSON) | 预期结果 | 测试类型 | 优先级 | 覆盖说明 | | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | | TC-0146 | POST /api/member/add | 正常添加 | `{"productCode":"p1","userId":1,"memberType":"MEMBER"}` | code=0, id>0 | 正常路径 | P0 | addMemberLogic | | TC-0147 | POST /api/member/add | 产品不存在 | `{"productCode":"notexist",...}` | code=404, "产品不存在" | 存在性校验 | P0 | FindOneByCode预检 | | TC-0148 | POST /api/member/add | 用户不存在 | `{"userId":9999,...}` | code=404, "用户不存在" | 存在性校验 | P0 | FindOne预检 | | TC-0149 | POST /api/member/add | 已是成员 | 重复添加 | code=409, "已是成员" | 异常路径 | P0 | FindOneByProductCodeUserId成功 | | TC-0150 | POST /api/member/add | 并发添加 | 两请求同时 | 一成功一冲突 | 并发 | P1 | uk_product_user | | TC-0530 | POST /api/member/add | 无效MemberType | `{"memberType":"INVALID"}` | code=400, "无效的成员类型" | 输入校验 | P0 | M-06修复: MemberType白名单 | | TC-0151 | POST /api/member/update | 正常更新 | `{"id":1,"memberType":"ADMIN"}` | code=0 | 正常路径 | P0 | updateMemberLogic | | TC-0152 | POST /api/member/update | 不存在 | `{"id":9999,...}` | code=404 | 异常路径 | P0 | FindOne失败 | | TC-0531 | POST /api/member/update | 无效MemberType | `{"id":1,"memberType":"INVALID"}` | code=400, "无效的成员类型" | 输入校验 | P0 | M-06修复: MemberType白名单 | | TC-0153 | POST /api/member/list | 正常查询(批量查用户) | `{"productCode":"p1","page":1,"pageSize":10}` | 含username/nickname | 正常路径 | P0 | FindByIds批量 | | TC-0154 | POST /api/member/list | 成员用户已删除 | userId不存在于FindByIds结果 | username/nickname为空 | 分支覆盖 | P1 | userMap无对应key | | TC-0155 | POST /api/member/list | pageSize超过上限 | `{"productCode":"p1","pageSize":200}` | 实际pageSize=100 | 边界 | P0 | NormalizePage cap | | TC-0156 | POST /api/member/list | 空成员列表 | productCode下无成员 | total=0, list=[], 不调FindByIds | 分支覆盖 | P1 | userIds空 | | TC-0157 | POST /api/member/remove | 正常移除+级联(事务内) | `{"id":1}` (含角色/权限) | code=0, user_role+user_perm同步清理 | 正常+事务 | P0 | TransactCtx全路径 | | TC-0158 | POST /api/member/remove | 跨产品隔离 | 用户在多产品有角色 | 仅清理该产品的 | 深度业务 | P0 | ForProductTx子查询 | | TC-0159 | POST /api/member/remove | 成员不存在 | `{"id":9999}` | code=404, "成员不存在" | 异常路径 | P0 | FindOne失败 | | TC-0160 | POST /api/member/remove | 事务回滚 | 模拟DeleteWithTx失败 | 级联删除全部回滚 | 事务验证 | P0 | TransactCtx | --- ## 三、gRPC 接口测试用例 ### 3.1 gRPC SyncPermissions | TC编号 | 接口/方法 | 测试场景 | 输入 | 预期结果 | 测试类型 | 优先级 | 覆盖说明 | | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | | TC-0161 | SyncPermissions | 正常同步 | valid req | added/updated/disabled计数正确 | 正常路径 | P0 | permserver.go SyncPermissions | | TC-0162 | SyncPermissions | appKey无效 | invalid appKey | codes.Unauthenticated | 异常路径 | P0 | status.Error | | TC-0163 | SyncPermissions | appSecret错误 | wrong secret | codes.Unauthenticated | 异常路径 | P0 | status.Error | | TC-0164 | SyncPermissions | 产品已禁用 | disabled product | codes.PermissionDenied | 分支覆盖 | P0 | status.Error | | TC-0165 | SyncPermissions | 验证disabled计数 | DB有5条,perms含2条 | disabled=3 | 功能验证 | P0 | RowsAffected | ### 3.2 gRPC Login / RefreshToken / VerifyToken / GetUserPerms | TC编号 | 接口/方法 | 测试场景 | 输入 | 预期结果 | 测试类型 | 优先级 | 覆盖说明 | | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | | TC-0166 | Login | 正常登录(普通用户+productCode) | valid credentials + productCode | token对+userInfo | 正常路径 | P0 | permserver.go Login | | TC-0167 | Login | 用户不存在 | wrong username | codes.Unauthenticated | 异常路径 | P0 | status.Error | | TC-0168 | Login | 密码错误 | wrong password | codes.Unauthenticated | 异常路径 | P0 | status.Error | | TC-0169 | Login | 账号冻结 | frozen user | codes.PermissionDenied | 分支覆盖 | P0 | status.Error | | TC-0170 | Login | 超管被拒绝 | isSuperAdmin=1+productCode | codes.PermissionDenied, "超级管理员不允许通过产品端登录" | 安全 | P0 | IsSuperAdmin==1 → 拒绝 | | TC-0171 | Login | 普通用户+productCode | 普通MEMBER+productCode | perms含角色权限, memberType="MEMBER" | 分支覆盖 | P0 | !isSuperAdmin && productCode!="" | | TC-0510 | Login | productCode为空 | productCode="" | codes.InvalidArgument, "productCode不能为空" | 输入校验 | P0 | 第一个校验点 | | TC-0172 | RefreshToken | 正常刷新 | valid token | 新token对 | 正常路径 | P0 | RefreshToken | | TC-0173 | RefreshToken | token无效 | invalid token | codes.Unauthenticated | 异常路径 | P0 | status.Error | | TC-0174 | RefreshToken | 账号冻结 | frozen | codes.PermissionDenied | 分支覆盖 | P0 | status.Error | | TC-0175 | RefreshToken | productCode回退到claims | req.ProductCode="", claims含productCode | 使用claims.ProductCode | 分支覆盖 | P0 | productCode==""回退 | | TC-0176 | RefreshToken | 超管+productCode | isSuperAdmin=1+productCode | memberType="SUPER_ADMIN", perms全量 | 分支覆盖 | P0 | isSuperAdmin && productCode!="" | | TC-0177 | RefreshToken | 普通用户+productCode | 普通MEMBER+productCode | perms含角色权限 | 分支覆盖 | P0 | !isSuperAdmin && productCode!="" | | TC-0178 | VerifyToken | 有效token | valid | valid=true, userId/perms正确 | 正常路径 | P0 | VerifyToken | | TC-0179 | VerifyToken | 无效token | invalid | valid=false | 异常路径 | P0 | err或!Valid | | TC-0180 | VerifyToken | 缺少userId | 伪造claims | valid=false | 安全 | P0 | !ok断言保护 | | TC-0181 | GetUserPerms | 用户不存在 | userId=9999 | codes.NotFound | 异常路径 | P0 | status.Error | | TC-0182 | GetUserPerms | 超管 | isSuperAdmin | perms全量, "SUPER_ADMIN" | 正常路径 | P0 | GetUserPerms(true) | | TC-0183 | GetUserPerms | MEMBER-DENY覆盖 | 角色有permA, DENY permA | perms不含permA | 深度业务 | P0 | denySet过滤 | --- ## 四、JWT中间件 / 统一响应测试用例 | TC编号 | 测试场景 | 输入 | 预期结果 | 类型 | 优先级 | 覆盖说明 | | :--- | :--- | :--- | :--- | :--- | :--- | :--- | | TC-0184 | 正常Bearer token | `Authorization: Bearer {valid}` | 通过, ctx注入5个值 | 正常路径 | P0 | middleware全路径 | | TC-0185 | 无Authorization头 | 无Header | code=401, "未登录" | 异常 | P0 | authHeader=="" | | TC-0186 | 无Bearer前缀 | `Authorization: xxx` | code=401, "token格式错误" | 异常 | P0 | TrimPrefix相等 | | TC-0187 | token签名错误 | 错误secret | code=401, "token无效或已过期" | 异常 | P0 | !Valid | | TC-0188 | token过期 | expired | code=401 | 异常 | P0 | jwt过期 | | TC-0189 | claims类型断言失败 | 非标准claims | code=401, "token无效或类型错误" | 异常 | P1 | !ok 防御性分支,jwt.ParseWithClaims(&Claims{}) 下不可达;TokenType 检查由 TC-0434 覆盖 | | TC-0434 | refresh token被拒绝 | 用refresh token访问API | code=401, "token无效或类型错误" | 安全 | P0 | TokenType="refresh"时拒绝 | | TC-0190 | 业务错误(CodeError) | 触发404等 | `{code:业务码, msg:业务消息}` | 正常 | P0 | errors.As成功 | | TC-0191 | 内部错误 | DB异常 | `{code:500, msg:"服务器内部错误"}` | 安全 | P0 | logx.Errorf+兜底 | | TC-0192 | 成功(有data) | 正常请求 | `{code:0, msg:"ok", data:{...}}` | 正常 | P0 | v!=nil | | TC-0193 | 成功(无data) | 返回nil | `{code:0, msg:"ok"}` | 正常 | P0 | v==nil | --- ## 五、util 层测试用例 ### 5.1 NormalizePage | TC编号 | 测试场景 | 输入 | 预期结果 | 类型 | 优先级 | 覆盖说明 | | :--- | :--- | :--- | :--- | :--- | :--- | :--- | | TC-0194 | 正常值 | page=2, pageSize=10 | (2, 10) | 正常路径 | P0 | 无修正 | | TC-0195 | page<=0 | page=0, pageSize=10 | (1, 10) | 边界 | P0 | page<=0→1 | | TC-0196 | page=-1 | page=-1, pageSize=10 | (1, 10) | 边界 | P0 | page<=0→1 | | TC-0197 | pageSize<=0 | page=1, pageSize=0 | (1, 20) | 边界 | P0 | pageSize<=0→20 | | TC-0198 | pageSize>100 | page=1, pageSize=500 | (1, 100) | 边界-上限 | P0 | pageSize>100→100 | | TC-0199 | pageSize=100 | page=1, pageSize=100 | (1, 100) | 边界 | P1 | 恰好不触发 | | TC-0200 | pageSize=101 | page=1, pageSize=101 | (1, 100) | 边界 | P1 | 恰好触发 | | TC-0201 | 双零 | page=0, pageSize=0 | (1, 20) | 边界 | P1 | 两条件同时 | ### 5.2 IsValidEmail | TC编号 | 测试场景 | 输入 | 预期结果 | 类型 | 优先级 | 覆盖说明 | | :--- | :--- | :--- | :--- | :--- | :--- | :--- | | TC-0202 | 正常邮箱 | `user@example.com` | true | 正常路径 | P0 | 标准格式 | | TC-0203 | 含点号 | `user.name@example.com` | true | 正常路径 | P1 | 允许点号 | | TC-0204 | 含加号 | `user+tag@example.com` | true | 正常路径 | P1 | 允许加号 | | TC-0205 | 缺少@ | `userexample.com` | false | 异常路径 | P0 | 无@ | | TC-0206 | 缺少域名 | `user@` | false | 异常路径 | P0 | 无域名 | | TC-0207 | 缺少TLD | `user@example` | false | 异常路径 | P0 | TLD<2字符 | | TC-0208 | 空字符串 | `""` | false | 边界 | P1 | 空 | ### 5.3 IsValidPhone | TC编号 | 测试场景 | 输入 | 预期结果 | 类型 | 优先级 | 覆盖说明 | | :--- | :--- | :--- | :--- | :--- | :--- | :--- | | TC-0209 | 国内手机号 | `13800138000` | true | 正常路径 | P0 | 11位数字 | | TC-0210 | 带+国际码 | `+8613800138000` | true | 正常路径 | P0 | +前缀 | | TC-0211 | 太短(6位) | `123456` | false | 边界 | P0 | <7位 | | TC-0212 | 恰好7位 | `1234567` | true | 边界 | P1 | 最小长度 | | TC-0213 | 最长15位 | `+123456789012345` | true | 边界 | P1 | 最大长度 | | TC-0214 | 超长16位 | `1234567890123456` | false | 边界 | P1 | 超限 | | TC-0215 | 包含字母 | `1380013abc` | false | 异常路径 | P0 | 非数字 | | TC-0216 | 空字符串 | `""` | false | 边界 | P1 | 空 | --- ## 六、Logic 层单元测试用例 > 以下针对 Logic 层中的核心共享函数,使用 mock Model 接口进行纯单元测试。 ### 6.1 auth/jwt.go — GenerateAccessToken | TC编号 | 测试场景 | 输入 | 预期结果 | 类型 | 优先级 | 覆盖说明 | | :--- | :--- | :--- | :--- | :--- | :--- | :--- | | TC-0217 | 正常生成 | secret="s", expire=3600, userId=1, username="u", productCode="p", memberType="M", perms=["a"] | 返回非空token, err=nil | 正常路径 | P0 | jwt.NewWithClaims(HS256) | | TC-0218 | 解析token验证claims | 上述token | ParseWithClaims可解析出正确userId/username/productCode/memberType/perms | 功能验证 | P0 | claims完整性 | | TC-0219 | 空secret | secret="" | 仍能生成token(空key签名) | 边界 | P2 | HS256 允许空key | | TC-0220 | 空perms | perms=nil | token生成成功, 解析后perms=nil | 边界 | P1 | nil slice | | TC-0221 | 过期时间验证 | expireSeconds=1, sleep 2s | ParseWithClaims返回过期错误 | 功能验证 | P0 | ExpiresAt | ### 6.2 auth/jwt.go — GenerateRefreshToken | TC编号 | 测试场景 | 输入 | 预期结果 | 类型 | 优先级 | 覆盖说明 | | :--- | :--- | :--- | :--- | :--- | :--- | :--- | | TC-0222 | 正常生成 | secret="s", expire=86400, userId=1, productCode="p" | 返回非空token | 正常路径 | P0 | RefreshClaims | | TC-0223 | 解析验证 | 上述token | ParseRefreshToken解析出userId=1, productCode="p" | 功能验证 | P0 | 往返一致 | | TC-0224 | productCode为空 | productCode="" | 生成成功, 解析后productCode="" | 边界 | P1 | 空字符串 | ### 6.3 auth/jwt.go — ParseRefreshToken | TC编号 | 测试场景 | 输入 | 预期结果 | 类型 | 优先级 | 覆盖说明 | | :--- | :--- | :--- | :--- | :--- | :--- | :--- | | TC-0225 | 正常解析 | 有效token+正确secret | 返回RefreshClaims, err=nil | 正常路径 | P0 | token.Valid | | TC-0226 | 错误secret | 有效token+错误secret | err!=nil | 异常路径 | P0 | 签名验证失败 | | TC-0227 | 无效token字符串 | "invalid-token" | err!=nil | 异常路径 | P0 | 解析失败 | | TC-0228 | 空token | "" | err!=nil | 边界 | P1 | 空字符串 | | TC-0229 | 过期token | 已过期的token | err!=nil (token expired) | 异常路径 | P0 | ExpiresAt已过 | | TC-0230 | AccessToken误用 | 用AccessToken当RefreshToken解析 | err!=nil (TokenType="access"≠"refresh") | 安全 | P0 | TokenType字段校验 | ### 6.4 auth/perms.go — GetUserPerms > **L-03重构**:`GetUserPerms` 已重构为直接委托 `UserDetailsLoader.Load()`,不再包含权限计算逻辑。 > 原 TC-0231~TC-0257 的权限计算逻辑已迁移到 UserDetailsLoader 的测试中(第九章 TC-0467~TC-0477),此处仅需验证委托行为。 | TC编号 | 测试场景 | 输入 | 预期结果 | 类型 | 优先级 | 覆盖说明 | | :--- | :--- | :--- | :--- | :--- | :--- | :--- | | TC-0534 | GetUserPerms委托到Loader | userId+productCode | 返回值等于UserDetailsLoader.Load().Perms和.MemberType, err=nil | 正常路径 | P0 | L-03修复: 消除重复逻辑 | ### 6.5 middleware — 辅助函数单元测试 | TC编号 | 测试场景 | 输入 | 预期结果 | 类型 | 优先级 | 覆盖说明 | | :--- | :--- | :--- | :--- | :--- | :--- | :--- | | TC-0258 | GetUserId-正常 | ctx含userId=100 | 100 | 正常路径 | P0 | 类型断言成功 | | TC-0259 | GetUserId-空ctx | 空ctx | 0 | 边界 | P0 | 断言失败→零值 | | TC-0260 | GetUsername-正常 | ctx含username="admin" | "admin" | 正常路径 | P0 | 类型断言 | | TC-0261 | GetUsername-空ctx | 空ctx | "" | 边界 | P0 | 零值 | | TC-0262 | GetProductCode-正常 | ctx含productCode="p1" | "p1" | 正常路径 | P0 | 类型断言 | | TC-0263 | GetMemberType-正常 | ctx含memberType="ADMIN" | "ADMIN" | 正常路径 | P0 | 类型断言 | | TC-0264 | IsSuperAdmin-是 | memberType="SUPER_ADMIN" | true | 正常路径 | P0 | == | | TC-0265 | IsSuperAdmin-否 | memberType="MEMBER" | false | 分支覆盖 | P0 | != | | TC-0266 | IsSuperAdmin-空 | 无memberType | false | 边界 | P1 | 零值 | --- ## 七、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-0267 | **Insert** | 正常插入 | 返回Result+nil, DB有新记录 | 正常路径 | P0 | ExecCtx+缓存key清理 | | TC-0268 | **Insert** | 唯一索引冲突 | 返回DB错误(1062) | 异常路径 | P0 | MySQL uk | | TC-0269 | **Insert** | 缓存key生成正确 | 验证清理的缓存key包含主键和唯一索引 | 功能验证 | P0 | cacheSys*Prefix | | TC-0270 | **InsertWithTx** | 事务内插入 | 使用session执行, 返回Result | 正常路径 | P0 | session.ExecCtx | | TC-0271 | **InsertWithTx** | 事务回滚后无数据 | 事务内Insert+外部回滚→DB无记录 | 事务验证 | P0 | TransactCtx | | TC-0272 | **FindOne** | 正常查询(缓存未命中) | 返回记录, 缓存已写入 | 正常路径 | P0 | QueryRowCtx→DB | | TC-0273 | **FindOne** | 正常查询(缓存命中) | 不触发DB查询, 返回缓存数据 | 正常路径 | P0 | QueryRowCtx→cache | | TC-0274 | **FindOne** | 记录不存在 | 返回ErrNotFound | 异常路径 | P0 | sqlc.ErrNotFound→ErrNotFound | | TC-0275 | **FindOne** | DB异常(非ErrNotFound) | 返回原始error | 异常路径 | P1 | default分支 | | TC-0276 | **FindOneWithTx** | 事务内正常查询 | 使用session.QueryRowCtx, 返回记录 | 正常路径 | P0 | session直查无缓存 | | TC-0277 | **FindOneWithTx** | 事务内记录不存在 | 返回ErrNotFound | 异常路径 | P0 | sqlx.ErrNotFound | | TC-0278 | **FindOneWithTx** | 事务内可见性 | InsertWithTx后FindOneWithTx可读到 | 事务验证 | P0 | 同session内可见 | | TC-0279 | **Update** | 正常更新 | 旧缓存key+新缓存key均被清理 | 正常路径 | P0 | FindOne→ExecCtx | | TC-0280 | **Update** | 记录不存在 | FindOne失败→返回ErrNotFound | 异常路径 | P0 | FindOne err | | TC-0281 | **UpdateWithTx** | 事务内更新 | 使用session, 缓存被清理 | 正常路径 | P0 | session.ExecCtx | | TC-0282 | **Delete** | 正常删除 | 记录被删, 缓存key被清理 | 正常路径 | P0 | FindOne→ExecCtx DELETE | | TC-0283 | **Delete** | 记录不存在 | FindOne失败→返回ErrNotFound | 异常路径 | P0 | FindOne err | | TC-0284 | **DeleteWithTx** | 事务内删除 | 使用session, 缓存被清理 | 正常路径 | P0 | session.ExecCtx | | TC-0285 | **TransactCtx** | 正常事务 | fn执行成功→提交 | 正常路径 | P0 | conn.TransactCtx | | TC-0286 | **TransactCtx** | fn返回错误 | 自动回滚 | 异常路径 | P0 | 回滚 | | TC-0287 | **TableName** | 获取表名 | 返回正确表名(如 `` `sys_user` ``) | 正常路径 | P0 | m.table | ### 7.2 批量插入方法 | TC编号 | 方法 | 测试场景 | 预期结果 | 类型 | 优先级 | 覆盖说明 | | :--- | :--- | :--- | :--- | :--- | :--- | :--- | | TC-0288 | **BatchInsert** | 空列表 | 直接返回nil, 不执行SQL | 边界 | P0 | len==0 early return | | TC-0289 | **BatchInsert** | 单条记录 | 生成1组VALUES, 执行成功 | 正常路径 | P0 | 单条 | | TC-0290 | **BatchInsert** | 多条记录(3条) | 生成3组VALUES, SQL正确, 缓存key全清理 | 正常路径 | P0 | 多条+缓存 | | TC-0291 | **BatchInsert** | 唯一索引冲突 | 全部失败, 返回DB错误 | 异常路径 | P0 | MySQL uk | | TC-0292 | **BatchInsert** | 大批量(1000条) | SQL长度合理, 执行成功 | 性能 | P2 | 拼接性能 | | TC-0293 | **BatchInsertWithTx** | 空列表 | 直接返回nil | 边界 | P0 | len==0 | | TC-0294 | **BatchInsertWithTx** | 正常多条 | 使用session执行 | 正常路径 | P0 | session.ExecCtx | | TC-0295 | **BatchInsertWithTx** | 事务回滚 | 外部回滚→无新记录 | 事务验证 | P0 | TransactCtx | ### 7.3 批量更新方法 | TC编号 | 方法 | 测试场景 | 预期结果 | 类型 | 优先级 | 覆盖说明 | | :--- | :--- | :--- | :--- | :--- | :--- | :--- | | TC-0296 | **BatchUpdate** | 空列表 | 直接返回nil | 边界 | P0 | len==0 early return | | TC-0297 | **BatchUpdate** | 单条记录 | CASE-WHEN SQL正确, 更新成功 | 正常路径 | P0 | buildBatchUpdateQuery 单条 | | TC-0298 | **BatchUpdate** | 多条记录(3条) | CASE-WHEN生成3个WHEN子句, 旧缓存key全清理 | 正常路径 | P0 | buildBatchUpdateQuery 多条 | | TC-0299 | **BatchUpdate** | 部分id不存在 | findListByPrimaryKeys返回部分→仅清理存在的缓存 | 边界 | P1 | oldList可能少于dataList | | TC-0300 | **BatchUpdateWithTx** | 空列表 | 直接返回nil | 边界 | P0 | len==0 | | TC-0301 | **BatchUpdateWithTx** | 正常多条 | 使用session执行 | 正常路径 | P0 | session.ExecCtx | | TC-0302 | **buildBatchUpdateQuery** | 单条 | SQL: `UPDATE SET field=CASE WHEN id=? THEN ? ELSE field END WHERE id IN (?)` | 功能验证 | P0 | SQL结构 | | TC-0303 | **buildBatchUpdateQuery** | 多条 | 每个字段均有多个WHEN子句, WHERE IN含全部id | 功能验证 | P0 | SQL正确性 | | TC-0304 | **buildBatchUpdateQuery** | vals数量正确 | vals = N*(fields*2) + N (WHERE IN) | 功能验证 | P0 | 参数计数 | ### 7.4 批量删除方法 | TC编号 | 方法 | 测试场景 | 预期结果 | 类型 | 优先级 | 覆盖说明 | | :--- | :--- | :--- | :--- | :--- | :--- | :--- | | TC-0305 | **BatchDelete** | 空ids | 直接返回nil | 边界 | P0 | len==0 early return | | TC-0306 | **BatchDelete** | 单个id | DELETE WHERE id IN (?), 缓存清理 | 正常路径 | P0 | 单条 | | TC-0307 | **BatchDelete** | 多个id(3个) | 3个占位符, 旧数据查询→缓存key全清理 | 正常路径 | P0 | findListByPrimaryKeys | | TC-0308 | **BatchDelete** | 包含不存在id | findListByPrimaryKeys返回部分, 不报错 | 边界 | P1 | 部分存在 | | TC-0309 | **BatchDeleteWithTx** | 空ids | 直接返回nil | 边界 | P0 | len==0 | | TC-0310 | **BatchDeleteWithTx** | 正常多条 | 使用session执行 | 正常路径 | P0 | session.ExecCtx | ### 7.5 唯一索引查询方法 (按 Model 差异) | TC编号 | Model | 方法 | 测试场景 | 预期结果 | 优先级 | 覆盖说明 | | :--- | :--- | :--- | :--- | :--- | :--- | :--- | | TC-0311 | SysUser | FindOneByUsername | 正常查询 | 返回用户, 缓存写入 (索引缓存→主键缓存双层) | P0 | QueryRowIndexCtx | | TC-0312 | SysUser | FindOneByUsername | 不存在 | 返回ErrNotFound | P0 | sqlc.ErrNotFound | | TC-0313 | SysUser | FindOneByUsernameWithTx | 事务内正常查询 | 返回用户, 使用session直查 | P0 | session.QueryRowCtx | | TC-0314 | SysUser | FindOneByUsernameWithTx | 事务内不存在 | 返回ErrNotFound | P0 | sqlx.ErrNotFound | | TC-0315 | SysProduct | FindOneByAppKey | 正常查询 | 返回产品 | P0 | appKey唯一索引 | | TC-0316 | SysProduct | FindOneByAppKey | 不存在 | 返回ErrNotFound | P0 | | | TC-0317 | SysProduct | FindOneByAppKeyWithTx | 事务内正常查询 | 返回产品 | P0 | session直查 | | TC-0318 | SysProduct | FindOneByAppKeyWithTx | 事务内不存在 | 返回ErrNotFound | P0 | | | TC-0319 | SysProduct | FindOneByCode | 正常查询 | 返回产品 | P0 | code唯一索引 | | TC-0320 | SysProduct | FindOneByCode | 不存在 | 返回ErrNotFound | P0 | | | TC-0321 | SysProduct | FindOneByCodeWithTx | 事务内正常查询 | 返回产品 | P0 | session直查 | | TC-0322 | SysProduct | FindOneByCodeWithTx | 事务内不存在 | 返回ErrNotFound | P0 | | | TC-0323 | SysPerm | FindOneByProductCodeCode | 正常查询 | 返回权限(复合唯一索引) | P0 | productCode+code | | TC-0324 | SysPerm | FindOneByProductCodeCode | 不存在 | 返回ErrNotFound | P0 | | | TC-0325 | SysPerm | FindOneByProductCodeCodeWithTx | 事务内正常查询 | 返回权限 | P0 | session直查 | | TC-0326 | SysPerm | FindOneByProductCodeCodeWithTx | 事务内不存在 | 返回ErrNotFound | P0 | | | TC-0327 | SysRole | FindOneByProductCodeName | 正常查询 | 返回角色(复合唯一索引) | P0 | productCode+name | | TC-0328 | SysRole | FindOneByProductCodeName | 不存在 | 返回ErrNotFound | P0 | | | TC-0329 | SysRole | FindOneByProductCodeNameWithTx | 事务内正常查询 | 返回角色 | P0 | session直查 | | TC-0330 | SysRole | FindOneByProductCodeNameWithTx | 事务内不存在 | 返回ErrNotFound | P0 | | | TC-0331 | SysRolePerm | FindOneByRoleIdPermId | 正常查询 | 返回关联记录 | P0 | roleId+permId | | TC-0332 | SysRolePerm | FindOneByRoleIdPermId | 不存在 | 返回ErrNotFound | P0 | | | TC-0333 | SysRolePerm | FindOneByRoleIdPermIdWithTx | 事务内正常查询 | 返回关联记录 | P0 | session直查 | | TC-0334 | SysRolePerm | FindOneByRoleIdPermIdWithTx | 事务内不存在 | 返回ErrNotFound | P0 | | | TC-0335 | SysUserPerm | FindOneByUserIdPermId | 正常查询 | 返回关联记录 | P0 | userId+permId | | TC-0336 | SysUserPerm | FindOneByUserIdPermId | 不存在 | 返回ErrNotFound | P0 | | | TC-0337 | SysUserPerm | FindOneByUserIdPermIdWithTx | 事务内正常查询 | 返回关联记录 | P0 | session直查 | | TC-0338 | SysUserPerm | FindOneByUserIdPermIdWithTx | 事务内不存在 | 返回ErrNotFound | P0 | | | TC-0339 | SysUserRole | FindOneByUserIdRoleId | 正常查询 | 返回关联记录 | P0 | userId+roleId | | TC-0340 | SysUserRole | FindOneByUserIdRoleId | 不存在 | 返回ErrNotFound | P0 | | | TC-0341 | SysUserRole | FindOneByUserIdRoleIdWithTx | 事务内正常查询 | 返回关联记录 | P0 | session直查 | | TC-0342 | SysUserRole | FindOneByUserIdRoleIdWithTx | 事务内不存在 | 返回ErrNotFound | P0 | | | TC-0343 | SysProductMember | FindOneByProductCodeUserId | 正常查询 | 返回成员记录 | P0 | productCode+userId | | TC-0344 | SysProductMember | FindOneByProductCodeUserId | 不存在 | 返回ErrNotFound | P0 | | | TC-0345 | SysProductMember | FindOneByProductCodeUserIdWithTx | 事务内正常查询 | 返回成员记录 | P0 | session直查 | | TC-0346 | SysProductMember | FindOneByProductCodeUserIdWithTx | 事务内不存在 | 返回ErrNotFound | P0 | | ### 7.6 内部辅助方法 | TC编号 | 方法 | 测试场景 | 预期结果 | 类型 | 优先级 | 覆盖说明 | | :--- | :--- | :--- | :--- | :--- | :--- | :--- | | TC-0347 | **findListByPrimaryKeys** | 空ids | 返回空slice, 不执行SQL | 边界 | P0 | len==0 | | TC-0348 | **findListByPrimaryKeys** | 正常ids | 返回匹配记录(无缓存) | 正常路径 | P0 | QueryRowsNoCacheCtx | | TC-0349 | **findListByPrimaryKeys** | 部分不存在 | 仅返回存在的记录 | 边界 | P1 | IN查询 | | TC-0350 | **findListByPrimaryKeys** | DB异常 | 返回nil, err | 异常路径 | P1 | err透传 | | TC-0351 | **getPrimaryKeyValue** | 正常 | 返回data.Id | 功能验证 | P0 | interface{} | | TC-0352 | **formatPrimary** | 正常 | 返回 "cache:sysXxx:id:{id}" | 功能验证 | P0 | 缓存key格式 | | TC-0353 | **queryPrimary** | 正常 | 执行 SELECT WHERE id=? | 功能验证 | P0 | SQL | ### 7.7 缓存key与前缀初始化 | TC编号 | 测试场景 | 输入 | 预期结果 | 类型 | 优先级 | 覆盖说明 | | :--- | :--- | :--- | :--- | :--- | :--- | :--- | | TC-0354 | cachePrefix为空 | cachePrefix="" | 使用默认前缀 (如 "cache:sysUser:id:") | 分支覆盖 | P0 | if cachePrefix!="" 未进入 | | TC-0355 | cachePrefix非空 | cachePrefix="test" | 前缀变为 "test:cache:sysUser:id:" | 分支覆盖 | P0 | if cachePrefix!="" 进入 | | TC-0356 | 多唯一索引前缀(SysProduct) | cachePrefix="test" | 3个缓存前缀均更新: id/appKey/code | 功能验证 | P0 | 3个变量均修改 | --- ## 八、Model 层自定义方法测试用例 ### 8.1 SysUserModel | TC编号 | 方法 | 测试场景 | 输入 | 预期结果 | 类型 | 优先级 | 覆盖说明 | | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | | TC-0357 | FindListByPage | 正常分页 | page=1, pageSize=10, DB有20条 | 返回10条+total=20 | 正常路径 | P0 | count+limit offset | | TC-0358 | FindListByPage | 第二页 | page=2, pageSize=10 | offset=10, 返回后10条 | 正常路径 | P0 | (page-1)*pageSize | | TC-0359 | FindListByPage | 空表 | 无数据 | total=0, list为空 | 边界 | P0 | count=0 | | TC-0360 | FindListByPage | count查询失败 | DB异常 | 返回0,0,err | 异常路径 | P1 | 第一个err | | TC-0361 | FindListByPage | list查询失败 | DB异常 | 返回0,total,err | 异常路径 | P1 | 第二个err | | TC-0362 | FindListByDeptIds | 正常查询 | deptIds=[1,2], page=1, pageSize=10 | 返回属于dept 1或2的用户 | 正常路径 | P0 | WHERE deptId IN | | TC-0363 | FindListByDeptIds | 空deptIds | deptIds=[] | 返回nil,0,nil | 边界 | P0 | len==0 early return | | TC-0364 | FindListByDeptIds | 单个deptId | deptIds=[1] | IN(?)单值 | 正常路径 | P1 | 单值 | | TC-0365 | FindListByDeptIds | deptId不存在 | deptIds=[9999] | total=0, list空 | 边界 | P1 | 无匹配 | | TC-0366 | FindByIds | 正常批量查询 | ids=[1,2,3] | 返回3条 | 正常路径 | P0 | IN查询 | | TC-0367 | FindByIds | 空ids | ids=[] | 返回nil,nil | 边界 | P0 | len==0 | | TC-0368 | FindByIds | 部分id不存在 | ids=[1,9999] | 仅返回存在的 | 边界 | P1 | IN不报错 | | TC-0369 | FindByIds | DB异常 | 连接失败 | 返回nil,err | 异常路径 | P1 | err透传 | ### 8.2 SysProductModel | TC编号 | 方法 | 测试场景 | 输入 | 预期结果 | 类型 | 优先级 | 覆盖说明 | | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | | TC-0370 | FindList | 正常分页 | page=1, pageSize=10 | 返回list+total | 正常路径 | P0 | count+limit | | TC-0371 | FindList | 空表 | 无数据 | total=0, list空 | 边界 | P0 | | | TC-0372 | FindList | count失败 | DB异常 | 返回err | 异常路径 | P1 | 第一个err | ### 8.3 SysPermModel | TC编号 | 方法 | 测试场景 | 输入 | 预期结果 | 类型 | 优先级 | 覆盖说明 | | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | | TC-0373 | FindListByProductCode | 正常分页 | productCode="p1", page=1, pageSize=10 | list+total | 正常路径 | P0 | WHERE productCode=? | | TC-0374 | FindListByProductCode | 不存在的productCode | "notexist" | total=0, list空 | 边界 | P1 | | | TC-0375 | FindAllByProductCode | 正常查询(仅status=1) | DB有status=1和status=2 | 仅返回status=1 | 正常路径 | P0 | AND status=1 | | TC-0376 | FindAllByProductCode | 无启用权限 | 全部status=2 | 返回空 | 边界 | P1 | | | TC-0377 | FindAllCodesByProductCode | 正常查询 | DB有3条启用权限 | 返回3个code | 正常路径 | P0 | SELECT code WHERE status=1 | | TC-0378 | FindAllCodesByProductCode | 空结果 | 无匹配 | 空slice | 边界 | P1 | | | TC-0379 | FindByIds | 正常 | ids=[1,2] | 返回2条 | 正常路径 | P0 | IN查询 | | TC-0380 | FindByIds | 空ids | [] | 返回nil,nil | 边界 | P0 | len==0 | | TC-0381 | FindMapByProductCode | 正常查询 | productCode="p1" | map[code]*SysPerm, key为code | 正常路径 | P0 | result[p.Code]=p | | TC-0382 | FindMapByProductCode | 空结果 | 无匹配 | 空map | 边界 | P1 | | | TC-0383 | FindMapByProductCode | key唯一性 | 同productCode有3条 | map长度=3 | 功能验证 | P0 | code唯一 | | TC-0384 | DisableNotInCodes | codes非空-正常 | productCode="p1", codes=["a","b"], DB有a/b/c | 禁用c, 返回affected=1 | 正常路径 | P0 | NOT IN | | TC-0385 | DisableNotInCodes | codes为空-全部禁用 | codes=[] | 全部已启用的被禁用 | 分支覆盖 | P0 | len==0分支 | | TC-0386 | DisableNotInCodes | 无需禁用 | codes包含所有已启用 | affected=0 | 边界 | P1 | 0行更新 | | TC-0387 | DisableNotInCodes | DB异常 | 连接失败 | 返回0,err | 异常路径 | P1 | err | ### 8.4 SysDeptModel | TC编号 | 方法 | 测试场景 | 输入 | 预期结果 | 类型 | 优先级 | 覆盖说明 | | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | | TC-0388 | FindAll | 正常查询 | DB有5条 | 返回5条, 按sort asc排序 | 正常路径 | P0 | ORDER BY sort, id | | TC-0389 | FindAll | 空表 | 无数据 | 空slice | 边界 | P0 | | | TC-0390 | FindByParentId | 正常查询 | parentId=1 | 返回子部门列表 | 正常路径 | P0 | WHERE parentId=? | | TC-0391 | FindByParentId | 无子部门 | parentId=999 | 空slice | 边界 | P1 | | | TC-0392 | FindByPathPrefix | 正常查询 | pathPrefix="/1/" | 返回路径以/1/开头的部门 | 正常路径 | P0 | LIKE pathPrefix% | | TC-0393 | FindByPathPrefix | LIKE注入已阻止 | pathPrefix含% | 空slice(%和_已转义,不作为通配符) | 安全 | P1 | NewReplacer转义 | | TC-0394 | FindByPathPrefix | 无匹配 | "/999/" | 空slice | 边界 | P1 | | ### 8.5 SysRoleModel | TC编号 | 方法 | 测试场景 | 输入 | 预期结果 | 类型 | 优先级 | 覆盖说明 | | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | | TC-0395 | FindListByProductCode | 正常分页 | productCode="p1" | 按permsLevel asc排序 | 正常路径 | P0 | ORDER BY permsLevel, id | | TC-0396 | FindListByProductCode | 空结果 | 无匹配 | total=0 | 边界 | P1 | | | TC-0397 | FindByIds | 正常 | ids=[1,2] | 返回2条 | 正常路径 | P0 | IN查询 | | TC-0398 | FindByIds | 空ids | [] | 返回nil,nil | 边界 | P0 | len==0 | ### 8.6 SysRolePermModel | TC编号 | 方法 | 测试场景 | 输入 | 预期结果 | 类型 | 优先级 | 覆盖说明 | | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | | TC-0399 | FindPermIdsByRoleId | 正常查询 | roleId=1, DB有3条 | 返回3个permId | 正常路径 | P0 | SELECT permId WHERE roleId=? | | TC-0400 | FindPermIdsByRoleId | 无绑定 | roleId=999 | 空slice | 边界 | P1 | | | TC-0401 | FindPermIdsByRoleIds | 正常查询 | roleIds=[1,2] | 返回去重后的permId | 正常路径 | P0 | DISTINCT + IN | | TC-0402 | FindPermIdsByRoleIds | 空roleIds | [] | 返回nil,nil | 边界 | P0 | len==0 | | TC-0403 | FindPermIdsByRoleIds | 去重验证 | 两角色有相同permId | 结果中permId不重复 | 功能验证 | P0 | DISTINCT | | TC-0404 | DeleteByRoleId | 正常删除 | roleId=1 | 删除该角色所有权限绑定 | 正常路径 | P0 | ExecNoCacheCtx (不清缓存) | | TC-0405 | DeleteByRoleId | 无绑定 | roleId=999 | 删0行, 不报错 | 边界 | P1 | | | TC-0406 | DeleteByRoleIdTx | 正常事务内删除 | session+roleId | 使用session执行 | 正常路径 | P0 | session.ExecCtx | ### 8.7 SysUserPermModel | TC编号 | 方法 | 测试场景 | 输入 | 预期结果 | 类型 | 优先级 | 覆盖说明 | | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | | TC-0407 | FindByUserId | 正常查询 | userId=1 | 返回该用户所有权限配置 | 正常路径 | P0 | WHERE userId=? | | TC-0408 | FindByUserId | 无记录 | userId=999 | 空slice | 边界 | P1 | | | TC-0409 | FindPermIdsByUserIdAndEffect | ALLOW | userId=1, effect="ALLOW" | 返回ALLOW的permIds | 正常路径 | P0 | AND effect=? | | TC-0410 | FindPermIdsByUserIdAndEffect | DENY | userId=1, effect="DENY" | 返回DENY的permIds | 正常路径 | P0 | effect="DENY" | | TC-0411 | FindPermIdsByUserIdAndEffect | 无记录 | 无匹配 | 空slice | 边界 | P1 | | | TC-0412 | DeleteByUserId | 正常删除 | userId=1 | 删除该用户所有权限配置 | 正常路径 | P0 | ExecNoCacheCtx | | TC-0413 | DeleteByUserIdTx | 事务内删除 | session+userId | 使用session | 正常路径 | P0 | session.ExecCtx | | TC-0414 | DeleteByUserIdForProduct | 正常删除 | userId=1, productCode="p1" | 仅删除该产品下的权限配置 | 正常路径 | P0 | 子查询 sys_perm.productCode | | TC-0415 | DeleteByUserIdForProduct | 跨产品隔离 | 用户在多产品有配置 | 仅删目标产品的 | 深度业务 | P0 | 子查询隔离 | | TC-0416 | DeleteByUserIdForProductTx | 事务内跨产品删除 | session+userId+productCode | 使用session | 正常路径 | P0 | session.ExecCtx | ### 8.8 SysUserRoleModel | TC编号 | 方法 | 测试场景 | 输入 | 预期结果 | 类型 | 优先级 | 覆盖说明 | | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | | TC-0417 | FindRoleIdsByUserId | 正常查询 | userId=1, DB有3条 | 返回3个roleId | 正常路径 | P0 | SELECT roleId | | TC-0418 | FindRoleIdsByUserId | 无绑定 | userId=999 | 空slice | 边界 | P1 | | | TC-0419 | FindByUserId | 正常查询 | userId=1 | 返回完整SysUserRole列表 | 正常路径 | P0 | SELECT * | | TC-0420 | DeleteByUserId | 正常删除 | userId=1 | 删除该用户所有角色绑定 | 正常路径 | P0 | ExecNoCacheCtx | | TC-0421 | DeleteByUserIdTx | 事务内删除 | session+userId | 使用session | 正常路径 | P0 | session.ExecCtx | | TC-0422 | DeleteByRoleIdTx | 正常删除 | session+roleId | 删除该角色的所有用户绑定 | 正常路径 | P0 | session.ExecCtx | | TC-0423 | DeleteByUserIdForProduct | 正常删除 | userId=1, productCode="p1" | 仅删该产品下角色绑定 | 正常路径 | P0 | 子查询 sys_role.productCode | | TC-0424 | DeleteByUserIdForProduct | 跨产品隔离 | 用户在多产品有角色 | 仅删目标产品的 | 深度业务 | P0 | 子查询隔离 | | TC-0425 | DeleteByUserIdForProductTx | 事务内跨产品删除 | session+userId+productCode | 使用session | 正常路径 | P0 | session.ExecCtx | ### 8.9 SysProductMemberModel | TC编号 | 方法 | 测试场景 | 输入 | 预期结果 | 类型 | 优先级 | 覆盖说明 | | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | | TC-0426 | FindListByProductCode | 正常分页 | productCode="p1" | list+total | 正常路径 | P0 | WHERE productCode=? | | TC-0427 | FindListByProductCode | 空结果 | 无匹配 | total=0 | 边界 | P1 | | | TC-0428 | FindByUserId | 正常查询 | userId=1 | 返回用户所有产品成员身份 | 正常路径 | P0 | WHERE userId=? | | TC-0429 | FindByUserId | 无成员身份 | userId=999 | 空slice | 边界 | P1 | | | TC-0430 | FindMapByProductCodeUserIds | 正常批量 | productCode="p1", userIds=[1,2] | map key=userId | 正常路径 | P0 | IN+productCode | | TC-0431 | FindMapByProductCodeUserIds | 空userIds | [] | 返回空map | 边界 | P0 | len==0 | | TC-0432 | FindMapByProductCodeUserIds | 部分不是成员 | userIds含非成员 | map仅含成员 | 边界 | P1 | | | TC-0433 | FindMapByProductCodeUserIds | map key正确 | 查询结果 | key=userId, val=*SysProductMember | 功能验证 | P0 | result[pm.UserId] | ## 九、访问控制 (auth/access.go) ### 9.1 RequireSuperAdmin | TC编号 | 测试场景 | 输入 | 预期结果 | 类型 | 优先级 | 覆盖说明 | | :--- | :--- | :--- | :--- | :--- | :--- | :--- | | TC-0435 | 超管通过 | ctx含SuperAdmin UserDetails | nil (允许) | 正常路径 | P0 | caller.IsSuperAdmin | | TC-0436 | 非超管拒绝 | ctx含ADMIN UserDetails | 403 "仅超级管理员" | 异常路径 | P0 | !IsSuperAdmin | | TC-0437 | MEMBER拒绝 | ctx含MEMBER UserDetails | 403 "仅超级管理员" | 异常路径 | P0 | | | TC-0438 | 未登录 | ctx无UserDetails | 401 "未登录" | 边界 | P0 | caller==nil | ### 9.2 RequireProductAdmin | TC编号 | 测试场景 | 输入 | 预期结果 | 类型 | 优先级 | 覆盖说明 | | :--- | :--- | :--- | :--- | :--- | :--- | :--- | | TC-0439 | 超管通过 | ctx含SuperAdmin | nil | 正常路径 | P0 | IsSuperAdmin | | TC-0440 | ADMIN通过 | ctx含ADMIN | nil | 正常路径 | P0 | MemberType==ADMIN | | TC-0441 | DEVELOPER拒绝 | ctx含DEVELOPER | 403 | 异常路径 | P0 | 非Admin | | TC-0442 | MEMBER拒绝 | ctx含MEMBER | 403 | 异常路径 | P0 | 非Admin | | TC-0443 | 未登录 | ctx无UserDetails | 401 | 边界 | P0 | caller==nil | ### 9.3 CheckMemberTypeAssignment | TC编号 | 测试场景 | 输入 | 预期结果 | 类型 | 优先级 | 覆盖说明 | | :--- | :--- | :--- | :--- | :--- | :--- | :--- | | TC-0444 | 超管可分配任何类型 | caller=SuperAdmin, assigned=ADMIN | nil | 正常路径 | P0 | IsSuperAdmin豁免 | | TC-0445 | ADMIN分配DEVELOPER | caller=ADMIN, assigned=DEVELOPER | nil | 正常路径 | P0 | callerPri(1) < assignPri(2) | | TC-0446 | ADMIN分配ADMIN(同级拒绝) | caller=ADMIN, assigned=ADMIN | 403 | 深度业务 | P0 | callerPri >= assignPri | | TC-0447 | DEVELOPER分配ADMIN(越级拒绝) | caller=DEVELOPER, assigned=ADMIN | 403 | 深度业务 | P0 | callerPri > assignPri | | TC-0448 | MEMBER分配MEMBER(同级拒绝) | caller=MEMBER, assigned=MEMBER | 403 | 深度业务 | P0 | | | TC-0449 | 未登录 | ctx无UserDetails | 401 | 边界 | P0 | | ### 9.4 CheckManageAccess | TC编号 | 测试场景 | 输入 | 预期结果 | 类型 | 优先级 | 覆盖说明 | | :--- | :--- | :--- | :--- | :--- | :--- | :--- | | TC-0450 | 超管可管理任何人 | caller=SuperAdmin | nil | 正常路径 | P0 | IsSuperAdmin豁免 | | TC-0451 | 操作自己 | caller.UserId==targetUserId | nil | 正常路径 | P0 | self豁免 | | TC-0452 | ADMIN跳过部门检查 | caller=ADMIN | nil (直接比级别) | 深度业务 | P0 | checkDeptHierarchy ADMIN豁免 | | TC-0453 | 非ADMIN无部门拒绝 | caller.DeptId=0 | 403 "未归属部门" | 边界 | P0 | caller.DeptId==0 | | TC-0454 | 目标用户无部门 | target.DeptId=0 | 403 "目标用户未归属部门" | 边界 | P0 | target.DeptId==0 | | TC-0455 | 目标在不同部门 | 目标不在caller子部门 | 403 "无权管理其他部门" | 深度业务 | P0 | !HasPrefix | | TC-0456 | 未登录 | ctx无UserDetails | 401 | 边界 | P0 | | | TC-0523 | caller.DeptPath为空时拒绝 | caller有DeptId但DeptPath="" | 403 "无权管理" | 安全 | P0 | H-08修复: DeptPath空串保护 | ### 9.5 memberTypePriority | TC编号 | 测试场景 | 输入 | 预期结果 | 类型 | 优先级 | 覆盖说明 | | :--- | :--- | :--- | :--- | :--- | :--- | :--- | | TC-0457 | 各类型优先级正确 | 全部4种+未知 | SA=0,A=1,D=2,M=3,unknown=MaxInt32 | 白盒 | P0 | switch分支 | ## 十、UserDetailsLoader (loaders/userDetailsLoader.go) ### 10.1 Load / 缓存 | TC编号 | 测试场景 | 输入 | 预期结果 | 类型 | 优先级 | 覆盖说明 | | :--- | :--- | :--- | :--- | :--- | :--- | :--- | | TC-0458 | DB加载(缓存miss) | 有效userId+productCode | 返回完整UserDetails | 正常路径 | P0 | loadFromDB全链路 | | TC-0459 | 缓存命中 | 第二次Load同key | 从Redis返回,不查DB | 正常路径 | P0 | GetCtx hit | | TC-0460 | 用户不存在 | userId=999999 | 返回零值UserDetails(Status=0) | 边界 | P0 | loadUser失败 | | TC-0461 | productCode为空 | productCode="" | 跳过产品/成员/角色/权限加载 | 边界 | P1 | 各方法guard | ### 10.2 缓存失效 | TC编号 | 测试场景 | 输入 | 预期结果 | 类型 | 优先级 | 覆盖说明 | | :--- | :--- | :--- | :--- | :--- | :--- | :--- | | TC-0462 | Del删除指定缓存 | Del(uid, pc) | 缓存被删除,下次Load查DB | 正常路径 | P0 | DelCtx | | TC-0463 | Clean清除用户所有产品缓存 | Clean(uid) | 该用户所有key被删 | 正常路径 | P0 | KEYS pattern | | TC-0464 | CleanByProduct清除产品所有用户 | CleanByProduct(pc) | 该产品所有key被删 | 正常路径 | P0 | KEYS pattern | | TC-0465 | BatchDel批量删除 | BatchDel([uid1,uid2], pc) | 多个key被删 | 正常路径 | P0 | DelCtx多key | | TC-0466 | BatchDel空数组 | BatchDel([], pc) | 无操作 | 边界 | P1 | len==0 guard | ### 10.3 loadPerms权限计算 | TC编号 | 测试场景 | 输入 | 预期结果 | 类型 | 优先级 | 覆盖说明 | | :--- | :--- | :--- | :--- | :--- | :--- | :--- | | TC-0467 | 超管全量权限 | IsSuperAdmin=true | Perms=全部启用的权限码 | 正常路径 | P0 | 超管分支 | | TC-0468 | ADMIN全量权限 | MemberType=ADMIN | Perms=全量 | 正常路径 | P0 | ADMIN分支 | | TC-0469 | DEVELOPER全量权限 | MemberType=DEVELOPER | Perms=全量 | 正常路径 | P0 | DEVELOPER分支 | | TC-0470 | DEV部门全量权限 | DeptType=DEV | Perms=全量 | 正常路径 | P0 | DeptTypeDev分支 | | TC-0471 | MEMBER角色权限+ALLOW-DENY | 有角色+ALLOW+DENY | 正确计算 | 深度业务 | P0 | denySet过滤 | ### 10.4 loadRoles + MinPermsLevel | TC编号 | 测试场景 | 输入 | 预期结果 | 类型 | 优先级 | 覆盖说明 | | :--- | :--- | :--- | :--- | :--- | :--- | :--- | | TC-0472 | 多角色取最小permsLevel | 用户有level=10和level=5的角色 | MinPermsLevel=5 | 正常路径 | P0 | min计算 | | TC-0473 | 无角色 | 用户无角色 | MinPermsLevel=MaxInt64 | 边界 | P0 | 默认值 | | TC-0474 | 角色跨产品过滤 | 角色在不同产品 | 仅加载当前产品角色 | 深度业务 | P0 | productCode过滤 | | TC-0475 | 禁用角色不计入 | 角色status=2 | 不在Roles列表中 | 深度业务 | P0 | Status==Enabled | ### 10.5 loadMembership | TC编号 | 测试场景 | 输入 | 预期结果 | 类型 | 优先级 | 覆盖说明 | | :--- | :--- | :--- | :--- | :--- | :--- | :--- | | TC-0476 | 超管自动设置SUPER_ADMIN | IsSuperAdmin=true | MemberType=SUPER_ADMIN, 不查DB | 正常路径 | P0 | 早期return | | TC-0477 | 非成员MemberType为空 | 用户非该产品成员 | MemberType="" | 边界 | P0 | ErrNotFound | ## 十一、中间件 — 冻结账号拦截 | TC编号 | 测试场景 | 输入 | 预期结果 | 类型 | 优先级 | 覆盖说明 | | :--- | :--- | :--- | :--- | :--- | :--- | :--- | | TC-0478 | 冻结用户被403 | 有效token但Status=2 | code=403 "账号已被冻结" | 安全 | P0 | ud.Status!=Enabled | | TC-0479 | 用户不存在(Status=0) | token中userId不存在 | code=403 "账号已被冻结" | 安全 | P0 | loadUser失败→Status=0 | | TC-0480 | UserDetails注入context | 正常请求 | GetUserDetails(ctx)非nil | 正常路径 | P0 | WithUserDetails | ## 十二、Logic层 — 访问控制负面测试 | TC编号 | 测试场景 | 输入 | 预期结果 | 类型 | 优先级 | 覆盖说明 | | :--- | :--- | :--- | :--- | :--- | :--- | :--- | | TC-0481 | createDept非超管拒绝 | ctx=ADMIN | 403 "仅超级管理员" | 安全 | P0 | RequireSuperAdmin | | TC-0482 | updateDept非超管拒绝 | ctx=ADMIN | 403 "仅超级管理员" | 安全 | P0 | RequireSuperAdmin | | TC-0483 | deleteDept非超管拒绝 | ctx=ADMIN | 403 "仅超级管理员" | 安全 | P0 | RequireSuperAdmin | | TC-0484 | createProduct非超管拒绝 | ctx=ADMIN | 403 "仅超级管理员" | 安全 | P0 | RequireSuperAdmin | | TC-0485 | updateProduct非超管拒绝 | ctx=ADMIN | 403 "仅超级管理员" | 安全 | P0 | RequireSuperAdmin | | TC-0486 | createUser非产品管理员拒绝 | ctx=MEMBER | 403 "仅超级管理员或产品管理员" | 安全 | P0 | RequireProductAdmin | | TC-0487 | createRole非产品管理员拒绝 | ctx=MEMBER | 403 | 安全 | P0 | RequireProductAdmin | | TC-0488 | updateRole非产品管理员拒绝 | ctx=MEMBER | 403 | 安全 | P0 | RequireProductAdmin | | TC-0489 | deleteRole非产品管理员拒绝 | ctx=MEMBER | 403 | 安全 | P0 | RequireProductAdmin | | TC-0490 | bindRolePerms非产品管理员拒绝 | ctx=MEMBER | 403 | 安全 | P0 | RequireProductAdmin | | TC-0491 | updateUser非本人非超管拒绝 | ctx=MEMBER, id!=self | 403 "仅允许修改自己的信息或超管操作" | 安全 | P0 | H-01修复: 非超管不能改他人 | | TC-0511 | updateUser自己修改DeptId被拒绝 | ctx含userId=X, req.Id=X, req.DeptId!=nil | 403 "不允许修改自己的部门和状态" | 安全 | P0 | H-01修复: 自编辑限制DeptId | | TC-0512 | updateUser自己修改Status被拒绝 | ctx含userId=X, req.Id=X, req.Status!=0 | 403 "不允许修改自己的部门和状态" | 安全 | P0 | H-01修复: 自编辑限制Status | | TC-0513 | updateUser未登录被拒绝 | ctx无UserDetails | 401 "未登录" | 安全 | P0 | H-01修复: caller==nil | ## 十三、Model层 — 新增方法 ### 13.1 SysRoleModel | TC编号 | 测试场景 | 输入 | 预期结果 | 类型 | 优先级 | 覆盖说明 | | :--- | :--- | :--- | :--- | :--- | :--- | :--- | | TC-0492 | FindMinPermsLevelByUserIdAndProductCode正常 | 有角色用户 | 返回最小permsLevel | 正常路径 | P0 | MIN聚合 | | TC-0493 | FindMinPermsLevelByUserIdAndProductCode无角色 | 无角色用户 | error(ErrNotFound) | 边界 | P0 | IFNULL返回-1→level<0→ErrNotFound | ### 13.2 SysPermModel | TC编号 | 测试场景 | 输入 | 预期结果 | 类型 | 优先级 | 覆盖说明 | | :--- | :--- | :--- | :--- | :--- | :--- | :--- | | TC-0494 | FindAllCodesByProductCode正常 | 有权限产品 | 返回code列表(仅status=1) | 正常路径 | P0 | WHERE status=1 | | TC-0495 | FindAllCodesByProductCode无权限 | 无权限产品 | 空slice | 边界 | P1 | | ### 13.3 SysUserModel | TC编号 | 测试场景 | 输入 | 预期结果 | 类型 | 优先级 | 覆盖说明 | | :--- | :--- | :--- | :--- | :--- | :--- | :--- | | TC-0496 | FindIdsByDeptId正常 | 有用户的部门 | 返回id列表 | 正常路径 | P0 | WHERE deptId=? | | TC-0497 | FindIdsByDeptId空部门 | 无用户部门 | 空slice | 边界 | P1 | | ### 13.4 SysUserRoleModel | TC编号 | 测试场景 | 输入 | 预期结果 | 类型 | 优先级 | 覆盖说明 | | :--- | :--- | :--- | :--- | :--- | :--- | :--- | | TC-0498 | FindUserIdsByRoleId正常 | 有绑定的角色 | 返回userId列表 | 正常路径 | P0 | WHERE roleId=? | | TC-0499 | FindUserIdsByRoleId无绑定 | 无绑定角色 | 空slice | 边界 | P1 | | ---