| | |
| | | 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; |
| | |
| | | */ |
| | | 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; |
| | | } |
| | |
| | | @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; |
| | | } |
| | |
| | | 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(); |
| | |
| | | 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()); |
| | |
| | | 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:结算锁抢占失败快速返回 ===== |
| | |
| | | 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); |
| | |
| | | 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:已结算订单重复账单幂等 ===== |
| | |
| | | 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()); |
| | | } |
| | | } |