测试门禁:覆盖失败提示与空响应重发保护
This commit is contained in:
1 parent
63914e2499
commit
481c998e82
4 files changed
+401
No files matched your search
+145
@@ -19,6 +19,7 @@ import com.ruoyi.train.integration.dms.gateway.ExternalCredentialGateway;
|
||||
import com.ruoyi.train.integration.dms.orchestration.AccessAuthorizationOrchestrator;
|
||||
import com.ruoyi.train.integration.dms.orchestration.BatchReconciliationResult;
|
||||
import com.ruoyi.train.integration.dms.orchestration.ReconciliationDecision;
|
||||
import com.ruoyi.train.integration.dms.orchestration.ReconciliationItemVO;
|
||||
import com.ruoyi.train.integration.dms.orchestration.ReconciliationResult;
|
||||
import com.ruoyi.train.mapper.TrainAccommodationMapper;
|
||||
import com.ruoyi.train.mapper.TrainClassPlanMapper;
|
||||
@@ -474,6 +475,146 @@ public class DmsConsistencyHardeningPackageA1Test {
|
||||
assertEquals(TrainExternalOperation.STATUS_CREATED, op.getOperationStatus());
|
||||
}
|
||||
|
||||
/**
|
||||
* MAJ-03 行为测试 1: 2 dispatched + 1 failed -> PARTIAL_FAILURE -> success=false
|
||||
*/
|
||||
@Test
|
||||
public void testMaj03_Behavior1_TwoDispatchedOneFailed_PartialFailure_SuccessFalse() {
|
||||
BatchReconciliationResult batch = new BatchReconciliationResult(101L);
|
||||
batch.addItem(new ReconciliationItemVO(1L, 11L, "REVOKE", "DISPATCHED", "ok"));
|
||||
batch.addItem(new ReconciliationItemVO(2L, 12L, "REVOKE", "DISPATCHED", "ok"));
|
||||
batch.addItem(new ReconciliationItemVO(3L, 13L, "REVOKE", "FAILED", "vendor error"));
|
||||
batch.setDispatched(2);
|
||||
batch.setFailed(1);
|
||||
batch.computeOutcome();
|
||||
|
||||
assertEquals(BatchReconciliationResult.OUTCOME_PARTIAL_FAILURE, batch.getOutcome());
|
||||
assertFalse(batch.isSuccess());
|
||||
|
||||
ReconciliationResult recon = ReconciliationResult.batch(batch);
|
||||
assertFalse("ReconciliationResult.batch() success 必须为 false", recon.isSuccess());
|
||||
assertTrue(recon.isExternalInvoked());
|
||||
}
|
||||
|
||||
/**
|
||||
* MAJ-03 行为测试 2: 全部 failed -> ALL_FAILED -> success=false
|
||||
*/
|
||||
@Test
|
||||
public void testMaj03_Behavior2_AllFailed_SuccessFalse() {
|
||||
BatchReconciliationResult batch = new BatchReconciliationResult(101L);
|
||||
batch.addItem(new ReconciliationItemVO(1L, 11L, "REVOKE", "FAILED", "error 1"));
|
||||
batch.addItem(new ReconciliationItemVO(2L, 12L, "REVOKE", "FAILED", "error 2"));
|
||||
batch.setFailed(2);
|
||||
batch.computeOutcome();
|
||||
|
||||
assertEquals(BatchReconciliationResult.OUTCOME_ALL_FAILED, batch.getOutcome());
|
||||
assertFalse(batch.isSuccess());
|
||||
|
||||
ReconciliationResult recon = ReconciliationResult.batch(batch);
|
||||
assertFalse("ReconciliationResult.batch() success 必须为 false", recon.isSuccess());
|
||||
assertFalse(recon.isExternalInvoked());
|
||||
}
|
||||
|
||||
/**
|
||||
* MAJ-03 行为测试 3: manualRequired > 0 -> REQUIRES_MANUAL_SETTLEMENT -> success=false
|
||||
*/
|
||||
@Test
|
||||
public void testMaj03_Behavior3_ManualRequired_RequiresManualSettlement_SuccessFalse() {
|
||||
BatchReconciliationResult batch = new BatchReconciliationResult(101L);
|
||||
batch.addItem(new ReconciliationItemVO(1L, 11L, "REVOKE", "MANUAL_REQUIRED", "AMBIGUOUS_EXTERNAL_CREDENTIAL"));
|
||||
batch.setManualRequired(1);
|
||||
batch.computeOutcome();
|
||||
|
||||
assertEquals(BatchReconciliationResult.OUTCOME_REQUIRES_MANUAL_SETTLEMENT, batch.getOutcome());
|
||||
assertFalse(batch.isSuccess());
|
||||
|
||||
ReconciliationResult recon = ReconciliationResult.batch(batch);
|
||||
assertFalse("ReconciliationResult.batch() success 必须为 false", recon.isSuccess());
|
||||
assertFalse(recon.isExternalInvoked());
|
||||
}
|
||||
|
||||
/**
|
||||
* MAJ-03 行为测试 4: queued only -> WAITING_FOR_DMS -> success=true -> externalInvoked=false
|
||||
*/
|
||||
@Test
|
||||
public void testMaj03_Behavior4_QueuedOnly_WaitingForDms_SuccessTrue_ExternalInvokedFalse() {
|
||||
BatchReconciliationResult batch = new BatchReconciliationResult(101L);
|
||||
batch.addItem(new ReconciliationItemVO(1L, 11L, "REVOKE", "QUEUED", "DMS disabled"));
|
||||
batch.setQueued(1);
|
||||
batch.computeOutcome();
|
||||
|
||||
assertEquals(BatchReconciliationResult.OUTCOME_WAITING_FOR_DMS, batch.getOutcome());
|
||||
assertTrue(batch.isSuccess());
|
||||
assertFalse(batch.isExternalInvoked());
|
||||
|
||||
ReconciliationResult recon = ReconciliationResult.batch(batch);
|
||||
assertTrue("ReconciliationResult.batch() success 必须为 true", recon.isSuccess());
|
||||
assertFalse("ReconciliationResult.batch() externalInvoked 必须为 false", recon.isExternalInvoked());
|
||||
}
|
||||
|
||||
/**
|
||||
* MAJ-03 行为测试 5: dispatched only -> IN_PROGRESS -> success=true
|
||||
*/
|
||||
@Test
|
||||
public void testMaj03_Behavior5_DispatchedOnly_InProgress_SuccessTrue() {
|
||||
BatchReconciliationResult batch = new BatchReconciliationResult(101L);
|
||||
batch.addItem(new ReconciliationItemVO(1L, 11L, "REVOKE", "DISPATCHED", "ok"));
|
||||
batch.addItem(new ReconciliationItemVO(2L, 12L, "REVOKE", "DISPATCHED", "ok"));
|
||||
batch.setDispatched(2);
|
||||
batch.computeOutcome();
|
||||
|
||||
assertEquals(BatchReconciliationResult.OUTCOME_IN_PROGRESS, batch.getOutcome());
|
||||
assertTrue(batch.isSuccess());
|
||||
assertTrue(batch.isExternalInvoked());
|
||||
|
||||
ReconciliationResult recon = ReconciliationResult.batch(batch);
|
||||
assertTrue("ReconciliationResult.batch() success 必须为 true", recon.isSuccess());
|
||||
assertTrue("ReconciliationResult.batch() externalInvoked 必须为 true", recon.isExternalInvoked());
|
||||
}
|
||||
|
||||
/**
|
||||
* EMPTY_RESPONSE ambiguity 行为测试:
|
||||
* 厂商 Mutation 返回 HTTP 200,但 body 为空字符串/null,
|
||||
* 异常被归类为不确定性异常 (DmsTransportException),
|
||||
* 必须进入 operation = AMBIGUOUS, auth = ERROR,
|
||||
* 第二次 Issue 必须被重发守卫阻断,且 gateway total call 仍然 = 1,严禁 FAILED 导致重发!
|
||||
*/
|
||||
@Test
|
||||
public void testEmptyResponse_MutationClassifiedAsAmbiguous_BlocksReissue() {
|
||||
TrainDormAuth auth = new TrainDormAuth();
|
||||
auth.setAuthId(authSeq.incrementAndGet());
|
||||
auth.setAccommodationId(201L);
|
||||
auth.setAuthStatus(TrainDormAuth.AUTH_STATUS_PENDING_GRANT);
|
||||
auth.setCredentialType("ONLINE_PASSWORD");
|
||||
dormAuthDb.put(201L, auth);
|
||||
|
||||
gateway.shouldThrowEmptyResponse = true;
|
||||
|
||||
// 第一次下发:由于空响应,抛出 DmsTransportException
|
||||
IssueResultVO result1 = orchestrator.issueAuthorization(auth.getAuthId(), "admin");
|
||||
assertNotNull(result1);
|
||||
assertEquals("第一次下发由于空响应必须为 AMBIGUOUS_EXTERNAL_MUTATION",
|
||||
"AMBIGUOUS_EXTERNAL_MUTATION", result1.getStatus());
|
||||
|
||||
TrainExternalOperation op1 = operationService.selectLatestGrantOperationByDoorAuthId(auth.getAuthId());
|
||||
assertNotNull(op1);
|
||||
assertEquals("operation_status 必须为 AMBIGUOUS",
|
||||
TrainExternalOperation.STATUS_AMBIGUOUS, op1.getOperationStatus());
|
||||
|
||||
TrainDormAuth updatedAuth = dormAuthService.selectTrainDormAuthByAuthId(auth.getAuthId());
|
||||
assertEquals("auth_status 必须为 ERROR",
|
||||
TrainDormAuth.AUTH_STATUS_ERROR, updatedAuth.getAuthStatus());
|
||||
assertEquals("网关已被调用 1 次", 1, gateway.issueCallCount.get());
|
||||
|
||||
// 第二次下发:必须被重发守卫阻断,严禁重新调用外部网关!
|
||||
IssueResultVO result2 = orchestrator.issueAuthorization(auth.getAuthId(), "admin");
|
||||
assertNotNull(result2);
|
||||
assertEquals("第二次下发必须被拦截为 AMBIGUOUS_EXTERNAL_MUTATION",
|
||||
"AMBIGUOUS_EXTERNAL_MUTATION", result2.getStatus());
|
||||
assertEquals("网关总调用次数仍然必须为 1,绝对禁止第二次调用",
|
||||
1, gateway.issueCallCount.get());
|
||||
}
|
||||
|
||||
/**
|
||||
* T9: 同一宿舍同一有效床位(status in 0,1)重复分配:
|
||||
* Service 必须预检拦截并抛出明确业务错误 BED_ALREADY_OCCUPIED
|
||||
@@ -598,6 +739,7 @@ public class DmsConsistencyHardeningPackageA1Test {
|
||||
final AtomicInteger revokeCallCount = new AtomicInteger(0);
|
||||
boolean shouldThrowTimeout = false;
|
||||
boolean shouldRejectBusiness = false;
|
||||
boolean shouldThrowEmptyResponse = false;
|
||||
Long failOnKeyId = null;
|
||||
|
||||
@Override
|
||||
@@ -605,6 +747,9 @@ public class DmsConsistencyHardeningPackageA1Test {
|
||||
String plaintextPassword, String validStart, String validUntil) {
|
||||
issueCallCount.incrementAndGet();
|
||||
|
||||
if (shouldThrowEmptyResponse) {
|
||||
throw new DmsTransportException("EMPTY_RESPONSE: Received null response from DMS password creation");
|
||||
}
|
||||
if (shouldThrowTimeout) {
|
||||
throw new DmsTransportException("SocketTimeoutException: Read timed out after HTTP write", new SocketTimeoutException());
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user