puzhibing
2026-09-14 34e836924716843f64a6c68442e5fb297d0e25e4
ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/service/impl/CardVinOrderCompensationTest.java
@@ -1,21 +1,43 @@
package com.ruoyi.order.service.impl;
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.baomidou.mybatisplus.extension.conditions.update.LambdaUpdateChainWrapper;
import com.ruoyi.account.api.feignClient.ChargingCardClient;
import com.ruoyi.account.api.vo.ChargingCardDeductVO;
import com.ruoyi.chargingPile.api.feignClient.ChargingGunClient;
import com.ruoyi.chargingPile.api.model.TChargingGun;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.order.api.model.TCardAccountingStrategyDetailOrder;
import com.ruoyi.order.api.model.TCardChargingOrder;
import com.ruoyi.order.api.model.TCardChargingOrderAccountingStrategy;
import com.ruoyi.order.api.model.TVinAccountingStrategyDetailOrder;
import com.ruoyi.order.api.model.TVinChargingOrder;
import com.ruoyi.order.api.model.TVinChargingOrderAccountingStrategy;
import com.ruoyi.order.api.vo.TransactionRecordMessageVO;
import com.ruoyi.order.mapper.TCardChargingOrderMapper;
import com.ruoyi.order.mapper.TVinChargingOrderMapper;
import com.ruoyi.order.service.TCardAccountingStrategyDetailOrderService;
import com.ruoyi.order.service.TCardChargingOrderAccountingStrategyService;
import com.ruoyi.order.service.TVinAccountingStrategyDetailOrderService;
import com.ruoyi.order.service.TVinChargingOrderAccountingStrategyService;
import org.apache.ibatis.builder.MapperBuilderAssistant;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -38,9 +60,21 @@
 */
class CardVinOrderCompensationTest {
    /**
     * 纯单测无 Spring 容器,MyBatis-Plus 的 lambda 列缓存需手工初始化,
     * 否则服务实现里的 LambdaUpdateWrapper 解析列名时抛 "can not find lambda cache"
     */
    @BeforeAll
    static void initMybatisPlusTableInfo() {
        MapperBuilderAssistant assistant = new MapperBuilderAssistant(new MybatisConfiguration(), "");
        TableInfoHelper.initTableInfo(assistant, TCardChargingOrder.class);
        TableInfoHelper.initTableInfo(assistant, TVinChargingOrder.class);
    }
    @SuppressWarnings("unchecked")
    private R<Boolean> okBoolean() {
        R<Boolean> result = mock(R.class);
        when(result.getCode()).thenReturn(R.SUCCESS);
        when(result.getData()).thenReturn(true);
        return result;
    }
@@ -48,6 +82,7 @@
    @SuppressWarnings("unchecked")
    private R<TChargingGun> gunOf(TChargingGun gun) {
        R<TChargingGun> result = mock(R.class);
        when(result.getCode()).thenReturn(R.SUCCESS);
        when(result.getData()).thenReturn(gun);
        return result;
    }
@@ -76,7 +111,7 @@
        ReflectionTestUtils.setField(service, "baseMapper", mapper);
        ReflectionTestUtils.setField(service, "chargingCardClient", cardClient);
        ReflectionTestUtils.setField(service, "chargingGunClient", gunClient);
        when(mapper.updateById(any(TCardChargingOrder.class))).thenReturn(1);
        when(mapper.update(any(TCardChargingOrder.class), any())).thenReturn(1);
        R<Boolean> unlockResult = okBoolean();
        when(cardClient.unlockForCharging(anyLong(), anyString())).thenReturn(unlockResult);
        TChargingGun gun = new TChargingGun();
@@ -94,7 +129,7 @@
        service.closeTimeoutStartingOrder(order);
        ArgumentCaptor<TCardChargingOrder> captor = ArgumentCaptor.forClass(TCardChargingOrder.class);
        verify(mapper).updateById(captor.capture());
        verify(mapper).update(captor.capture(), any());
        assertEquals(-1, captor.getValue().getStatus());
        assertEquals(0, captor.getValue().getEndMode());
        assertNotNull(captor.getValue().getEndTime());
@@ -102,6 +137,30 @@
        ArgumentCaptor<TChargingGun> gunCaptor = ArgumentCaptor.forClass(TChargingGun.class);
        verify(gunClient).updateChargingGunById(gunCaptor.capture());
        assertEquals(2, gunCaptor.getValue().getStatus());
    }
    @Test
    void ignoresLateCardStartReplyAfterOrderClosed() {
        TCardChargingOrderServiceImpl service = new TCardChargingOrderServiceImpl();
        TCardChargingOrderMapper mapper = mock(TCardChargingOrderMapper.class);
        ChargingGunClient gunClient = mock(ChargingGunClient.class);
        ReflectionTestUtils.setField(service, "baseMapper", mapper);
        ReflectionTestUtils.setField(service, "chargingGunClient", gunClient);
        TCardChargingOrder closed = new TCardChargingOrder();
        closed.setId(1L);
        closed.setCode("SD1");
        closed.setStatus(-1);
        when(mapper.selectOne(any())).thenReturn(closed);
        when(mapper.selectList(any())).thenReturn(Collections.singletonList(closed));
        com.ruoyi.order.api.vo.PlatformStartChargingReplyMessageVO reply =
                new com.ruoyi.order.api.vo.PlatformStartChargingReplyMessageVO();
        reply.setTransaction_serial_number("SD1");
        reply.setStartup_result(1);
        service.cardStartChargeSuccessfully(reply);
        verify(mapper, never()).update(any(), any());
        verifyNoInteractions(gunClient);
    }
    // ===== SD:结算锁抢占失败快速返回 =====
@@ -159,7 +218,7 @@
        ChargingGunClient gunClient = mock(ChargingGunClient.class);
        ReflectionTestUtils.setField(service, "baseMapper", mapper);
        ReflectionTestUtils.setField(service, "chargingGunClient", gunClient);
        when(mapper.updateById(any(TVinChargingOrder.class))).thenReturn(1);
        when(mapper.update(any(TVinChargingOrder.class), any())).thenReturn(1);
        TChargingGun gun = new TChargingGun();
        gun.setId(7);
        gun.setStatus(4);
@@ -174,13 +233,37 @@
        service.closeTimeoutStartingOrder(order);
        ArgumentCaptor<TVinChargingOrder> captor = ArgumentCaptor.forClass(TVinChargingOrder.class);
        verify(mapper).updateById(captor.capture());
        verify(mapper).update(captor.capture(), any());
        assertEquals(-1, captor.getValue().getStatus());
        assertEquals(0, captor.getValue().getEndMode());
        assertNotNull(captor.getValue().getEndTime());
        ArgumentCaptor<TChargingGun> gunCaptor = ArgumentCaptor.forClass(TChargingGun.class);
        verify(gunClient).updateChargingGunById(gunCaptor.capture());
        assertEquals(2, gunCaptor.getValue().getStatus());
    }
    @Test
    void ignoresLateVinStartReplyAfterOrderClosed() {
        TVinChargingOrderServiceImpl service = new TVinChargingOrderServiceImpl();
        TVinChargingOrderMapper mapper = mock(TVinChargingOrderMapper.class);
        ChargingGunClient gunClient = mock(ChargingGunClient.class);
        ReflectionTestUtils.setField(service, "baseMapper", mapper);
        ReflectionTestUtils.setField(service, "chargingGunClient", gunClient);
        TVinChargingOrder closed = new TVinChargingOrder();
        closed.setId(2L);
        closed.setCode("VD1");
        closed.setStatus(-1);
        when(mapper.selectOne(any())).thenReturn(closed);
        when(mapper.selectList(any())).thenReturn(Collections.singletonList(closed));
        com.ruoyi.order.api.vo.PlatformStartChargingReplyMessageVO reply =
                new com.ruoyi.order.api.vo.PlatformStartChargingReplyMessageVO();
        reply.setTransaction_serial_number("VD1");
        reply.setStartup_result(1);
        service.vinStartChargeSuccessfully(reply);
        verify(mapper, never()).update(any(), any());
        verifyNoInteractions(gunClient);
    }
    // ===== VD:已结算订单重复账单幂等 =====
@@ -210,4 +293,167 @@
        assertEquals(R.SUCCESS, result.getCode());
        verify(gunClient, never()).updateChargingGunById(any(TChargingGun.class));
    }
    // ===== VD:结算明细补齐计费策略明细id与首次添加时间 =====
    @Test
    @SuppressWarnings("unchecked")
    void fillsVinSettlementDetailStrategyIdAndCreateTime() throws InterruptedException {
        TVinChargingOrderServiceImpl service = new TVinChargingOrderServiceImpl();
        TVinChargingOrderMapper mapper = mock(TVinChargingOrderMapper.class);
        ChargingGunClient gunClient = mock(ChargingGunClient.class);
        TVinAccountingStrategyDetailOrderService detailOrderService = mock(TVinAccountingStrategyDetailOrderService.class);
        TVinChargingOrderAccountingStrategyService strategyService = mock(TVinChargingOrderAccountingStrategyService.class);
        TransactionTemplate txTemplate = mock(TransactionTemplate.class);
        ReflectionTestUtils.setField(service, "baseMapper", mapper);
        ReflectionTestUtils.setField(service, "chargingGunClient", gunClient);
        ReflectionTestUtils.setField(service, "vinAccountingStrategyDetailOrderService", detailOrderService);
        ReflectionTestUtils.setField(service, "vinChargingOrderAccountingStrategyService", strategyService);
        ReflectionTestUtils.setField(service, "transactionTemplate", txTemplate);
        ReflectionTestUtils.setField(service, "redissonClient", mockedRedisson(mockedLock(true)));
        TVinChargingOrder charging = new TVinChargingOrder();
        charging.setId(2L);
        charging.setCode("VD1");
        charging.setStatus(4);
        charging.setChargingGunId(7);
        when(mapper.selectOne(any())).thenReturn(charging);
        when(mapper.updateById(any())).thenReturn(1);
        TChargingGun gun = new TChargingGun();
        gun.setId(7);
        gun.setStatus(4);
        R<TChargingGun> gunResult = gunOf(gun);
        when(gunClient.getChargingGunById(any())).thenReturn(gunResult);
        // 计费策略快照(尖段,电费1.0/服务费0.5),明细id=1920101(IdType.INPUT复制自原明细id)
        TVinAccountingStrategyDetailOrder snapshot = new TVinAccountingStrategyDetailOrder();
        snapshot.setId(1920101);
        snapshot.setType(1);
        snapshot.setStartTime("00:00");
        snapshot.setEndTime("06:00");
        snapshot.setElectrovalence(new BigDecimal("1.0000"));
        snapshot.setServiceCharge(new BigDecimal("0.5000"));
        snapshot.setCostServiceCharge(new BigDecimal("0.5000"));
        LambdaQueryChainWrapper<TVinAccountingStrategyDetailOrder> queryWrapper = mock(LambdaQueryChainWrapper.class);
        when(detailOrderService.lambdaQuery()).thenReturn(queryWrapper);
        when(queryWrapper.eq(any(), any())).thenReturn(queryWrapper);
        when(queryWrapper.list()).thenReturn(Collections.singletonList(snapshot));
        LambdaUpdateChainWrapper<TVinChargingOrderAccountingStrategy> 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("VD1");
        vo.setSpike_charge1(new BigDecimal("5"));
        R result = service.vinEndChargeBillingCharge(vo);
        assertEquals(R.SUCCESS, result.getCode());
        ArgumentCaptor<Collection<TVinChargingOrderAccountingStrategy>> captor = ArgumentCaptor.forClass(Collection.class);
        verify(strategyService).saveBatch(captor.capture());
        List<TVinChargingOrderAccountingStrategy> saved = new ArrayList<>(captor.getValue());
        assertEquals(1, saved.size());
        assertEquals(Integer.valueOf(1920101), saved.get(0).getAccountingStrategyDetailId());
        assertNotNull(saved.get(0).getCreateTime());
        assertEquals(Integer.valueOf(1), saved.get(0).getType());
        assertEquals("00:00", saved.get(0).getStartTime());
        assertEquals("06:00", saved.get(0).getEndTime());
    }
    // ===== SD:结算明细补齐计费策略明细id与首次添加时间 =====
    @Test
    @SuppressWarnings("unchecked")
    void fillsCardSettlementDetailStrategyIdAndCreateTime() 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("SD1");
        charging.setStatus(4);
        charging.setCardId(5L);
        charging.setChargingGunId(9);
        charging.setChargeAmount(new BigDecimal("20.00"));
        when(mapper.selectOne(any())).thenReturn(charging);
        when(mapper.updateById(any())).thenReturn(1);
        // 计费策略快照(尖段,电费1.0/服务费0.5),明细id=1910101(IdType.INPUT复制自原明细id)
        TCardAccountingStrategyDetailOrder snapshot = new TCardAccountingStrategyDetailOrder();
        snapshot.setId(1910101);
        snapshot.setType(1);
        snapshot.setStartTime("00:00");
        snapshot.setEndTime("06:00");
        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));
        });
        // 扣款/解锁/复位枪桩成功返回,聚焦结算明细字段断言
        ChargingCardDeductVO deductVO = new ChargingCardDeductVO();
        R<ChargingCardDeductVO> deductResult = mock(R.class);
        when(deductResult.getCode()).thenReturn(R.SUCCESS);
        when(deductResult.getData()).thenReturn(deductVO);
        when(cardClient.deductForCharging(any(com.ruoyi.account.api.dto.ChargingCardDeductDTO.class)))
                .thenReturn(deductResult);
        R<Boolean> unlockResult = okBoolean();
        when(cardClient.unlockForCharging(anyLong(), anyString())).thenReturn(unlockResult);
        TChargingGun gun = new TChargingGun();
        gun.setId(9);
        gun.setStatus(4);
        R<TChargingGun> gunResult = gunOf(gun);
        when(gunClient.getChargingGunById(any())).thenReturn(gunResult);
        R<String> gunUpdateResult = mock(R.class);
        when(gunUpdateResult.getCode()).thenReturn(R.SUCCESS);
        when(gunClient.updateChargingGunById(any(TChargingGun.class))).thenReturn(gunUpdateResult);
        TransactionRecordMessageVO vo = new TransactionRecordMessageVO();
        vo.setTransaction_serial_number("SD1");
        vo.setSpike_charge1(new BigDecimal("5"));
        R result = service.cardEndChargeBillingCharge(vo);
        assertEquals(R.SUCCESS, result.getCode());
        ArgumentCaptor<Collection<TCardChargingOrderAccountingStrategy>> captor = ArgumentCaptor.forClass(Collection.class);
        verify(strategyService).saveBatch(captor.capture());
        List<TCardChargingOrderAccountingStrategy> saved = new ArrayList<>(captor.getValue());
        assertEquals(1, saved.size());
        assertEquals(Integer.valueOf(1910101), saved.get(0).getAccountingStrategyDetailId());
        assertNotNull(saved.get(0).getCreateTime());
        assertEquals(Integer.valueOf(1), saved.get(0).getType());
        assertEquals("00:00", saved.get(0).getStartTime());
        assertEquals("06:00", saved.get(0).getEndTime());
    }
}