修复稳定化:接入考勤人工调整闭环并增加住宿与门禁历史删除防护
This commit is contained in:
1 parent
5bcb6273ef
commit
b1433dcaa1
7 files changed
+491
-9
No files matched your search
+233
@@ -0,0 +1,233 @@
|
||||
package com.ruoyi.web.controller.train;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import com.ruoyi.common.exception.ServiceException;
|
||||
import com.ruoyi.train.domain.TrainAccommodation;
|
||||
import com.ruoyi.train.domain.TrainDormAuth;
|
||||
import com.ruoyi.train.domain.dto.AttendanceAdjustDTO;
|
||||
import com.ruoyi.train.mapper.TrainAccommodationMapper;
|
||||
import com.ruoyi.train.mapper.TrainDormAuthMapper;
|
||||
import com.ruoyi.train.service.impl.TrainAccommodationServiceImpl;
|
||||
import com.ruoyi.train.service.impl.TrainDormAuthServiceImpl;
|
||||
|
||||
/**
|
||||
* 试用前最终内部缺陷修复 (PRE-CUSTOMER FINAL INTERNAL FIXES) 契约与防护测试:
|
||||
* 1. 考勤人工修正 Controller 契约与权限一致性 (train:attendance:edit)
|
||||
* 2. 住宿历史记录物理删除拦截 (ACCOMMODATION_HISTORY_PHYSICAL_DELETE_BLOCKED)
|
||||
* 3. 门禁注销授权与操作流水物理删除拦截 (DOOR_AUTH_HISTORY_PHYSICAL_DELETE_BLOCKED)
|
||||
*/
|
||||
public class PreCustomerStabilizationFinalFixesContractTest
|
||||
{
|
||||
private TrainAccommodationServiceImpl accommodationService;
|
||||
private TrainDormAuthServiceImpl dormAuthService;
|
||||
private Map<Long, TrainAccommodation> accommodationStore;
|
||||
private Map<Long, TrainDormAuth> authStore;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception
|
||||
{
|
||||
accommodationService = new TrainAccommodationServiceImpl();
|
||||
dormAuthService = new TrainDormAuthServiceImpl();
|
||||
accommodationStore = new HashMap<>();
|
||||
authStore = new HashMap<>();
|
||||
|
||||
TrainAccommodationMapper mockAccMapper = (TrainAccommodationMapper) java.lang.reflect.Proxy.newProxyInstance(
|
||||
TrainAccommodationMapper.class.getClassLoader(),
|
||||
new Class<?>[]{TrainAccommodationMapper.class},
|
||||
(proxy, method, args) -> {
|
||||
String name = method.getName();
|
||||
if ("selectTrainAccommodationByAccommodationId".equals(name))
|
||||
{
|
||||
return accommodationStore.get(args[0]);
|
||||
}
|
||||
if ("deleteTrainAccommodationByAccommodationId".equals(name))
|
||||
{
|
||||
return accommodationStore.remove(args[0]) != null ? 1 : 0;
|
||||
}
|
||||
if (method.getReturnType().equals(int.class)) return 0;
|
||||
return null;
|
||||
}
|
||||
);
|
||||
|
||||
TrainDormAuthMapper mockAuthMapper = (TrainDormAuthMapper) java.lang.reflect.Proxy.newProxyInstance(
|
||||
TrainDormAuthMapper.class.getClassLoader(),
|
||||
new Class<?>[]{TrainDormAuthMapper.class},
|
||||
(proxy, method, args) -> {
|
||||
String name = method.getName();
|
||||
if ("selectTrainDormAuthByAuthId".equals(name))
|
||||
{
|
||||
return authStore.get(args[0]);
|
||||
}
|
||||
if ("deleteTrainDormAuthByAuthId".equals(name))
|
||||
{
|
||||
return authStore.remove(args[0]) != null ? 1 : 0;
|
||||
}
|
||||
if (method.getReturnType().equals(int.class)) return 0;
|
||||
return null;
|
||||
}
|
||||
);
|
||||
|
||||
Field accField = TrainAccommodationServiceImpl.class.getDeclaredField("trainAccommodationMapper");
|
||||
accField.setAccessible(true);
|
||||
accField.set(accommodationService, mockAccMapper);
|
||||
|
||||
Field authField = TrainDormAuthServiceImpl.class.getDeclaredField("trainDormAuthMapper");
|
||||
authField.setAccessible(true);
|
||||
authField.set(dormAuthService, mockAuthMapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. 验证 AttendanceController 的人工调整契约与权限注解严格为 train:attendance:edit
|
||||
*/
|
||||
@Test
|
||||
public void testAttendanceAdjustControllerContract() throws Exception
|
||||
{
|
||||
Method adjustMethod = AttendanceController.class.getMethod("adjust", Long.class, AttendanceAdjustDTO.class);
|
||||
assertNotNull("adjust 方法必须存在", adjustMethod);
|
||||
|
||||
PutMapping putMapping = adjustMethod.getAnnotation(PutMapping.class);
|
||||
assertNotNull("必须声明 @PutMapping", putMapping);
|
||||
assertEquals("/{checkinId}", putMapping.value()[0]);
|
||||
|
||||
PreAuthorize preAuth = adjustMethod.getAnnotation(PreAuthorize.class);
|
||||
assertNotNull("必须声明 @PreAuthorize 权限校验", preAuth);
|
||||
assertEquals("@ss.hasPermi('train:attendance:edit')", preAuth.value());
|
||||
}
|
||||
|
||||
/**
|
||||
* 2. 住宿历史删除防护:
|
||||
* A. 预分配错误/尚未真实入住:允许物理清理
|
||||
* B. 活跃在住或已退宿真实历史:强行拦截并保护历史事实
|
||||
*/
|
||||
@Test
|
||||
public void testAccommodationHistoricalDeleteGuard()
|
||||
{
|
||||
// Case A: 预分配错误 (STATUS_PREALLOCATED = 0)
|
||||
TrainAccommodation draft = new TrainAccommodation();
|
||||
draft.setAccommodationId(9001L);
|
||||
draft.setAccommodationStatus(TrainAccommodation.STATUS_PREALLOCATED);
|
||||
accommodationStore.put(draft.getAccommodationId(), draft);
|
||||
|
||||
int draftDeleted = accommodationService.deleteTrainAccommodationByAccommodationId(9001L);
|
||||
assertEquals("未实际入住的预分配记录允许纠错删除", 1, draftDeleted);
|
||||
|
||||
// Case B1: 活跃在住 (STATUS_CHECKED_IN = 1)
|
||||
TrainAccommodation active = new TrainAccommodation();
|
||||
active.setAccommodationId(9002L);
|
||||
active.setAccommodationStatus(TrainAccommodation.STATUS_CHECKED_IN);
|
||||
accommodationStore.put(active.getAccommodationId(), active);
|
||||
|
||||
try
|
||||
{
|
||||
accommodationService.deleteTrainAccommodationByAccommodationId(9002L);
|
||||
fail("在住记录禁止物理删除");
|
||||
}
|
||||
catch (ServiceException e)
|
||||
{
|
||||
assertTrue(e.getMessage().contains("当前学员正处于已入住状态"));
|
||||
}
|
||||
|
||||
// Case B2: 已退宿历史记录 (STATUS_CHECKED_OUT = 2) - 核心历史履历防护
|
||||
TrainAccommodation historical = new TrainAccommodation();
|
||||
historical.setAccommodationId(9003L);
|
||||
historical.setAccommodationStatus(TrainAccommodation.STATUS_CHECKED_OUT);
|
||||
accommodationStore.put(historical.getAccommodationId(), historical);
|
||||
|
||||
try
|
||||
{
|
||||
accommodationService.deleteTrainAccommodationByAccommodationId(9003L);
|
||||
fail("已退宿真实历史记录禁止物理删除,必须保留历史履历事实");
|
||||
}
|
||||
catch (ServiceException e)
|
||||
{
|
||||
assertTrue(e.getMessage().contains("禁止物理删除历史履历")
|
||||
|| e.getMessage().contains("真实历史入住与退宿事实"));
|
||||
}
|
||||
assertNotNull("历史退宿记录在库中依然完好保存", accommodationStore.get(9003L));
|
||||
}
|
||||
|
||||
/**
|
||||
* 3. 门禁注销授权与操作流水物理删除拦截防护:
|
||||
* A. 初始草稿尚未产生操作:允许删除
|
||||
* B. 生效中/授权中:必须先注销
|
||||
* C. 已注销/已关联外部流水:严禁物理删除,保全审计溯源链
|
||||
*/
|
||||
@Test
|
||||
public void testDoorAuthHistoryPhysicalDeleteGuard()
|
||||
{
|
||||
// Case A: 初始草稿待下发 (STATUS_PENDING / AUTH_STATUS_PENDING_GRANT)
|
||||
TrainDormAuth draft = new TrainDormAuth();
|
||||
draft.setAuthId(8001L);
|
||||
draft.setFaceSyncStatus(TrainDormAuth.STATUS_PENDING);
|
||||
draft.setAuthStatus(TrainDormAuth.AUTH_STATUS_PENDING_GRANT);
|
||||
authStore.put(draft.getAuthId(), draft);
|
||||
|
||||
int draftDeleted = dormAuthService.deleteTrainDormAuthByAuthId(8001L);
|
||||
assertEquals("无外部流水草稿允许清理", 1, draftDeleted);
|
||||
|
||||
// Case B: 生效中授权 (ACTIVE)
|
||||
TrainDormAuth active = new TrainDormAuth();
|
||||
active.setAuthId(8002L);
|
||||
active.setFaceSyncStatus(TrainDormAuth.STATUS_ACTIVE);
|
||||
active.setAuthStatus(TrainDormAuth.AUTH_STATUS_ACTIVE);
|
||||
authStore.put(active.getAuthId(), active);
|
||||
|
||||
try
|
||||
{
|
||||
dormAuthService.deleteTrainDormAuthByAuthId(8002L);
|
||||
fail("生效中门禁授权禁止直接删除");
|
||||
}
|
||||
catch (ServiceException e)
|
||||
{
|
||||
assertTrue(e.getMessage().contains("当前门禁授权处于生效或授权中状态"));
|
||||
}
|
||||
|
||||
// Case C: 已注销授权 (REVOKED) - 核心安全审计链防护
|
||||
TrainDormAuth revoked = new TrainDormAuth();
|
||||
revoked.setAuthId(8003L);
|
||||
revoked.setFaceSyncStatus(TrainDormAuth.STATUS_REVOKED);
|
||||
revoked.setAuthStatus(TrainDormAuth.AUTH_STATUS_REVOKED);
|
||||
authStore.put(revoked.getAuthId(), revoked);
|
||||
|
||||
try
|
||||
{
|
||||
dormAuthService.deleteTrainDormAuthByAuthId(8003L);
|
||||
fail("已注销门禁授权严禁物理删除,以保障审计链完整");
|
||||
}
|
||||
catch (ServiceException e)
|
||||
{
|
||||
assertTrue(e.getMessage().contains("禁止物理删除历史凭证")
|
||||
|| e.getMessage().contains("已进入授权生命周期"));
|
||||
}
|
||||
assertNotNull("已注销门禁授权依然完好存在", authStore.get(8003L));
|
||||
|
||||
// Case D: 带有外部操作流水关联 (latestOperationId != null)
|
||||
TrainDormAuth opLinked = new TrainDormAuth();
|
||||
opLinked.setAuthId(8004L);
|
||||
opLinked.setLatestOperationId(6666L);
|
||||
authStore.put(opLinked.getAuthId(), opLinked);
|
||||
|
||||
try
|
||||
{
|
||||
dormAuthService.deleteTrainDormAuthByAuthId(8004L);
|
||||
fail("关联外部流水的门禁授权严禁物理删除");
|
||||
}
|
||||
catch (ServiceException e)
|
||||
{
|
||||
assertTrue(e.getMessage().contains("产生外部审计流水"));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user