测试考勤:覆盖参训状态签到守卫与真实前端策略

This commit is contained in:
wangzhibiao committed 2026-09-22 00:05:56 +08:00
1 parent e2c15e6216
commit 08b8abe778
2 files changed
+344 -151

No files matched your search

@@ -196,16 +196,18 @@ public class AttendanceActionContractTest
lesson.setPlanCourseId(10L);
lessons.put(1001L, lesson);
// 学员 E_A 属于 PLAN_A
// 学员 E_A 属于 PLAN_A (1:已报到在训)
TrainEnrollment ea = new TrainEnrollment();
ea.setEnrollmentId(201L);
ea.setPlanId("PLAN_A");
ea.setEnrollmentStatus(1);
enrollments.put(201L, ea);
// 学员 E_B 属于 PLAN_B (跨班)
// 学员 E_B 属于 PLAN_B (跨班, 1:已报到在训)
TrainEnrollment eb = new TrainEnrollment();
eb.setEnrollmentId(202L);
eb.setPlanId("PLAN_B");
eb.setEnrollmentStatus(1);
enrollments.put(202L, eb);
}
@@ -594,4 +596,227 @@ public class AttendanceActionContractTest
assertEquals(AttendanceErrorCode.LESSON_NOT_FOUND, (int) e.getCode());
}
}
@Test
public void testSignInLifecycle_Status0_WaitingReport_Rejected()
{
// status=0 待报到学员:严禁直接签到绕过报到生命周期
TrainEnrollment e0 = new TrainEnrollment();
e0.setEnrollmentId(300L);
e0.setPlanId("PLAN_A");
e0.setEnrollmentStatus(0); // 0: 待报到
enrollments.put(300L, e0);
AttendanceSignInDTO dto = new AttendanceSignInDTO();
dto.setEnrollmentId(300L);
dto.setLessonId(1001L);
try
{
appService.signIn(dto, "admin");
fail("待报到学员必须被拦截禁止签到");
}
catch (ServiceException e)
{
assertEquals(AttendanceErrorCode.ATTENDANCE_SIGN_IN_REQUIRES_REPORTED_ENROLLMENT, (int) e.getCode());
assertEquals(AttendanceErrorCode.ATTENDANCE_SIGN_IN_REQUIRES_REPORTED_ENROLLMENT_MESSAGE, e.getMessage());
}
// 保证无任何写产生
assertNull(checkinsByEnrollLesson.get("300_1001"));
}
@Test
public void testSignInLifecycle_Status1_Reported_Allowed()
{
// status=1 已报到学员:允许签到
TrainEnrollment e1 = new TrainEnrollment();
e1.setEnrollmentId(301L);
e1.setPlanId("PLAN_A");
e1.setEnrollmentStatus(1); // 1: 已报到
enrollments.put(301L, e1);
AttendanceSignInDTO dto = new AttendanceSignInDTO();
dto.setEnrollmentId(301L);
dto.setLessonId(1001L);
TrainStudentCheckin c = appService.signIn(dto, "admin");
assertNotNull(c);
assertNotNull(c.getSignInTime());
assertEquals(Integer.valueOf(1), c.getAttendanceStatus());
}
@Test
public void testSignInLifecycle_Status2_Leave_Allowed_PreviewDefault()
{
// status=2 请假学员:Preview Default 允许签到 (LEAVE_SIGNIN_POLICY: PREVIEW_DEFAULT_ALLOWED)
TrainEnrollment e2 = new TrainEnrollment();
e2.setEnrollmentId(302L);
e2.setPlanId("PLAN_A");
e2.setEnrollmentStatus(2); // 2: 请假
enrollments.put(302L, e2);
AttendanceSignInDTO dto = new AttendanceSignInDTO();
dto.setEnrollmentId(302L);
dto.setLessonId(1001L);
TrainStudentCheckin c = appService.signIn(dto, "admin");
assertNotNull(c);
assertNotNull(c.getSignInTime());
assertEquals(Integer.valueOf(1), c.getAttendanceStatus());
}
@Test
public void testSignInLifecycle_Status3_Abandoned_Rejected()
{
// status=3 放弃学员:严禁直接签到
TrainEnrollment e3 = new TrainEnrollment();
e3.setEnrollmentId(303L);
e3.setPlanId("PLAN_A");
e3.setEnrollmentStatus(3); // 3: 放弃
enrollments.put(303L, e3);
AttendanceSignInDTO dto = new AttendanceSignInDTO();
dto.setEnrollmentId(303L);
dto.setLessonId(1001L);
try
{
appService.signIn(dto, "admin");
fail("已放弃学员必须被拦截禁止签到");
}
catch (ServiceException e)
{
assertEquals(AttendanceErrorCode.ATTENDANCE_SIGN_IN_ENROLLMENT_ABANDONED, (int) e.getCode());
assertEquals(AttendanceErrorCode.ATTENDANCE_SIGN_IN_ENROLLMENT_ABANDONED_MESSAGE, e.getMessage());
}
assertNull(checkinsByEnrollLesson.get("303_1001"));
}
@Test
public void testSignInLifecycle_Status4_Completed_Rejected()
{
// status=4 已完成学员:严禁直接签到
TrainEnrollment e4 = new TrainEnrollment();
e4.setEnrollmentId(304L);
e4.setPlanId("PLAN_A");
e4.setEnrollmentStatus(4); // 4: 已完成
enrollments.put(304L, e4);
AttendanceSignInDTO dto = new AttendanceSignInDTO();
dto.setEnrollmentId(304L);
dto.setLessonId(1001L);
try
{
appService.signIn(dto, "admin");
fail("已结训学员必须被拦截禁止签到");
}
catch (ServiceException e)
{
assertEquals(AttendanceErrorCode.ATTENDANCE_SIGN_IN_ENROLLMENT_COMPLETED, (int) e.getCode());
assertEquals(AttendanceErrorCode.ATTENDANCE_SIGN_IN_ENROLLMENT_COMPLETED_MESSAGE, e.getMessage());
}
assertNull(checkinsByEnrollLesson.get("304_1001"));
}
@Test
public void testSignInLifecycle_UnknownOrNullStatus_Rejected()
{
// 未知或 null 参训状态:fail closed 严禁签到
TrainEnrollment eNull = new TrainEnrollment();
eNull.setEnrollmentId(305L);
eNull.setPlanId("PLAN_A");
eNull.setEnrollmentStatus(null);
enrollments.put(305L, eNull);
AttendanceSignInDTO dto = new AttendanceSignInDTO();
dto.setEnrollmentId(305L);
dto.setLessonId(1001L);
try
{
appService.signIn(dto, "admin");
fail("null 状态学员必须被 fail closed 拦截");
}
catch (ServiceException e)
{
assertEquals(AttendanceErrorCode.ATTENDANCE_SIGN_IN_ENROLLMENT_STATUS_INVALID, (int) e.getCode());
}
// 状态 99
eNull.setEnrollmentStatus(99);
try
{
appService.signIn(dto, "admin");
fail("非法状态 99 学员必须被拦截");
}
catch (ServiceException e)
{
assertEquals(AttendanceErrorCode.ATTENDANCE_SIGN_IN_ENROLLMENT_STATUS_INVALID, (int) e.getCode());
}
}
@Test
public void testSignInLifecycle_Status4_WithExistingCheckin_IdempotentRetainsFact()
{
// 场景:历史已经合法签到的学员,后来结训归档变为 status=4。重复调用 signIn 保持幂等返回已有事实,不产生新写
TrainEnrollment e4 = new TrainEnrollment();
e4.setEnrollmentId(306L);
e4.setPlanId("PLAN_A");
e4.setEnrollmentStatus(4); // 4: 已完成
enrollments.put(306L, e4);
TrainStudentCheckin historical = new TrainStudentCheckin();
historical.setCheckinId(6001L);
historical.setEnrollmentId(306L);
historical.setLessonId(1001L);
Date historicalTime = new Date(System.currentTimeMillis() - 86400000L);
historical.setSignInTime(historicalTime);
historical.setAttendanceStatus(1);
checkinsById.put(6001L, historical);
checkinsByEnrollLesson.put("306_1001", historical);
AttendanceSignInDTO dto = new AttendanceSignInDTO();
dto.setEnrollmentId(306L);
dto.setLessonId(1001L);
TrainStudentCheckin returned = appService.signIn(dto, "admin");
assertNotNull(returned);
assertEquals(Long.valueOf(6001L), returned.getCheckinId());
assertEquals(historicalTime, returned.getSignInTime());
}
@Test
public void testRosterLifecycleEligibility_Status0And4_CannotSignIn_Status1And2_CanSignIn()
{
// 测试名册对于 status 0, 1, 2, 4 的 canSignIn 权限对齐:
// 310 (status=0 待报到), 311 (status=1 已报到), 312 (status=2 请假), 314 (status=4 已完成)
enrollments.clear();
checkinsById.clear();
checkinsByEnrollLesson.clear();
TrainEnrollment e0 = new TrainEnrollment(); e0.setEnrollmentId(310L); e0.setPlanId("PLAN_A"); e0.setEnrollmentStatus(0); enrollments.put(310L, e0);
TrainEnrollment e1 = new TrainEnrollment(); e1.setEnrollmentId(311L); e1.setPlanId("PLAN_A"); e1.setEnrollmentStatus(1); enrollments.put(311L, e1);
TrainEnrollment e2 = new TrainEnrollment(); e2.setEnrollmentId(312L); e2.setPlanId("PLAN_A"); e2.setEnrollmentStatus(2); enrollments.put(312L, e2);
TrainEnrollment e4 = new TrainEnrollment(); e4.setEnrollmentId(314L); e4.setPlanId("PLAN_A"); e4.setEnrollmentStatus(4); enrollments.put(314L, e4);
List<com.ruoyi.train.domain.vo.AttendanceLessonRosterVO> roster = appService.getLessonRoster(1001L);
assertEquals(4, roster.size());
com.ruoyi.train.domain.vo.AttendanceLessonRosterVO r0 = roster.stream().filter(r -> r.getEnrollmentId().equals(310L)).findFirst().orElse(null);
assertNotNull(r0);
assertFalse("status=0 待报到学员名册 canSignIn 必须为 false", Boolean.TRUE.equals(r0.getCanSignIn()));
com.ruoyi.train.domain.vo.AttendanceLessonRosterVO r1 = roster.stream().filter(r -> r.getEnrollmentId().equals(311L)).findFirst().orElse(null);
assertNotNull(r1);
assertTrue("status=1 已报到学员名册 canSignIn 必须为 true", Boolean.TRUE.equals(r1.getCanSignIn()));
com.ruoyi.train.domain.vo.AttendanceLessonRosterVO r2 = roster.stream().filter(r -> r.getEnrollmentId().equals(312L)).findFirst().orElse(null);
assertNotNull(r2);
assertTrue("status=2 请假学员名册 canSignIn 必须为 true (Preview Default)", Boolean.TRUE.equals(r2.getCanSignIn()));
com.ruoyi.train.domain.vo.AttendanceLessonRosterVO r4 = roster.stream().filter(r -> r.getEnrollmentId().equals(314L)).findFirst().orElse(null);
assertNotNull(r4);
assertFalse("status=4 已结训学员名册 canSignIn 必须为 false", Boolean.TRUE.equals(r4.getCanSignIn()));
}
}