ether-docs/02-DESIGN/domains/permission-upgrade-v3/test_plan.md

600 lines
20 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 权限体系升级测试方案
**文档版本**: v1.0
**创建日期**: 2026-02-27
---
## 一、测试目标
验证权限体系升级后的功能正确性、兼容性和性能,确保:
1. 新增的13个角色体系正常工作
2. 按钮级权限控制正确
3. 状态驱动权限正确
4. 数据范围权限正确
5. 三端(管理后台、员工端、业主端)权限检查正常
6. 现有业务功能不受影响
---
## 二、测试范围
### 2.1 测试端覆盖
| 端 | 端口 | 测试重点 |
|---|------|---------|
| 管理后台 | 5175 | 完整权限管理、角色分配、权限检查 |
| 员工端 | 5174 | 执行层权限、移动端适配 |
| 业主端 | 5176 | 业主权限、房屋绑定权限 |
### 2.2 测试模块覆盖
| 模块 | 测试内容 |
|------|---------|
| 用户认证 | 登录、登出、Token刷新 |
| 角色管理 | 角色创建、角色分配、角色权限 |
| 权限检查 | 菜单权限、按钮权限、API权限 |
| 数据权限 | ALL/PROJECT/DEPARTMENT/SELF |
| 状态驱动 | 工单状态-操作映射 |
---
## 三、测试策略
### 3.1 测试层次
```
┌─────────────────────────────────────────────────────────────────┐
│ E2E 测试 │
│ (Puppeteer 自动化测试,覆盖三端核心业务流程) │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ 集成测试 │
│ (Spring Boot Test验证服务间协作和数据库交互) │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ 单元测试 │
│ (JUnit 5 + Vitest验证单个方法和组件的正确性) │
└─────────────────────────────────────────────────────────────────┘
```
### 3.2 测试数据准备
#### 3.2.1 测试账号
| 角色 | 用户名 | 密码 | 数据范围 | 用途 |
|------|--------|------|---------|------|
| 超级管理员 | super_admin | Test@123 | ALL | 全权限测试 |
| 系统管理员 | sys_admin | Test@123 | ALL | 系统配置测试 |
| 物业经理 | property_manager | Test@123 | PROJECT | 多项目管理测试 |
| 项目经理 | project_manager | Test@123 | PROJECT | 单项目管理测试 |
| 工程主管 | engineering_lead | Test@123 | DEPARTMENT | 工单管理测试 |
| 安保主管 | security_lead | Test@123 | DEPARTMENT | 安保管理测试 |
| 保洁主管 | cleaning_lead | Test@123 | DEPARTMENT | 保洁管理测试 |
| 财务主管 | finance_lead | Test@123 | DEPARTMENT | 财务管理测试 |
| 维修人员 | maintenance_staff | Test@123 | SELF | 工单执行测试 |
| 安保人员 | security_staff | Test@123 | SELF | 巡检验证测试 |
| 保洁人员 | cleaning_staff | Test@123 | SELF | 保洁任务测试 |
| 客服人员 | cs_staff | Test@123 | PROJECT | 服务支持测试 |
| 业主 | owner | Test@123 | SELF | 业主功能测试 |
#### 3.2.2 测试项目数据
```sql
-- 测试项目
INSERT INTO mdm_project (id, name, code, status) VALUES
('test-project-001', '测试小区A', 'TEST-A', 'ACTIVE'),
('test-project-002', '测试小区B', 'TEST-B', 'ACTIVE');
-- 测试楼栋
INSERT INTO mdm_space_node (id, project_id, name, node_type, parent_id) VALUES
('building-001', 'test-project-001', '1号楼', 'BUILDING', NULL),
('building-002', 'test-project-001', '2号楼', 'BUILDING', NULL);
-- 测试房间
INSERT INTO mdm_space_node (id, project_id, name, node_type, parent_id) VALUES
('room-101', 'test-project-001', '101', 'ROOM', 'building-001'),
('room-102', 'test-project-001', '102', 'ROOM', 'building-001');
```
---
## 四、单元测试方案
### 4.1 后端单元测试
#### 4.1.1 测试文件
| 测试类 | 测试内容 |
|-------|---------|
| PermissionServiceV3Test | 权限服务核心方法 |
| RoleServiceTest | 角色管理方法 |
| DataScopeHelperTest | 数据权限工具类 |
| WorkOrderStatusTest | 工单状态-操作映射 |
#### 4.1.2 测试用例
**PermissionServiceV3Test:**
```java
@Test
@DisplayName("测试数据范围枚举包含PROJECT")
void testDataScopeContainsProject() {
Role.DataScope[] scopes = Role.DataScope.values();
assertTrue(List.of(scopes).contains(Role.DataScope.PROJECT));
}
@Test
@DisplayName("测试新角色模板存在")
void testNewRoleTemplatesExist() {
List<String> expectedRoles = List.of(
"SYS_ADMIN", "ENGINEERING_LEAD", "SECURITY_LEAD",
"CLEANING_LEAD", "FINANCE_LEAD", "CLEANING_STAFF"
);
for (String roleCode : expectedRoles) {
assertTrue(roleRepository.findByRoleCode(roleCode).isPresent());
}
}
@Test
@DisplayName("测试按钮级权限编码存在")
void testButtonPermissionsExist() {
List<String> expectedPermissions = List.of(
"ops:work_order:assign",
"ops:work_order:accept",
"ops:work_order:complete"
);
for (String permCode : expectedPermissions) {
assertTrue(permissionRepository.findByPermissionCode(permCode).isPresent());
}
}
@Test
@DisplayName("测试状态驱动权限-CREATED状态")
void testStateDrivenPermission_Created() {
WorkOrderStatus status = WorkOrderStatus.CREATED;
assertTrue(status.isActionAllowed(WorkOrderAction.ASSIGN));
assertTrue(status.isActionAllowed(WorkOrderAction.EDIT));
assertTrue(status.isActionAllowed(WorkOrderAction.DELETE));
assertFalse(status.isActionAllowed(WorkOrderAction.ACCEPT));
}
@Test
@DisplayName("测试状态驱动权限-CLOSED状态")
void testStateDrivenPermission_Closed() {
WorkOrderStatus status = WorkOrderStatus.CLOSED;
assertTrue(status.getAllowedActions().isEmpty());
}
```
**DataScopeHelperTest:**
```java
@Test
@DisplayName("测试ALL数据范围")
void testDataScope_All() {
DataScopeContext.setDataScopeInfo(new DataScopeInfo("ALL", userId, projectId, deptId));
assertTrue(DataScopeHelper.canAccess(anyProjectId, anyDeptId, anyCreatorId));
}
@Test
@DisplayName("测试PROJECT数据范围")
void testDataScope_Project() {
DataScopeContext.setDataScopeInfo(new DataScopeInfo("PROJECT", userId, projectId, deptId));
assertTrue(DataScopeHelper.canAccess(projectId, anyDeptId, anyCreatorId));
assertFalse(DataScopeHelper.canAccess(otherProjectId, anyDeptId, anyCreatorId));
}
@Test
@DisplayName("测试DEPARTMENT数据范围")
void testDataScope_Department() {
DataScopeContext.setDataScopeInfo(new DataScopeInfo("DEPARTMENT", userId, projectId, deptId));
assertTrue(DataScopeHelper.canAccess(projectId, deptId, anyCreatorId));
assertFalse(DataScopeHelper.canAccess(projectId, otherDeptId, anyCreatorId));
}
@Test
@DisplayName("测试SELF数据范围")
void testDataScope_Self() {
DataScopeContext.setDataScopeInfo(new DataScopeInfo("SELF", userId, projectId, deptId));
assertTrue(DataScopeHelper.canAccess(projectId, deptId, userId));
assertFalse(DataScopeHelper.canAccess(projectId, deptId, otherUserId));
}
```
### 4.2 前端单元测试
#### 4.2.1 测试文件
| 测试文件 | 测试内容 |
|---------|---------|
| permissionStore.test.ts | 权限Store方法 |
| permissionDirective.test.ts | 权限指令 |
#### 4.2.2 测试用例
**permissionStore.test.ts:**
```typescript
describe('Permission Store V3', () => {
describe('hasPermissionCode', () => {
it('should return true when permission exists', () => {
const store = usePermissionStore()
store.permissions = [
{ code: 'ops:work_order:view', name: '查看工单', type: 'BUTTON' }
]
expect(store.hasPermissionCode('ops:work_order:view')).toBe(true)
})
it('should return false when permission not exists', () => {
const store = usePermissionStore()
store.permissions = []
expect(store.hasPermissionCode('ops:work_order:view')).toBe(false)
})
})
describe('hasAnyPermission', () => {
it('should return true when any permission exists', () => {
const store = usePermissionStore()
store.permissions = [
{ code: 'ops:work_order:view', name: '查看工单', type: 'BUTTON' }
]
expect(store.hasAnyPermission(['ops:work_order:view', 'ops:work_order:edit'])).toBe(true)
})
it('should return false when no permission exists', () => {
const store = usePermissionStore()
store.permissions = []
expect(store.hasAnyPermission(['ops:work_order:view', 'ops:work_order:edit'])).toBe(false)
})
})
describe('checkActionPermission', () => {
it('should call API with correct parameters', async () => {
const store = usePermissionStore()
const result = await store.checkActionPermission({
permission: 'ops:work_order:assign',
status: 'CREATED',
action: 'ASSIGN',
entityType: 'work_order'
})
expect(typeof result).toBe('boolean')
})
})
})
```
---
## 五、集成测试方案
### 5.1 测试场景
| 场景ID | 测试场景 | 测试步骤 | 预期结果 |
|--------|---------|---------|---------|
| INT-001 | 用户登录获取权限 | 1. 用户登录<br>2. 获取用户权限<br>3. 验证权限列表 | 返回正确的权限列表 |
| INT-002 | 角色权限分配 | 1. 创建角色<br>2. 分配权限<br>3. 验证角色权限 | 权限正确关联 |
| INT-003 | 数据权限过滤 | 1. 设置用户数据范围<br>2. 查询数据列表<br>3. 验证过滤结果 | 数据正确过滤 |
| INT-004 | 状态驱动权限 | 1. 创建工单<br>2. 检查可执行操作<br>3. 执行操作 | 操作正确执行 |
### 5.2 测试代码示例
```java
@SpringBootTest
@Transactional
class PermissionIntegrationTest {
@Autowired
private PermissionService permissionService;
@Autowired
private WorkOrderService workOrderService;
@Test
@DisplayName("集成测试:用户登录获取权限")
void testUserLoginAndGetPermissions() {
// 1. 用户登录
LoginResponse response = authService.login("project_manager", "Test@123");
assertNotNull(response.getAccessToken());
// 2. 获取用户权限
UserPermissionsResponse permissions = permissionService.getUserPermissions(response.getUserId());
assertNotNull(permissions);
assertFalse(permissions.getPermissions().isEmpty());
// 3. 验证权限列表包含预期权限
assertTrue(permissions.getPermissions().stream()
.anyMatch(p -> p.getPermissionCode().startsWith("ops:work_order")));
}
@Test
@DisplayName("集成测试:状态驱动权限")
void testStateDrivenPermission() {
// 1. 创建工单
WorkOrder workOrder = workOrderService.create(createRequest);
assertEquals(WorkOrderStatus.CREATED, workOrder.getStatus());
// 2. 检查可执行操作
Set<String> actions = permissionService.getAllowedActions(
userId, projectId, "work_order", "CREATED");
assertTrue(actions.contains("ASSIGN"));
assertFalse(actions.contains("ACCEPT"));
// 3. 分配工单
workOrderService.assign(workOrder.getId(), assigneeId);
assertEquals(WorkOrderStatus.ASSIGNED, workOrder.getStatus());
}
}
```
---
## 六、E2E测试方案
### 6.1 测试用例清单
#### 6.1.1 管理后台测试用例
| 用例ID | 测试场景 | 测试步骤 | 预期结果 | 优先级 |
|--------|---------|---------|---------|--------|
| E2E-ADMIN-001 | 超级管理员登录 | 1. 打开登录页<br>2. 输入超级管理员账号<br>3. 点击登录 | 登录成功,显示所有菜单 | P0 |
| E2E-ADMIN-002 | 项目经理登录 | 1. 打开登录页<br>2. 输入项目经理账号<br>3. 点击登录 | 登录成功,显示项目管理菜单 | P0 |
| E2E-ADMIN-003 | 工程主管工单管理 | 1. 登录工程主管账号<br>2. 进入工单管理<br>3. 检查可用按钮 | 显示分配、审核按钮 | P0 |
| E2E-ADMIN-004 | 维修人员工单执行 | 1. 登录维修人员账号<br>2. 进入工单列表<br>3. 检查可用按钮 | 显示接单、开始、完成按钮 | P0 |
| E2E-ADMIN-005 | 工单状态驱动按钮 | 1. 创建工单<br>2. 检查按钮状态<br>3. 分配工单<br>4. 再次检查按钮 | 按钮根据状态变化 | P0 |
| E2E-ADMIN-006 | 数据权限过滤 | 1. 登录部门主管账号<br>2. 查看工单列表<br>3. 验证数据范围 | 仅显示本部门数据 | P1 |
#### 6.1.2 员工端测试用例
| 用例ID | 测试场景 | 测试步骤 | 预期结果 | 优先级 |
|--------|---------|---------|---------|--------|
| E2E-EMP-001 | 维修人员移动端登录 | 1. 打开员工端<br>2. 输入维修人员账号<br>3. 点击登录 | 登录成功,显示工单列表 | P0 |
| E2E-EMP-002 | 工单接单流程 | 1. 查看待接单工单<br>2. 点击接单<br>3. 验证状态变化 | 工单状态变为已接单 | P0 |
| E2E-EMP-003 | 工单执行流程 | 1. 点击开始处理<br>2. 填写处理结果<br>3. 点击完成 | 工单状态变为已完成 | P0 |
| E2E-EMP-004 | 安保人员巡检 | 1. 登录安保人员账号<br>2. 进入巡检任务<br>3. 执行巡检 | 可扫码签到、上报异常 | P1 |
| E2E-EMP-005 | 保洁人员任务 | 1. 登录保洁人员账号<br>2. 查看任务列表<br>3. 执行任务 | 显示分配给自己的任务 | P1 |
#### 6.1.3 业主端测试用例
| 用例ID | 测试场景 | 测试步骤 | 预期结果 | 优先级 |
|--------|---------|---------|---------|--------|
| E2E-OWNER-001 | 业主登录 | 1. 打开业主端<br>2. 输入业主账号<br>3. 点击登录 | 登录成功,显示首页 | P0 |
| E2E-OWNER-002 | 在线报修 | 1. 点击报修<br>2. 填写报修信息<br>3. 提交 | 报修工单创建成功 | P0 |
| E2E-OWNER-003 | 查看账单 | 1. 点击账单<br>2. 查看账单列表<br>3. 验证数据 | 显示本人房屋账单 | P1 |
| E2E-OWNER-004 | 在线缴费 | 1. 选择账单<br>2. 点击缴费<br>3. 完成支付 | 缴费成功 | P1 |
| E2E-OWNER-005 | 访客邀请 | 1. 点击访客邀请<br>2. 填写访客信息<br>3. 提交 | 访客凭证生成成功 | P2 |
### 6.2 E2E测试脚本示例
```typescript
// e2e/permission-workflow.spec.ts
import { test, expect } from '@playwright/test'
test.describe('权限工作流测试', () => {
test('E2E-ADMIN-005: 工单状态驱动按钮', async ({ page }) => {
// 1. 登录工程主管账号
await page.goto('http://localhost:5175/login')
await page.fill('[name="username"]', 'engineering_lead')
await page.fill('[name="password"]', 'Test@123')
await page.click('button[type="submit"]')
await page.waitForURL('**/dashboard')
// 2. 进入工单管理
await page.click('text=工单管理')
await page.click('text=工单列表')
// 3. 创建新工单
await page.click('button:has-text("新增工单")')
await page.fill('[name="title"]', '测试工单')
await page.fill('[name="description"]', '测试描述')
await page.click('button:has-text("提交")')
// 4. 验证CREATED状态下的按钮
const assignButton = page.locator('button:has-text("分配")')
const acceptButton = page.locator('button:has-text("接单")')
await expect(assignButton).toBeVisible()
await expect(acceptButton).not.toBeVisible()
// 5. 分配工单
await assignButton.click()
await page.selectOption('[name="assignee"]', 'maintenance_staff')
await page.click('button:has-text("确认")')
// 6. 验证ASSIGNED状态下的按钮
await expect(assignButton).not.toBeVisible()
await expect(acceptButton).toBeVisible()
})
test('E2E-EMP-002: 维修人员工单接单流程', async ({ page }) => {
// 1. 登录维修人员账号
await page.goto('http://localhost:5174/login')
await page.fill('[name="username"]', 'maintenance_staff')
await page.fill('[name="password"]', 'Test@123')
await page.click('button[type="submit"]')
await page.waitForURL('**/home')
// 2. 查看待接单工单
await page.click('text=待接单')
// 3. 接单
await page.click('button:has-text("接单")')
// 4. 验证状态变化
await expect(page.locator('text=已接单')).toBeVisible()
// 5. 开始处理
await page.click('button:has-text("开始处理")')
await expect(page.locator('text=处理中')).toBeVisible()
})
})
```
---
## 七、测试执行计划
### 7.1 执行顺序
```
Day 1: 单元测试
├── 后端单元测试 (2小时)
│ ├── PermissionServiceV3Test
│ ├── RoleServiceTest
│ ├── DataScopeHelperTest
│ └── WorkOrderStatusTest
└── 前端单元测试 (2小时)
├── permissionStore.test.ts
└── permissionDirective.test.ts
Day 2: 集成测试
├── 用户认证集成测试 (1小时)
├── 角色权限集成测试 (1小时)
├── 数据权限集成测试 (1小时)
└── 状态驱动权限集成测试 (1小时)
Day 3: E2E测试
├── 管理后台E2E测试 (2小时)
├── 员工端E2E测试 (1小时)
└── 业主端E2E测试 (1小时)
Day 4: 回归测试
├── 全量回归测试 (3小时)
└── 缺陷修复验证 (1小时)
```
### 7.2 测试环境
| 环境 | 用途 | 配置 |
|------|------|------|
| 开发环境 | 单元测试、集成测试 | 本地开发机器 |
| 测试环境 | E2E测试 | 独立测试服务器 |
| 预发布环境 | 回归测试 | 类生产环境 |
---
## 八、缺陷管理
### 8.1 缺陷等级定义
| 等级 | 定义 | 处理时限 |
|------|------|---------|
| P0 | 核心功能不可用,阻塞测试 | 立即修复 |
| P1 | 主要功能异常,影响业务流程 | 24小时内 |
| P2 | 次要功能异常,不影响主流程 | 48小时内 |
| P3 | UI/体验问题 | 下版本修复 |
### 8.2 缺陷报告模板
```markdown
## 缺陷标题
[模块] 简短描述
## 缺陷详情
- **发现时间**: YYYY-MM-DD HH:mm
- **发现人**:
- **测试环境**:
- **测试端**: 管理后台/员工端/业主端
## 复现步骤
1. 步骤1
2. 步骤2
3. 步骤3
## 预期结果
描述预期行为
## 实际结果
描述实际行为
## 附件
- 截图
- 日志
- 其他
## 影响范围
描述影响的功能和用户
```
---
## 九、测试报告
### 9.1 测试报告模板
```markdown
# 权限体系升级测试报告
## 一、测试概述
- **测试时间**: YYYY-MM-DD ~ YYYY-MM-DD
- **测试人员**:
- **测试环境**:
- **测试版本**: v3.0
## 二、测试执行情况
### 2.1 测试用例执行统计
| 测试类型 | 用例总数 | 执行数 | 通过数 | 失败数 | 通过率 |
|---------|---------|--------|--------|--------|--------|
| 单元测试 | | | | | |
| 集成测试 | | | | | |
| E2E测试 | | | | | |
| **总计** | | | | | |
### 2.2 缺陷统计
| 等级 | 发现数 | 已修复 | 待修复 | 遗留 |
|------|--------|--------|--------|------|
| P0 | | | | |
| P1 | | | | |
| P2 | | | | |
| P3 | | | | |
| **总计** | | | | |
## 三、测试结论
- **是否满足上线条件**: 是/否
- **遗留问题**:
- **建议**:
## 四、附录
- 测试用例详情
- 缺陷列表
- 测试截图
```
---
## 十、验收标准
### 10.1 功能验收标准
- [ ] 所有13个角色可正常创建和使用
- [ ] 所有40+按钮权限可正常检查
- [ ] 状态驱动权限正确工作
- [ ] 数据范围权限正确过滤
- [ ] 三端权限检查正常工作
### 10.2 兼容性验收标准
- [ ] 现有用户可正常登录
- [ ] 现有功能不受影响
- [ ] 现有数据完整保留
### 10.3 性能验收标准
- [ ] 权限检查响应时间 < 100ms
- [ ] 页面加载时间无明显增加
- [ ] 数据库查询性能正常
### 10.4 测试验收标准
- [ ] 单元测试覆盖率 > 80%
- [ ] 集成测试全部通过
- [ ] E2E测试全部通过
- [ ] 无P0/P1级别缺陷