From d4d70fe3a3a20f5fc29c9f37a1f89854a376597f Mon Sep 17 00:00:00 2001
From: yanderong <307678080@qq.com>
Date: 星期三, 16 九月 2026 18:42:36 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/2.3' into 2.3
---
ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/service/impl/TCardChargingOrderAmountFieldsTest.java | 179 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 179 insertions(+), 0 deletions(-)
diff --git a/ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/service/impl/TCardChargingOrderAmountFieldsTest.java b/ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/service/impl/TCardChargingOrderAmountFieldsTest.java
index 5372e59..fa05bfb 100644
--- a/ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/service/impl/TCardChargingOrderAmountFieldsTest.java
+++ b/ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/service/impl/TCardChargingOrderAmountFieldsTest.java
@@ -44,6 +44,8 @@
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -244,4 +246,181 @@
verify(cardClient).deductForCharging(deductCaptor.capture());
assertEquals(new BigDecimal("15.00"), deductCaptor.getValue().getAmount());
}
+
+ @Test
+ @SuppressWarnings("unchecked")
+ void deductFailureRollsFinishedOrderBackToStopping() throws InterruptedException {
+ TCardChargingOrderServiceImpl service = new TCardChargingOrderServiceImpl();
+ TCardChargingOrderMapper mapper = mock(TCardChargingOrderMapper.class);
+ ChargingCardClient cardClient = mock(ChargingCardClient.class);
+ ChargingGunClient gunClient = mock(ChargingGunClient.class);
+ TCardAccountingStrategyDetailOrderService detailOrderService = mock(TCardAccountingStrategyDetailOrderService.class);
+ TCardChargingOrderAccountingStrategyService strategyService = mock(TCardChargingOrderAccountingStrategyService.class);
+ TransactionTemplate txTemplate = mock(TransactionTemplate.class);
+ ReflectionTestUtils.setField(service, "baseMapper", mapper);
+ ReflectionTestUtils.setField(service, "chargingCardClient", cardClient);
+ ReflectionTestUtils.setField(service, "chargingGunClient", gunClient);
+ ReflectionTestUtils.setField(service, "cardAccountingStrategyDetailOrderService", detailOrderService);
+ ReflectionTestUtils.setField(service, "cardChargingOrderAccountingStrategyService", strategyService);
+ ReflectionTestUtils.setField(service, "transactionTemplate", txTemplate);
+ ReflectionTestUtils.setField(service, "redissonClient", mockedRedisson(mockedLock(true)));
+
+ TCardChargingOrder charging = new TCardChargingOrder();
+ charging.setId(1L);
+ charging.setCode("SD-FAIL");
+ charging.setStatus(4);
+ charging.setCardId(5L);
+ charging.setChargingGunId(9);
+ charging.setChargeAmount(new BigDecimal("100.00"));
+ when(mapper.selectOne(any())).thenReturn(charging);
+ when(mapper.updateById(any(TCardChargingOrder.class))).thenReturn(1);
+ when(mapper.update(any(), any())).thenReturn(1);
+
+ R<ChargingCardDeductVO> deductFailure = mock(R.class);
+ when(deductFailure.getCode()).thenReturn(500);
+ when(deductFailure.getMsg()).thenReturn("account unavailable");
+ when(cardClient.deductForCharging(any(ChargingCardDeductDTO.class))).thenReturn(deductFailure);
+
+ TCardAccountingStrategyDetailOrder snapshot = new TCardAccountingStrategyDetailOrder();
+ snapshot.setType(1);
+ snapshot.setElectrovalence(new BigDecimal("1.0000"));
+ snapshot.setServiceCharge(new BigDecimal("0.5000"));
+ snapshot.setCostServiceCharge(new BigDecimal("0.5000"));
+ LambdaQueryChainWrapper<TCardAccountingStrategyDetailOrder> queryWrapper = mock(LambdaQueryChainWrapper.class);
+ when(detailOrderService.lambdaQuery()).thenReturn(queryWrapper);
+ when(queryWrapper.eq(any(), any())).thenReturn(queryWrapper);
+ when(queryWrapper.list()).thenReturn(Collections.singletonList(snapshot));
+
+ LambdaUpdateChainWrapper<TCardChargingOrderAccountingStrategy> updateWrapper = mock(LambdaUpdateChainWrapper.class);
+ when(strategyService.lambdaUpdate()).thenReturn(updateWrapper);
+ when(updateWrapper.eq(any(), any())).thenReturn(updateWrapper);
+ when(updateWrapper.remove()).thenReturn(true);
+ when(strategyService.saveBatch(any(Collection.class))).thenReturn(true);
+ when(txTemplate.execute(any())).thenAnswer(invocation -> {
+ TransactionCallback<?> callback = invocation.getArgument(0);
+ return callback.doInTransaction(mock(TransactionStatus.class));
+ });
+
+ TransactionRecordMessageVO vo = new TransactionRecordMessageVO();
+ vo.setTransaction_serial_number("SD-FAIL");
+ vo.setSpike_charge1(BigDecimal.ONE);
+ R result = service.cardEndChargeBillingCharge(vo);
+
+ assertEquals(500, result.getCode());
+ verify(mapper).update(any(), any());
+ verify(cardClient, never()).unlockForCharging(anyLong(), anyString());
+ verify(gunClient, never()).getChargingGunById(any());
+ }
+
+ @Test
+ void compensationRollsBackOnlyFinishedOrdersWithoutDeductRecord() throws InterruptedException {
+ TCardChargingOrderServiceImpl service = new TCardChargingOrderServiceImpl();
+ TCardChargingOrderMapper mapper = mock(TCardChargingOrderMapper.class);
+ ChargingCardClient cardClient = mock(ChargingCardClient.class);
+ RLock lock = mockedLock(true);
+ ReflectionTestUtils.setField(service, "baseMapper", mapper);
+ ReflectionTestUtils.setField(service, "chargingCardClient", cardClient);
+ ReflectionTestUtils.setField(service, "redissonClient", mockedRedisson(lock));
+
+ TCardChargingOrder missing = new TCardChargingOrder();
+ missing.setId(1L);
+ missing.setCode("SD-MISSING");
+ missing.setStatus(5);
+ missing.setCardId(5L);
+ missing.setOrderAmount(BigDecimal.ONE);
+ when(mapper.selectList(any())).thenReturn(Collections.singletonList(missing));
+ when(mapper.update(any(), any())).thenReturn(1);
+ R<java.util.List<String>> noDeductRecords = okWithData(Collections.emptyList());
+ when(cardClient.listDeductedOrderCodes(any())).thenReturn(noDeductRecords);
+
+ assertEquals(1, service.recoverFinishedOrdersWithoutDeduct());
+ verify(cardClient, times(2)).listDeductedOrderCodes(any());
+ verify(mapper).update(any(), any());
+ }
+
+ @Test
+ void compensationSkipsFinishedOrdersHavingDeductRecord() {
+ TCardChargingOrderServiceImpl service = new TCardChargingOrderServiceImpl();
+ TCardChargingOrderMapper mapper = mock(TCardChargingOrderMapper.class);
+ ChargingCardClient cardClient = mock(ChargingCardClient.class);
+ ReflectionTestUtils.setField(service, "baseMapper", mapper);
+ ReflectionTestUtils.setField(service, "chargingCardClient", cardClient);
+
+ TCardChargingOrder deducted = new TCardChargingOrder();
+ deducted.setId(2L);
+ deducted.setCode("SD-DEDUCTED");
+ deducted.setStatus(5);
+ deducted.setCardId(5L);
+ deducted.setOrderAmount(BigDecimal.ONE);
+ when(mapper.selectList(any())).thenReturn(Collections.singletonList(deducted));
+ // 批量查询结果已包含该订单号时不再逐单复核,也不回退状态
+ R<java.util.List<String>> deductedRecords = okWithData(Collections.singletonList("SD-DEDUCTED"));
+ when(cardClient.listDeductedOrderCodes(any())).thenReturn(deductedRecords);
+
+ assertEquals(0, service.recoverFinishedOrdersWithoutDeduct());
+ verify(cardClient, times(1)).listDeductedOrderCodes(any());
+ verify(mapper, never()).update(any(), any());
+ }
+
+ @Test
+ void compensationKeepsFinishedStatusWhenDeductQueryFails() {
+ TCardChargingOrderServiceImpl service = new TCardChargingOrderServiceImpl();
+ TCardChargingOrderMapper mapper = mock(TCardChargingOrderMapper.class);
+ ChargingCardClient cardClient = mock(ChargingCardClient.class);
+ ReflectionTestUtils.setField(service, "baseMapper", mapper);
+ ReflectionTestUtils.setField(service, "chargingCardClient", cardClient);
+
+ TCardChargingOrder candidate = new TCardChargingOrder();
+ candidate.setId(3L);
+ candidate.setCode("SD-UNKNOWN");
+ candidate.setStatus(5);
+ candidate.setCardId(5L);
+ candidate.setOrderAmount(BigDecimal.ONE);
+ when(mapper.selectList(any())).thenReturn(Collections.singletonList(candidate));
+ // 账户服务不可用时无法确认扣款结果,必须保持原状态等待下轮,不能误回退
+ when(cardClient.listDeductedOrderCodes(any())).thenReturn(R.fail("account unavailable"));
+
+ assertEquals(0, service.recoverFinishedOrdersWithoutDeduct());
+ verify(mapper, never()).update(any(), any());
+ }
+
+ @Test
+ void compensationSkipsRemoteQueryWhenNoFinishedCandidate() {
+ TCardChargingOrderServiceImpl service = new TCardChargingOrderServiceImpl();
+ TCardChargingOrderMapper mapper = mock(TCardChargingOrderMapper.class);
+ ChargingCardClient cardClient = mock(ChargingCardClient.class);
+ ReflectionTestUtils.setField(service, "baseMapper", mapper);
+ ReflectionTestUtils.setField(service, "chargingCardClient", cardClient);
+
+ when(mapper.selectList(any())).thenReturn(Collections.emptyList());
+
+ assertEquals(0, service.recoverFinishedOrdersWithoutDeduct());
+ verify(cardClient, never()).listDeductedOrderCodes(any());
+ verify(mapper, never()).update(any(), any());
+ }
+
+ @Test
+ void compensationSkipsOrderWhenSettleLockNotAcquired() throws InterruptedException {
+ TCardChargingOrderServiceImpl service = new TCardChargingOrderServiceImpl();
+ TCardChargingOrderMapper mapper = mock(TCardChargingOrderMapper.class);
+ ChargingCardClient cardClient = mock(ChargingCardClient.class);
+ ReflectionTestUtils.setField(service, "baseMapper", mapper);
+ ReflectionTestUtils.setField(service, "chargingCardClient", cardClient);
+ // 结算锁被其他线程持有:本轮回退必须让行,交由持锁方完成结算
+ ReflectionTestUtils.setField(service, "redissonClient", mockedRedisson(mockedLock(false)));
+
+ TCardChargingOrder candidate = new TCardChargingOrder();
+ candidate.setId(4L);
+ candidate.setCode("SD-LOCKED");
+ candidate.setStatus(5);
+ candidate.setCardId(5L);
+ candidate.setOrderAmount(BigDecimal.ONE);
+ when(mapper.selectList(any())).thenReturn(Collections.singletonList(candidate));
+ R<java.util.List<String>> noDeductRecords = okWithData(Collections.emptyList());
+ when(cardClient.listDeductedOrderCodes(any())).thenReturn(noDeductRecords);
+
+ assertEquals(0, service.recoverFinishedOrdersWithoutDeduct());
+ verify(cardClient, times(1)).listDeductedOrderCodes(any());
+ verify(mapper, never()).update(any(), any());
+ }
}
--
Gitblit v1.7.1