test-spec.md 13 KB

单测 Spec — 组织上下文角色权限改造

status: done created: 2026-07-06

0. 测试原则

  • Red/Green TDD:权限计算、组织切换和数据权限必须先写能失败的测试,再实现通过。
  • First Run the Tests:开始前先跑现有最小相关测试或编译,记录基线。
  • 展示工作:执行后必须在 execution-log.md 记录命令、关键输出、失败原因或通过证据。
  • 增量复用:每次验证前先读取本文件、execution-log.mdspec.mdtasks.mdcode-copilot/rules/automated-testing-standard.md

1. 测试框架

项目
后端测试 JUnit 5 / Spring Boot Test / Mockito(按现有模块测试依赖为准)
前端测试 pnpm build 为 P0;如现有 Vitest 覆盖相关组件则补定向用例
数据库验证 Flyway 静态检查 + 可用 MySQL 环境下接口/SQL 验证
权限验证 后端服务测试 + 登录后接口验证

2. 覆盖范围

P0 — 核心权限计算(必须覆盖)

类名: UserLoadServiceImpl

方法 场景 输入 Mock/数据准备 预期结果
loadUserByUserId(userId, tenantId, preferredActiveOrgId) 用户在组织 A/B 分别拥有不同角色,preferred 为 A tenantId=T1, activeOrgId=A sys_user_org 有 A/B,sys_user_org_role A=R1、B=R2 LoginUser.activeOrgId=AroleIds=[R1],不包含 R2
同上 preferred 不属于用户组织 activeOrgId=X 用户组织只有 A/B,主组织 B 当前组织回退 B
同上 当前组织无角色 activeOrgId=A A 无 sys_user_org_role roleIds/permissions/apiPermissions 为空,不能继承 B
loadUserByUsername(username, tenantId, preferredActiveOrgId) /auth/userInfo 刷新保留当前组织 Session 原 activeOrgId=A A 仍有效 刷新后仍是 A

类名: SysOrgServiceImpl

方法 场景 输入 Mock/数据准备 预期结果
switchCurrentOrg 切到当前数据中心已加入组织 orgId=A 用户属于 A 返回重算后 LoginUser,Session 写入 A
switchCurrentOrg 切到未加入组织 orgId=X 用户不属于 X 抛业务异常
switchCurrentOrg 切到其他数据中心组织 orgId=OtherTenantOrg 组织 tenant_id != currentTenantId 抛业务异常

类名: SysUserServiceImpl

方法 场景 输入 Mock/数据准备 预期结果
bindUserOrgRoles 给组织成员授权适用角色 userId=U, orgId=A, roleIds=[R1] U 属于 A,R1 适用于 A 写入 sys_user_org_role
bindUserOrgRoles 角色不适用于目标组织 orgId=B, roleIds=[R1] R1 只适用于 A 拒绝授权
bindUserOrgs 移除组织 原 A/B,保存 A B 下存在角色授权 删除 B 的 sys_user_org_role

P0 — 数据权限(必须覆盖)

类名: DataScopeServiceImpl

方法 场景 输入 Mock/数据准备 预期结果
getCurrentUserDataScope ORG 数据范围 activeOrgId=A, orgIds=[A,B] 当前角色数据范围 ORG context.orgIds=[A]
同上 ORG_AND_CHILD 数据范围 activeOrgId=A A 子组织 A1/A2 拦截器使用 A/A1/A2,不含 B
同上 无当前组织 activeOrgId=null 普通用户角色为 ORG 后续 SQL 条件强制无数据

类名: DynamicDataScopeService

方法 场景 输入 Mock/数据准备 预期结果
buildCondition 低代码 FOLLOW_SYSTEM + ORG context.orgIds=[A] 模型配置 orgColumn=org_id SQL 条件只包含 A
buildCondition ORG_AND_CHILD activeOrgId=A A 子组织 A1 SQL 条件包含 A/A1,不含 B

P1 — 数据访问层

Mapper 场景 输入 预期结果
SysUserOrgRoleMapper 按用户当前组织查启用且适用角色 tenantId,userId,orgId 只返回该组织有效角色
SysRoleMapper.selectRoleUsers 按角色和组织查询用户 roleId,orgId 只返回该组织授权用户
BusinessMessageChannelMapper.selectUserIdsByRoleIds 按当前组织角色找用户 tenantId,orgId,roleIds 不返回其他组织用户

P1 — 入口层/接口

接口 场景 请求 预期结果
POST /system/org/switch 用户切换到组织 A orgId=A 返回 activeOrgId=A,权限为 A 的角色
GET /system/org/current/options 查询可切组织 当前登录用户 只返回当前数据中心已加入组织
POST /system/user/{id}/org-roles 保存组织角色 orgId + roleIds 成功后查询回显一致
POST /system/role/{id}/orgs 保存角色适用组织 orgIds 成功后不适用组织不能分配

P1 — 前端构建和交互

页面/组件 场景 验证
OrgSwitcher.vue 多组织用户切换组织 切换后清理菜单缓存并重新加载首页
system/user.vue 用户绑定多个组织和主组织 保存后列表展示多个组织,重新打开回显
system/user.vue 用户在组织内授权角色 选择组织 A/B 分别回显不同角色
system/role.vue 角色适用组织配置 保存后可回显,并限制用户授权角色列表

P2 — 旁路回归

模块 场景 预期结果
数据集 ACL ACL 按 ROLE/ORG 授权 只匹配当前组织和当前组织角色
流程启动变量 发起流程时注入角色变量 startUserRoleIds 为当前组织角色
业务消息 角色接收人 只发给当前组织或业务指定组织内拥有该角色的用户

不测试(明确列出原因)

  • 不做跨登录“上次组织”持久化测试:本期明确不新增跨登录持久化。
  • 不做全量 E2E 回归:本变更风险集中在权限链路,按自动化测试标准优先做最小闭环和关键接口验证。
  • 不在无数据库环境强制执行 Flyway 实跑:如果本地没有 MySQL,只记录静态检查和跳过原因。

3. 执行计划

  • Step 1: 运行基线检查:git diff --check
  • Step 2: 执行后端最小编译:mvn -pl forge-admin-server -am compile -DskipTests
  • Step 3: 为 UserLoadServiceImplSysOrgServiceImplSysUserServiceImplDataScopeServiceImpl 编写 P0 测试,先确认 Red。
  • Step 4: 实现对应功能后确认 P0 测试 Green。
  • Step 5: 执行 Mapper XML 和接口级验证。
  • Step 6: 执行前端构建:source ~/.nvm/nvm.sh && nvm use v20.19.0 && NODE_OPTIONS=--max-old-space-size=8192 pnpm --dir forge-admin-ui build
  • Step 7: 启动后端并用真实登录 Token 验证切组织、菜单、API 权限和数据权限。
  • Step 8: 更新 execution-log.mdtasks.mdspec.md

4. 历史验证基线

时间 范围 命令 结果 备注
2026-07-06 建档 未执行代码验证 未执行 本轮仅生成 SDD 文档
2026-07-06 Apply 增量验证 后端编译 + 前端构建 + 静态检查 通过 未启动服务/数据库做接口实测

5. 本轮增量验证

时间 变更范围 必跑项 实际命令 结果 跳过/警告
2026-07-06 SDD 文档生成 文档结构检查 git diff --check -- code-copilot/changes/org-scoped-role-permission 通过 建档阶段轻量检查
2026-07-06 组织上下文权限改造 后端最小编译 env JAVA_HOME=/opt/homebrew/Cellar/openjdk@17/17.0.13/libexec/openjdk.jdk/Contents/Home PATH=/opt/homebrew/Cellar/openjdk@17/17.0.13/libexec/openjdk.jdk/Contents/Home/bin:$PATH mvn -pl forge-admin-server -am compile -DskipTests 通过 仅既有 deprecation/unchecked/Lombok 警告
2026-07-06 前端组织切换/用户角色/角色组织范围 前端构建 source ~/.nvm/nvm.sh && nvm use v20.19.0 && NODE_OPTIONS=--max-old-space-size=8192 pnpm --dir forge-admin-ui build 通过 仅既有 chunk/CSS/组件命名警告
2026-07-06 Flyway 脚本 占位符静态扫描 rg -n '\$\{[^}]+\}' forge-server/db/migration 通过 无输出
2026-07-06 全量 diff 空白检查 git diff --check 通过 无输出
2026-07-06 当前组织写入链路 自动填充修正后后端最小编译 env JAVA_HOME=/opt/homebrew/Cellar/openjdk@17/17.0.13/libexec/openjdk.jdk/Contents/Home PATH=/opt/homebrew/Cellar/openjdk@17/17.0.13/libexec/openjdk.jdk/Contents/Home/bin:$PATH mvn -pl forge-admin-server -am compile -DskipTests 通过 create_dept 改为优先写 activeOrgId 后复跑;仅既有 deprecation/unchecked/Lombok 警告
2026-07-06 服务级验证准备 本地 MySQL/Redis 可用性探测 mysqladmin --protocol=tcp -h127.0.0.1 -P3407 -uroot ping; mysqladmin --protocol=tcp -h127.0.0.1 -P3306 -uroot ping; redis-cli -h 127.0.0.1 -p 6379 ping 跳过接口实测 3407 MySQL 未运行;3306 root 无密码访问被拒;Redis 探测受沙箱限制且提权未获批。未启动后端,未写数据库
2026-07-06 Flyway/全量 diff 占位符扫描 + 空白检查 rg -n '\$\{[^}]+\}' forge-server/db/migration; git diff --check 通过 Flyway 扫描无输出;diff 检查无输出
2026-07-06 Review 修复:服务权限兜底、旧角色加人阻断、角色加人直接组织筛选、消息业务组织上下文 后端最小编译 env JAVA_HOME=/opt/homebrew/Cellar/openjdk@17/17.0.13/libexec/openjdk.jdk/Contents/Home PATH=/opt/homebrew/Cellar/openjdk@17/17.0.13/libexec/openjdk.jdk/Contents/Home/bin:$PATH mvn -pl forge-admin-server -am compile -DskipTests 通过 Reactor BUILD SUCCESS;仅既有 deprecation/unchecked 警告
2026-07-06 Review 修复:前端角色用户选择直接组织筛选 前端构建 source ~/.nvm/nvm.sh && nvm use v20.19.0 && NODE_OPTIONS=--max-old-space-size=8192 pnpm --dir forge-admin-ui build 通过 Node v20.19.0 可用;仅既有 CSS // 注释、动态/静态导入和组件命名冲突提示
2026-07-06 Review 修复后静态检查 占位符扫描 + 空白检查 rg -n '\$\{[^}]+\}' forge-server/db/migration; git diff --check 通过 Flyway 扫描无输出;diff 检查无输出
2026-07-06 Review 修复后服务级验证 接口/数据库实测 未启动后端服务 跳过 本轮未引入新 Flyway;当前 dev 库仍不适合作为自动验证目标,服务级真实登录/组织授权/消息落库待可控本地 MySQL/Redis 环境补跑
2026-07-06 超级管理员组织选项边界修复 后端最小编译 + 空白检查 env JAVA_HOME=/opt/homebrew/Cellar/openjdk@17/17.0.13/libexec/openjdk.jdk/Contents/Home PATH=/opt/homebrew/Cellar/openjdk@17/17.0.13/libexec/openjdk.jdk/Contents/Home/bin:$PATH mvn -pl forge-admin-server -am compile -DskipTests; git diff --check 通过 超管有组织绑定时只返回/允许切换绑定组织;仅无绑定超管保留全量兜底。未改前端,未重复跑前端构建
2026-07-06 超级管理员显式组织绑定边界二次修正 后端最小编译 + Flyway 占位符扫描 + 空白检查 env JAVA_HOME=/opt/homebrew/Cellar/openjdk@17/17.0.13/libexec/openjdk.jdk/Contents/Home PATH=/opt/homebrew/Cellar/openjdk@17/17.0.13/libexec/openjdk.jdk/Contents/Home/bin:$PATH mvn -pl forge-admin-server -am compile -DskipTests; rg -n '\$\{[^}]+\}' forge-server/db/migration; git diff --check 通过 options/switch 直接查询 sys_user_org 区分真实绑定和历史超管全量兜底;本轮未改前端,扁平下拉展示不变
2026-07-06 超级管理员组织边界 Mapper XML 收敛 后端最小编译 + Flyway 占位符扫描 + 空白检查 env JAVA_HOME=/opt/homebrew/Cellar/openjdk@17/17.0.13/libexec/openjdk.jdk/Contents/Home PATH=/opt/homebrew/Cellar/openjdk@17/17.0.13/libexec/openjdk.jdk/Contents/Home/bin:$PATH mvn -pl forge-admin-server -am compile -DskipTests; rg -n '\$\{[^}]+\}' forge-server/db/migration; git diff --check 通过 新增 SysUserOrgMapper.xmlSysOrgMapper.selectEnabledOrgsByIds,显式绑定查询不在 Service 层拼接;本轮未改前端

6. 执行证据

  • execution-log.md:记录每次验证命令、结果、跳过项、服务启动和清理情况。
  • 关键接口:/auth/login/auth/userInfo/auth/current/menu/system/org/switch/system/user/{id}/org-roles
  • 关键数据库检查:sys_role_orgsys_user_org_rolesys_user_orgsys_rolesys_role_resource
  • 服务启动与停止:只停止本轮启动的后端、前端或测试服务。