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

20 KiB
Raw Blame History

权限体系升级测试方案

文档版本: 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 测试项目数据

-- 测试项目
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:

@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:

@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:

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. 用户登录
2. 获取用户权限
3. 验证权限列表
返回正确的权限列表
INT-002 角色权限分配 1. 创建角色
2. 分配权限
3. 验证角色权限
权限正确关联
INT-003 数据权限过滤 1. 设置用户数据范围
2. 查询数据列表
3. 验证过滤结果
数据正确过滤
INT-004 状态驱动权限 1. 创建工单
2. 检查可执行操作
3. 执行操作
操作正确执行

5.2 测试代码示例

@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. 打开登录页
2. 输入超级管理员账号
3. 点击登录
登录成功,显示所有菜单 P0
E2E-ADMIN-002 项目经理登录 1. 打开登录页
2. 输入项目经理账号
3. 点击登录
登录成功,显示项目管理菜单 P0
E2E-ADMIN-003 工程主管工单管理 1. 登录工程主管账号
2. 进入工单管理
3. 检查可用按钮
显示分配、审核按钮 P0
E2E-ADMIN-004 维修人员工单执行 1. 登录维修人员账号
2. 进入工单列表
3. 检查可用按钮
显示接单、开始、完成按钮 P0
E2E-ADMIN-005 工单状态驱动按钮 1. 创建工单
2. 检查按钮状态
3. 分配工单
4. 再次检查按钮
按钮根据状态变化 P0
E2E-ADMIN-006 数据权限过滤 1. 登录部门主管账号
2. 查看工单列表
3. 验证数据范围
仅显示本部门数据 P1

6.1.2 员工端测试用例

用例ID 测试场景 测试步骤 预期结果 优先级
E2E-EMP-001 维修人员移动端登录 1. 打开员工端
2. 输入维修人员账号
3. 点击登录
登录成功,显示工单列表 P0
E2E-EMP-002 工单接单流程 1. 查看待接单工单
2. 点击接单
3. 验证状态变化
工单状态变为已接单 P0
E2E-EMP-003 工单执行流程 1. 点击开始处理
2. 填写处理结果
3. 点击完成
工单状态变为已完成 P0
E2E-EMP-004 安保人员巡检 1. 登录安保人员账号
2. 进入巡检任务
3. 执行巡检
可扫码签到、上报异常 P1
E2E-EMP-005 保洁人员任务 1. 登录保洁人员账号
2. 查看任务列表
3. 执行任务
显示分配给自己的任务 P1

6.1.3 业主端测试用例

用例ID 测试场景 测试步骤 预期结果 优先级
E2E-OWNER-001 业主登录 1. 打开业主端
2. 输入业主账号
3. 点击登录
登录成功,显示首页 P0
E2E-OWNER-002 在线报修 1. 点击报修
2. 填写报修信息
3. 提交
报修工单创建成功 P0
E2E-OWNER-003 查看账单 1. 点击账单
2. 查看账单列表
3. 验证数据
显示本人房屋账单 P1
E2E-OWNER-004 在线缴费 1. 选择账单
2. 点击缴费
3. 完成支付
缴费成功 P1
E2E-OWNER-005 访客邀请 1. 点击访客邀请
2. 填写访客信息
3. 提交
访客凭证生成成功 P2

6.2 E2E测试脚本示例

// 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 缺陷报告模板

## 缺陷标题
[模块] 简短描述

## 缺陷详情
- **发现时间**: YYYY-MM-DD HH:mm
- **发现人**: 
- **测试环境**: 
- **测试端**: 管理后台/员工端/业主端

## 复现步骤
1. 步骤1
2. 步骤2
3. 步骤3

## 预期结果
描述预期行为

## 实际结果
描述实际行为

## 附件
- 截图
- 日志
- 其他

## 影响范围
描述影响的功能和用户

九、测试报告

9.1 测试报告模板

# 权限体系升级测试报告

## 一、测试概述
- **测试时间**: 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级别缺陷