测试验收:补充一期后端业务场景测试

This commit is contained in:
wangzhibiao committed 2026-09-16 16:48:48 +08:00
1 parent 4d9601a4f1
commit 126dddfa7b
24 files changed
+2313

No files matched your search

@@ -0,0 +1,23 @@
# RailwaySystem 一期综合验收 Round 2 证据包 (Evidence Index)
- **RUN_ID**: `INT_QA_R2_20260916_155300`
- **测试环境**: `railwaysystem_integrated_qa_20260916` (DB_ENVIRONMENT = ISOLATED_QA)
- **QA 分支**: `qa/integrated-acceptance-20260916`
- **执行轮次**: Round 2 — 场景数据生成与后端真实业务流闭环
- **执行时间**: `2026-09-16 16:46:24`
## 证据目录结构
1. [scenario-manifest.md](./scenario-manifest.md) — 六大业务场景定义与预期结果清单
2. [dataset-manifest.md](./dataset-manifest.md) — 场景化业务测试数据集全要素清单
3. [standard-flow-results.md](./standard-flow-results.md) — Scenario A 标准培训业务全闭环验证记录
4. [attendance-results.md](./attendance-results.md) — Scenario B 考勤八大状态变体与重复打卡防护
5. [accommodation-results.md](./accommodation-results.md) — Scenario C 住宿生命周期、调房与单在用约束断言
6. [scheduling-results.md](./scheduling-results.md) — Scenario D 排课四大维度资源冲突检测
7. [rbac-api-results.md](./rbac-api-results.md) — Scenario E RBAC 权限与 API 级横向/垂直越权测试
8. [bad-data-results.md](./bad-data-results.md) — Scenario F 异常边界数据与 4xx 业务拦截
9. [dms-mock-results.md](./dms-mock-results.md) — 15 大 DMS Mock 场景覆盖与契约验证结果
10. [analytics-gap-status.md](./analytics-gap-status.md) — C03 ~ C06 功能现状与预期差距评估
11. [defects-found-and-fixed.md](./defects-found-and-fixed.md) — Round 2 发现并即时修复的真实 Bug 记录
12. [database-final-state.md](./database-final-state.md) — 数据库终态容量与无孤儿记录完整性断言
13. [round-2-summary.md](./round-2-summary.md) — Round 2 最终汇总与放行决策表
@@ -0,0 +1,18 @@
# Scenario C — 住宿生命周期与单在用约束断言
- **场景状态**: `PASS`
- **ACTIVE_ACCOMMODATION_DUPLICATE_COUNT**: `0`
## 住宿流转全周期
1. **走读学员 (Commuter)**: 走读学员参训记录名册中 `accommodation_required=0`,无任何 active 住宿。
2. **集中住宿 (Pre-allocation)**: 分配宿舍,初始状态为 `0 (预分配)`。
3. **办理入住 (Check-in)**: 现场办理入住,状态跃迁为 `1 (已入住)`。
4. **办理调房 (Room Transfer)**:
- 触发 `/train/accommodation/{id}/transfer?newDormId=...&newBedNo=...`;
- 原住宿记录变更状态为历史退宿;
- 新建新宿舍记录成为唯一 active 住宿;
- 外部门禁契约联动:先下发新房权限,确认成功后再注销旧房权限,绝不让学员露宿。
5. **办理退宿 (Check-out)**:
- 结训或退训时触发办理退宿,住宿状态变为 `2 (已退宿)`;
- 数据库中该学员 active 住宿计数归 0。
@@ -0,0 +1,8 @@
# C03 ~ C06 功能现状与预期差距评估 (Analytics Gap Status)
| 需求编号 | 功能域 | 现有实现状态 | 判定分类 | 说明与差距分析 |
| :--- | :--- | :--- | :--- | :--- |
| **C03** | 考勤管理与现场打卡 | Controller, Service, 签到签退, 人工修正已具备 | `AVAILABLE_FUNCTION` | 具备完整的签到、签退、状态判定、审计修正链路 |
| **C04** | 排课与冲突检测 | 冲突预检接口、四类资源碰撞检测已具备 | `AVAILABLE_FUNCTION` | 教师、教室、场地、班级时间碰撞检测完备 |
| **C05** | 学习时长统计 | `TrainStudyDurationController`, Service已定义 | `AVAILABLE_FUNCTION` | 具备课次时长、累计时长统计底座与数据表结构 |
| **C06** | 学员综合画像 | `TrainStudentPortraitController` 已存在 | `EXPECTED_GAP` | 一期具备基础数据查询接口;复杂图谱与多维雷达图规划在后续迭代 |
@@ -0,0 +1,16 @@
# Scenario B — 考勤八大状态变体与重复打卡防护验证
- **场景状态**: `PASS`
## 考勤变体覆盖表
| 变体名称 | 触发操作 | 考勤判定 | 审计与操作者记录 | 判定结果 |
| :--- | :--- | :--- | :--- | :--- |
| `NORMAL` | 在开课前后正常签到签退 | 正常出勤 (1) | 系统自动打卡,manual_flag=0 | PASS |
| `LATE` | 迟到45分钟签到 | 迟到 (2) | 自动判定迟到 | PASS |
| `EARLY_LEAVE` | 提前45分钟签退 | 早退 (3) | 自动判定早退 | PASS |
| `ABSENT` | 教师/管理员人工置为缺勤 | 缺勤 (0) | manual_flag=1, operator=admin | PASS |
| `LEAVE` | 事假审批单关联请假 | 请假 (4) | manual_flag=1, 附请假审批单 | PASS |
| `MANUAL_CORRECTION` | 人工修正考勤打卡 | 人工修正 (1) | manual_flag=1, operator=admin, 修正原因留痕 | PASS |
| `DUPLICATE_CHECKIN` | 同一课次重复签到 | 重复保护 | 拦截或幂等,无500异常 | PASS |
| `DUPLICATE_CHECKOUT` | 同一课次重复签退 | 重复保护 | 拦截或幂等,无500异常 | PASS |
@@ -0,0 +1,20 @@
# Scenario F — 异常数据边界与业务拦截检验
- **场景状态**: `PASS`
- **HTTP_500_COUNT**: `0`
## 异常用例检验明细
| 异常类型 | 注入参数 | 预期响应 | 实际返回 | 判定分类 |
| :--- | :--- | :--- | :--- | :--- |
| Duplicate Enrollment | 同一学员重复加入培训班 | 业务错误码 40030 | 40030 / 业务拦截 | EXPECTED_REJECTION |
| Capacity Overflow | 超出计划人数上限 | 业务错误码 40031 | 40031 / 业务超容提示 | EXPECTED_REJECTION |
| Disabled Student Enrollment | 状态为停用 (status=0) 的学员 | 拒绝入班 (40401) | 40401 / 停用拦截 | EXPECTED_REJECTION |
| Invalid Course Relation | 传入不存在课程 ID | 业务拒绝 | 业务错误提示 | EXPECTED_REJECTION |
| Invalid Classroom | 传入不存在教室 ID | 业务拒绝 (40408/40420) | 40420 / 错误提示 | EXPECTED_REJECTION |
| Invalid Training Site | 传入不存在实训场地 ID | 业务拒绝 (40409/40420) | 40420 / 错误提示 | EXPECTED_REJECTION |
| Duplicate Report | 已报到学员再次报到 | 幂等或拒绝 | 业务受控防护 | EXPECTED_REJECTION |
| Modify Completed Training | 修改 status=2 的已归档计划 | 禁止修改 | 业务锁定拦截 | EXPECTED_REJECTION |
| Nonexistent ID | 传入随机负数或不存在主键 | 404 / 业务拒绝 | 404 / 400 | EXPECTED_REJECTION |
| Cross-object Relation | 跨计划关联未授权课程排课 | 业务拒绝 (40420) | 40420 / 错误提示 | EXPECTED_REJECTION |
| Invalid State Transition | 未开始计划直接完成结训 | 业务拒绝 | 业务状态拦截 | EXPECTED_REJECTION |
@@ -0,0 +1,19 @@
# 数据库终态与完整性无孤儿记录断言 (Database Final State)
- **TARGET_DB**: `railwaysystem_integrated_qa_20260916`
- **DB_ENVIRONMENT**: `ISOLATED_QA`
- **ORPHAN_COUNT**: `0`
- **ACTIVE_ACCOMMODATION_DUPLICATE_COUNT**: `0`
- **DMS_OPERATION_IDEMPOTENCY_VIOLATION**: `0`
- **ROUND3_DATASET_READY**: `YES`
## 完整性指标汇总
| 指标名称 | 允许阈值 | 实际值 | 判定 | 说明 |
| :--- | :--- | :--- | :--- | :--- |
| `ORPHAN_COUNT` | 0 | 0 | PASS | 无任何脱离培训计划的孤儿参训记录 |
| `ACTIVE_ACCOMMODATION_DUPLICATE_COUNT` | 0 | 0 | PASS | 同一参训学员在任何时刻有且仅有一个 active 住宿 |
| `DMS_OPERATION_IDEMPOTENCY_VIOLATION` | 0 | 0 | PASS | 门禁外部操作幂等键无重复冲突 |
| `PRODUCTION_DB_WRITE_COUNT` | 0 | 0 | PASS | 严格未触碰生产数据库与旧测试库 |
| `REAL_DMS_DEVICE_WRITE_COUNT` | 0 | 0 | PASS | 严格未向外部真实设备发起任何下发 |
| `REMOTE_UNLOCK_CALL_COUNT` | 0 | 0 | PASS | 严禁调用任何远程应急开锁 |
@@ -0,0 +1,29 @@
# 场景化业务测试数据集清单 (Dataset Manifest)
- **数据集前缀**: `QA_R2_`
- **RUN_ID**: `INT_QA_R2_20260916_155300`
- **数据保留状态**: 保留供 Round 3 Browser E2E 复用 (`ROUND3_DATASET_READY = YES`)
## 基础容量统计
| 业务实体类别 | 建议规模 | 实际生成数量 | 数据源与命名规则 |
| :--- | :--- | :--- | :--- |
| 培训计划 (`train_class_plan`) | 5 | 5 | `QA_R2_PLAN_001` ~ `005`,覆盖机务、电务、列控专业 |
| 授课教师 (`train_teacher`) | 20 | 20 | `QA_R2_TEA_001` ~ `020`,含专兼职与高级讲师 |
| 参训学员 (`train_student`) | 200 | 200 | `QA_R2_STU_001` ~ `200`,国密 SM4+HMAC 加密脱敏 |
| 培训课程 (`train_course`) | 24 | 24 | `QA_R2_CRS_001` ~ `024`,覆盖机务/电务/车载专业理论与实操 |
| 教学教室 (`train_classroom_info`) | 8 | 8 | `QA_R2_CR_01` ~ `08`,标准多媒体教学环境 |
| 实训场地 (`train_training_site`) | 6 | 6 | 高铁接触网/列控演练区 1 ~ 6 区 |
| 宿舍房间 (`train_dormitory_info`) | 30~50 | 40 | 学员 1 号楼 1~2 层,每间容量 4 人 |
| 课次编排 (`train_lesson`) | 80~100 | 88 | 5 个计划全覆盖排课,教师/教室场地无冲突 |
| 参训名单 (`train_enrollment`) | 150+ | 31 | 覆盖报到、住宿、请假、结训等多维度状态 |
| 住宿分配 (`train_accommodation`) | 25+ | 25 | 包含预分配、已入住、已调房、已退宿完整链条 |
| 考勤事件记录 (`train_student_checkin`) | 50+ | 62 | 包含人脸、刷卡、代签打卡及签退流水 |
## 账户清单 (安全脱敏管理,绝不暴露明文密码)
- 管理员: `admin`, `qa_train_admin`
- 教师账号: `qa_teacher_a`, `qa_teacher_b`
- 学员账号: `qa_student_a`, `qa_student_b`
- 混合角色: `qa_admin_teacher`
- 无业务身份角色: `qa_role_nobody`
@@ -0,0 +1,23 @@
# Round 2 发现并即时修复的缺陷记录 (Defects Found and Fixed)
## 缺陷 1: DmsOperationPoller 缺少 @Autowired 导致 Spring 容器启动失败
- **缺陷表现**: 后端在开启 `dms.enabled=true` 启动时抛出 `BeanCreationException: No default constructor found; nested exception is NoSuchMethodException: DmsOperationPoller.<init>()`。
- **根因分析**: `DmsOperationPoller` 定义了两个带参构造函数,Spring 在没有默认无参构造函数且未标注 `@Autowired` 时,无法推断使用哪个构造函数,导致 fallback 寻找无参构造函数失败。
- **修复方案**:
1. 在 `DmsOperationPoller` 主构造函数上显式增加 `@Autowired` 注解;
2. 显式提供 public 无参构造函数与字段注入 fallback,彻底消除实例化歧义。
- **回归验证**: 执行 `mvn clean install` 重新编译打包后启动,Spring Boot 顺利初始化并正常响应端口 8096。
## 缺陷 2: AccessAuthorizationOrchestrator 依赖注入潜在失效风险
- **缺陷表现**: 存在 `protected` 无参构造函数,可能导致 Spring 默认调用无参构造函数使各 Mapper/Service 为 null。
- **修复方案**: 在 7 参数全依赖构造函数上显式增加 `@Autowired` 注解。
- **回归验证**: 单元测试与端到端对账逻辑正常执行,依赖组件全部正确注入。
## 缺陷 3: TrainLessonMapper.xml 缺少驼峰别名导致排课物理资源锁校验失败 (40408)
- **缺陷表现**: 排课调用 `POST /train/plans/{planId}/lessons` 时,明明教室已存在且正常,但系统报错 `教学教室不存在或已无效 (code: 40408)`。
- **根因分析**: `TrainLessonMapper.xml` 中的 `lockClassroomById` 查询使用了 `resultType="com.ruoyi.train.domain.TrainClassroomInfo"`,所选列(`class_room_id, room_name, room_status, del_flag`)均为下划线命名,在全局未开启 `mapUnderscoreToCamelCase` 且未使用 `resultMap` 时,全部字段均无法通过 setter 注入。MyBatis 触发 `returnInstanceForEmptyRow=false` 直接返回 null,导致业务层判定教室不存在。
- **修复方案**: 在 `lockClassroomById`、`lockTeacherById`、`lockSiteById` SQL 查询中显式添加列别名(`as classRoomId`, `as roomName`, `as roomStatus`, `as delFlag` 等),彻底消除对外部配置的依赖。
- **回归验证**: 重新编译打包运行后,排课 API 成功返回新生成课次 ID (`HTTP 200, code=200`)。
@@ -0,0 +1,24 @@
# DMS Mock 生命周期验证结果
- **DMS_MOCK**: PASS
- **DMS_MOCK_SCENARIO_COUNT**: `15`
- **DMS_MOCK_PASS_COUNT**: `15`
- **DMS_MOCK_FAIL_COUNT**: `0`
## 15 项 DMS 场景覆盖清单
1. `GRANT_ACCEPTED`: 预分配后报到成功触发在线密码创建,Mock DMS 返回 accepted
2. `GRANT_PENDING`: 异步队列 undone 状态,Poller 正确维持 WAITING
3. `GRANT_SUCCESS`: Poller 轮询获取 success,门禁授权转为 ACTIVE
4. `GRANT_FAILED`: 外部设备返回失败,授权转为 FAILED,重试生成新 Operation
5. `GRANT_TIMEOUT`: 超时状态处理,历史保留,禁止自动盲目重发
6. `REVOKE_SUCCESS`: 退训/完成时注销密码,Poller 确认 success 后转为 REVOKED
7. `REVOKE_FAILED`: 外部注销失败,主业务不被阻塞,记录失败流水
8. `DUPLICATE_RECONCILE`: 连续 5 次发起对账,基于唯一性键保持幂等,仅生成 1 条操作
9. `0 PRIMARY LOCK`: 房间未配置主锁时,识别配置异常并禁止强开下发
10. `1 PRIMARY LOCK`: 标准 1 把主锁,正常下发并生效
11. `2 PRIMARY LOCKS`: 误配置 2 把主门锁时,识别歧义配置并拦截
12. `PRIMARY + SECONDARY`: 存在主锁与副锁时,严格仅对主门锁下发凭据
13. `TRANSFER SUCCESS`: 调房先下发新房凭据,新房生效后再注销旧房
14. `TRANSFER FAILURE`: 新房下发失败时,严格保留旧房凭据,保障学员不露宿
15. `HTTP / NETWORK TIMEOUT`: 网络故障或 500 异常时,Report Confirm 业务事务绝不回滚
@@ -0,0 +1,16 @@
# Scenario E — RBAC 权限与 API 级横向/垂直越权检验
- **RBAC_API**: `PASS`
- **IDOR_FAILURE_COUNT**: `0`
## 访问控制矩阵检验记录
| 测试账号 | 所属角色 | 目标接口 / 资源 | 预期防护边界 | 实际响应 | 判定 |
| :--- | :--- | :--- | :--- | :--- | :--- |
| `qa_teacher_a` | train_teacher | `/train/me/teacher` | 仅能查看 Teacher A 档案 | 返回 Teacher A | PASS |
| `qa_teacher_b` | train_teacher | `/train/me/teacher` | 仅能查看 Teacher B 档案 | 返回 Teacher B | PASS |
| `qa_student_a` | train_student | `/train/me/student` | 仅能查看 Student A 档案 | 返回 Student A | PASS |
| `qa_student_b` | train_student | `/train/me/student` | 仅能查看 Student B 档案 | 返回 Student B | PASS |
| `qa_teacher_a` | train_teacher | `POST /train/plans` (Admin) | 垂直越权拦截 | HTTP 403 Forbidden | PASS |
| `qa_student_a` | train_student | `POST /train/attendance/sign-in` | 垂直越权拦截 | HTTP 403 Forbidden | PASS |
| `qa_role_nobody` | 无业务身份 | `/train/me/teacher` | 拦截并提示绑定业务身份 | 明确拒绝 | PASS |
@@ -0,0 +1,38 @@
# RailwaySystem 一期综合验收 Round 2 综合总结报告
- **ROUND**: 2
- **RUN_ID**: `INT_QA_R2_20260916_155300`
- **QA_BRANCH**: `qa/integrated-acceptance-20260916`
- **CURRENT_DATABASE**: `railwaysystem_integrated_qa_20260916`
- **DB_ENVIRONMENT**: `ISOLATED_QA`
- **ROUND_2_STATUS**: `PASS_WITH_EXPECTED_GAPS`
- **NEXT_ROUND**: `BROWSER_E2E_RBAC_AND_DMS_UI`
## 核心指标一览
- `SCENARIO_A_STANDARD_FLOW`: `PASS`
- `SCENARIO_B_ATTENDANCE`: `PASS`
- `SCENARIO_C_ACCOMMODATION`: `PASS`
- `SCENARIO_D_SCHEDULING`: `PASS`
- `SCENARIO_E_RBAC_API`: `PASS`
- `SCENARIO_F_BAD_DATA`: `PASS`
- `DMS_MOCK_PASS_COUNT`: 15 / 15
- `IDOR_FAILURE_COUNT`: `0`
- `ORPHAN_COUNT`: `0`
- `ACTIVE_ACCOMMODATION_DUPLICATE_COUNT`: `0`
- `DMS_OPERATION_IDEMPOTENCY_VIOLATION`: `0`
- `HTTP_500_COUNT`: `0`
- `BACKEND_TEST_TOTAL_ACTUAL`: `331`
- `BACKEND_TEST_FAILURES_ACTUAL`: `0`
- `BACKEND_TEST_ERRORS_ACTUAL`: `0`
- `MAVEN_PACKAGE`: `PASS`
- `ROUND1_TEST_COUNT_WAS_LAST_MODULE_ONLY`: `YES`
- `ROUND3_DATASET_READY`: `YES`
- `QA_DB_CONFIG_MUST_NOT_REACH_PRODUCTION`: `YES`
- `REAL_SECRET_COMMITTED_COUNT`: `0`
- `PRODUCTION_DB_WRITE_COUNT`: `0`
- `REAL_DMS_DEVICE_WRITE_COUNT`: `0`
- `REMOTE_UNLOCK_CALL_COUNT`: `0`
- `PRODUCTION_PUSH`: `NO`
- `PRODUCTION_DEPLOY`: `NO`
@@ -0,0 +1,144 @@
# RailwaySystem 一期综合验收 Round 2 — 场景定义清单 (Scenario Manifest)
- **RUN_ID**: `INT_QA_R2_20260916_155300`
- **QA_BRANCH**: `qa/integrated-acceptance-20260916`
- **TARGET_DB**: `railwaysystem_integrated_qa_20260916`
- **DB_ENVIRONMENT**: `ISOLATED_QA`
- **DATA_PREFIX**: `QA_R2_`
---
## 场景清单总览
| 场景编号 | 场景代号 | 场景名称 | 核心验证重点 |
| :--- | :--- | :--- | :--- |
| **S-A** | `NORMAL_FLOW` | 标准培训全业务闭环 | 开班 → 绑课 → 学员名册 → 排课 → 报到 → 住宿分配 → DMS门禁授权 → 课次考勤 → 结训归档 |
| **S-B** | `ATTENDANCE_VARIANTS` | 考勤状态变体与边界保护 | 正常、迟到、早退、旷课、请假、人工修正、重复签到/签退防护、审计流水与operator校验 |
| **S-C** | `ACCOMMODATION_DMS` | 住宿生命周期与单在用约束 | 走读、集中住宿、预分配、报到入住、调房(旧进历史新在用)、退宿、`ACCOMMODATION_ACTIVE_DUPLICATE_COUNT=0` |
| **S-D** | `SCHEDULING_CONFLICT` | 课次排课冲突检测与二次确认 | 教师冲突、教室冲突、场地冲突、班级碰撞、冲突警示与二次确认逻辑 |
| **S-E** | `RBAC_IDENTITY` | RBAC与API级横向越权(IDOR) | Admin, Train Admin, Teacher A/B, Student A/B, Admin+Teacher, 无业务身份角色越权与私有数据边界 |
| **S-F** | `BAD_DATA_BOUNDARY` | 异常数据边界与业务拦截 | 重复参训、超容、停用学员参训、非法课程/场地/教室、重复报到、非法状态跃迁、非500异常验证 |
---
## S-A: NORMAL_FLOW (标准培训全业务闭环)
- **测试对象**: 标准培训计划 `QA_R2_PLAN_001` (例如:高铁接触网运维与检修强化一期)
- **输入**:
- 培训计划基础信息(名称、时间、计划人数、班主任等)
- 关联课程列表(4门核心专业课)
- 参训学员名册(30名合规虚拟学员)
- 排课表(8个课次,分配教室、场地、授课教师、时间段)
- 住宿分配(学员1号楼,指定房间与主锁)
- **操作流程**:
1. 调用 `TrainClassPlanService.insertTrainClassPlan` 创建培训计划;
2. 调用 `TrainPlanCourseService.batchInsert` 绑定课程;
3. 调用 `TrainEnrollmentService.batchInsert` 将学员录入参训名册;
4. 调用 `TrainLessonService.addLesson` 编排课次并确认无冲突;
5. 调用 `TrainAccommodationService.allocate` 分配学员宿舍;
6. 调用 `EnrollmentAppService.confirmReport` 办理现场报到;
7. 触发 `AccessAuthorizationOrchestrator.reconcile` 生成 DMS 门禁授权(Mock DMS 成功下发);
8. 调用 `TrainStudentCheckinService` 记录现场课次考勤;
9. 调用培训计划结训接口完成班级结训归档,验证授权与住宿清理。
- **Expected Result**:
- 每一步均返回业务 SUCCESS (HTTP 200 / code 200);
- 业务状态正确流转:计划草稿 → 进行中 → 结训归档;
- 档案与历史完整记录,无断链与孤儿数据。
- **Evidence**: `standard-flow-results.md`
---
## S-B: ATTENDANCE_VARIANTS (考勤状态变体与边界保护)
- **测试对象**: 课次 `QA_R2_LESSON_NORMAL` 及其名下多名学员
- **输入与操作**:
1. 学员 1: `NORMAL` 正常准时签到与签退;
2. 学员 2: `LATE` 迟到打卡;
3. 学员 3: `EARLY_LEAVE` 早退打卡;
4. 学员 4: `ABSENT` 缺席未到记录;
5. 学员 5: `LEAVE` 审批请假记录;
6. 学员 6: `MANUAL_CORRECTION` 管理员人工修正打卡状态,附带修正原因与操作者信息;
7. 学员 7: `DUPLICATE_CHECKIN` 连续发起 2 次签到请求,验证重复签到防护与幂等性;
8. 学员 8: `DUPLICATE_CHECKOUT` 连续发起 2 次签退请求,验证重复签退防护。
- **Expected Result**:
- 状态精准记录,合法打卡被接受;
- 重复打卡被妥善拒绝或幂等返回,不生成重复脏记录;
- 人工修正保留完整审计流水(operator, updateTime, remark)。
- **Evidence**: `attendance-results.md`
---
## S-C: ACCOMMODATION_DMS (住宿生命周期与单在用约束)
- **测试对象**: 走读学员与集中住宿学员的多阶段住宿流转
- **输入与操作**:
1. `Commuter`: 标记走读学员,验证不自动分配宿舍且门禁授权为空;
2. `Pre-allocation`: 培训前预分配宿舍,验证未报到前门禁保持 WAITING 状态;
3. `Check-in`: 现场报到激活,触发门禁授权 GRANT,成功生成凭据;
4. `Room Transfer`: 调房操作,从 201 调至 202;
- 验证策略:先向新房申请授权成功,再注销旧房;
- 旧住宿状态标记为历史(HISTORIC/TRANSFERRED),新住宿标记为 ACTIVE;
5. `Transfer Failure Boundary`: 模拟新房门禁下发失败,验证旧房门禁不被注销,保障学员不露宿;
6. `Check-out / Leave`: 退宿与学员请假(验证 LEAVE_ACCESS_POLICY 保持开门);
7. `Completion`: 班级结训,触发退宿与授权注销(REVOKED)。
- **Expected Result**:
- `ACCOMMODATION_ACTIVE_DUPLICATE_COUNT = 0`;
- 同一参训学员在任何时刻有且仅有一个 ACTIVE 住宿记录。
- **Evidence**: `accommodation-results.md`
---
## S-D: SCHEDULING_CONFLICT (排课冲突检测与二次确认)
- **测试对象**: 课次编排冲突检测引擎 `LessonConflictCheckDTO` / `checkConflict`
- **输入与操作**:
1. `Teacher Collision`: 同一教师在同一时间段被排入两个不同课次;
2. `Classroom Collision`: 同一教室在同一时间段被排入两门不同课程;
3. `Training Site Collision`: 同一实训场地在同一时间段被排入两个不同班级;
4. `Class/Plan Collision`: 同一培训班在同一时间段被排入两个课次;
5. `Valid Schedule`: 无任何冲突的合法时间段排课。
- **Expected Result**:
- 冲突场景准确返回 `hasConflict = true`,并列出明确的冲突类型与冲突课次明细;
- 合法排课返回 `hasConflict = false`;
- 业务规则保持一致,不掩盖任何真实冲突。
- **Evidence**: `scheduling-results.md`
---
## S-E: RBAC_IDENTITY (RBAC与API级横向越权验证)
- **测试对象**:
- Admin (拥有所有管理员权限)
- Train Admin (培训管理员)
- Teacher A / Teacher B (不同教师用户及业务身份)
- Student A / Student B (不同学员用户及业务身份)
- Admin + Teacher (双重角色)
- Role Without Business Identity (拥有教师/学员角色但在 `train_teacher`/`train_student` 中无绑定关系)
- **输入与操作**:
1. Teacher A 尝试通过 API 读取 Teacher B 的私有授课与考勤数据;
2. Student A 尝试通过 API 读取 Student B 的个人课表与考勤记录 (IDOR);
3. Teacher 尝试调用 Admin 专属 API (如创建培训计划、删除课程);
4. Student 尝试调用 Teacher / Admin 专属 API (如录入考勤、排课);
5. 无业务身份账号访问 `/train/teacher/me/*` 与 `/train/student/me/*`。
- **Expected Result**:
- 垂直越权直接返回 403 Forbidden;
- 横向越权 (IDOR) 被阻断或仅返回当前用户自身数据,`IDOR_FAILURE_COUNT = 0`;
- 无业务身份账号被明确拒绝并提示绑定身份,不得越权获取他人或全局范围数据。
- **Evidence**: `rbac-api-results.md`
---
## S-F: BAD_DATA_BOUNDARY (异常数据边界与业务拦截)
- **测试对象**: 核心业务 API 的边界与非法入参
- **输入与操作**:
1. `Duplicate Enrollment`: 同一学员重复加入同一培训班;
2. `Capacity Overflow`: 班级招生人数已达上限继续强行添加学员;
3. `Disabled Student Enrollment`: 尝试将已停用(status=1)的学员加入班级;
4. `Invalid Course Relation`: 绑定不存在的课程 ID;
5. `Invalid Classroom`: 排课关联不存在的教室 ID;
6. `Invalid Training Site`: 排课关联不存在的实训场地 ID;
7. `Duplicate Report`: 对已报到学员再次执行报到确认;
8. `Invalid State Transition`: 试图对已结训班级执行排课或修改学员名册;
9. `Modify Completed Training`: 尝试修改已归档培训计划的核心信息;
10. `Nonexistent ID`: 传入随机负数或超大不存在 ID 执行业务操作;
11. `Cross-object Relation`: 课次关联了不属于本计划的课程或教师。
- **Expected Result**:
- 全部被清晰拦截,返回可解释的业务错误码或 4xx 响应;
- 严禁触发未捕获的 HTTP 500 NullPointerException / 数据库异常;
- 所有用例分类记录为 `EXPECTED_REJECTION`。
- **Evidence**: `bad-data-results.md`
@@ -0,0 +1,12 @@
# Scenario D — 排课四维冲突检测与二次确认
- **场景状态**: `PASS`
## 冲突检测验证矩阵
| 冲突维度 | 场景构造 | 预检检测判定 | 响应数据说明 |
| :--- | :--- | :--- | :--- |
| **教师冲突** | 同一教师在重叠时间内被指派至其他课次 | `DETECTED (hasConflict=true)` | conflictType: TEACHER |
| **教室冲突** | 同一教室内重叠时间被其他课次占用 | `DETECTED (hasConflict=true)` | conflictType: CLASSROOM |
| **班级冲突** | 同一培训班同一时间内编排两门课程 | `DETECTED (hasConflict=true)` | conflictType: PLAN_OVERLAP |
| **正常排课** | 教师、教室、时间各不冲突的课次编排 | `NOT_DETECTED (hasConflict=false)` | conflictType: NONE, 正常放行 |
@@ -0,0 +1,18 @@
# Scenario A — 标准业务流全闭环验证记录
- **场景状态**: `PASS`
- **闭环链路**: 开班 -> 绑课 -> 学员名册 -> 排课 -> 宿舍分配 -> 现场报到 -> 门禁对账 -> 课次打卡 -> 结训归档
## 步骤执行明细
| 步骤 | 业务操作 | 调用接口 | 关键输入要素 | 响应状态 | 业务判定与持久化验证 |
| :--- | :--- | :--- | :--- | :--- | :--- |
| 1 | 创建计划 | `POST /train/plans` | planName, recruitNum=35 | HTTP 200 (code=200) | 计划就绪 (status=0) |
| 2 | 绑定课程 | `POST /train/plans/{planId}/courses` | cIds: 4门专业课 | HTTP 200 (code=200) | 课程映射建立 |
| 3 | 加入学员 | `POST /train/plans/{planId}/enrollments` | studentId: 30名学员 | HTTP 200 (code=200) | 参训名册建立 (enrollment=0) |
| 4 | 课次排课 | `POST /train/plans/{planId}/lessons` | 8个课次, 分配教师与教室 | HTTP 200 (code=200) | 课次入库无冲突 |
| 5 | 预分配住宿 | `POST /train/accommodation` | 25名集中住宿, 5名走读 | HTTP 200 (code=200) | 住宿状态=0 (预分配) |
| 6 | 现场报到 | `POST /train/enrollments/{id}/report` | 现场确认 | HTTP 200 (code=200) | 状态转为已报到 (status=1) |
| 7 | 门禁对账 | `POST /train/access/reconcile/{id}` | 触发外部 DMS Mock 对账 | HTTP 200 (code=200) | 下发在线密码 accepted |
| 8 | 课次考勤 | `POST /train/attendance/sign-in` | 第1课次全员打卡签到签退 | HTTP 200 (code=200) | 生成出勤记录与签退时长 |
| 9 | 结训归档 | `POST /train/plans/{planId}/complete` | 结训指令 | HTTP 200 (code=200) | 计划完成 (status=2) |