package com.ruoyi.other.service.impl; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.ruoyi.account.api.dto.VinManagementQueryDTO; import com.ruoyi.account.api.feignClient.FleetUnbindClient; import com.ruoyi.account.api.feignClient.VinManagementClient; import com.ruoyi.account.api.model.TVinManagement; import com.ruoyi.common.core.domain.R; import com.ruoyi.common.core.exception.ServiceException; import com.ruoyi.other.api.domain.TChargeAuthorization; import com.ruoyi.other.api.domain.TFleet; import com.ruoyi.other.api.domain.TGroupCompany; import com.ruoyi.other.api.feignClient.FleetOrderAggregateClient; import com.ruoyi.other.api.vo.FleetOrderAggregateVO; import com.ruoyi.other.mapper.TChargeAuthorizationMapper; import com.ruoyi.other.service.TGroupCompanyService; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; import org.mockito.Mockito; import org.springframework.test.util.ReflectionTestUtils; import java.math.BigDecimal; import java.util.Arrays; import java.util.Collections; import java.util.List; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; /** * 车队服务单测(覆盖 VIN 列表分页代理 + 5 列聚合 fillAggregates)。 * 远程客户端和 Mapper 以 Mock 替代,不依赖 Spring 容器与数据库。 */ class TFleetServiceImplTest { @Test void passesFleetIdAndPagingToVinService() { VinManagementClient client = Mockito.mock(VinManagementClient.class); Page expected = new Page<>(1, 10); expected.setTotal(1); expected.setRecords(Collections.singletonList(vehicle())); Mockito.when(client.getVinPageByFleet(Mockito.any())).thenReturn(R.ok(expected)); Page actual = buildService(fleet(4), client, null, null).selectVinPage(4, 1, 10); assertSame(expected, actual); ArgumentCaptor captor = ArgumentCaptor.forClass(VinManagementQueryDTO.class); Mockito.verify(client).getVinPageByFleet(captor.capture()); assertEquals(4, captor.getValue().getFleetId()); assertEquals(1, captor.getValue().getPageCurr()); assertEquals(10, captor.getValue().getPageSize()); } @Test void fallsBackToDefaultPagingWhenParamsInvalid() { VinManagementClient client = Mockito.mock(VinManagementClient.class); Mockito.when(client.getVinPageByFleet(Mockito.any())).thenReturn(R.ok(new Page<>(1, 10))); buildService(fleet(5), client, null, null).selectVinPage(5, null, 0); ArgumentCaptor captor = ArgumentCaptor.forClass(VinManagementQueryDTO.class); Mockito.verify(client).getVinPageByFleet(captor.capture()); assertEquals(1, captor.getValue().getPageCurr()); assertEquals(10, captor.getValue().getPageSize()); } @Test void rejectsMissingFleetId() { VinManagementClient client = Mockito.mock(VinManagementClient.class); TFleetServiceImpl service = buildService(null, client, null, null); ServiceException ex = assertThrows(ServiceException.class, () -> service.selectVinPage(null, 1, 10)); assertTrue(ex.getMessage().contains("车队ID不能为空"), ex.getMessage()); Mockito.verifyNoInteractions(client); } @Test void rejectsUnknownFleet() { VinManagementClient client = Mockito.mock(VinManagementClient.class); TFleetServiceImpl service = buildService(null, client, null, null); ServiceException ex = assertThrows(ServiceException.class, () -> service.selectVinPage(9, 1, 10)); assertTrue(ex.getMessage().contains("车队不存在"), ex.getMessage()); Mockito.verifyNoInteractions(client); } @Test void failsWhenRemoteReturnsError() { VinManagementClient client = Mockito.mock(VinManagementClient.class); Mockito.when(client.getVinPageByFleet(Mockito.any())).thenReturn(R.fail("VIN 管理服务调用失败")); ServiceException ex = assertThrows(ServiceException.class, () -> buildService(fleet(4), client, null, null).selectVinPage(4, 1, 10)); assertTrue(ex.getMessage().contains("VIN码车辆查询失败"), ex.getMessage()); } @Test void failsWhenRemoteReturnsNullData() { VinManagementClient client = Mockito.mock(VinManagementClient.class); Mockito.when(client.getVinPageByFleet(Mockito.any())).thenReturn(null); ServiceException ex = assertThrows(ServiceException.class, () -> buildService(fleet(4), client, null, null).selectVinPage(4, 1, 10)); assertTrue(ex.getMessage().contains("服务无响应"), ex.getMessage()); } @Test void fillsCompanyBalanceForSharedAccountMode() { VinManagementClient client = Mockito.mock(VinManagementClient.class); Page page = new Page<>(1, 10); page.setRecords(Collections.singletonList(vehicle())); Mockito.when(client.getVinPageByFleet(Mockito.any())).thenReturn(R.ok(page)); TGroupCompanyService groupCompanyService = Mockito.mock(TGroupCompanyService.class); TGroupCompany company = new TGroupCompany(); company.setId(7); company.setAccountMode(1); company.setAvailableBalance(new BigDecimal("500.00")); Mockito.when(groupCompanyService.getById(7)).thenReturn(company); TFleet fleet = fleet(4); fleet.setCompanyId(7); buildService(fleet, client, null, null, groupCompanyService).selectVinPage(4, 1, 10); // 共享模式展示公司当前可用余额,而非 VIN 自身余额(vehicle() balance=30.00) assertEquals(0, new BigDecimal("500.00").compareTo(page.getRecords().get(0).getAvailableBalance())); } @Test void fillsVinBalanceForAllocationAccountMode() { VinManagementClient client = Mockito.mock(VinManagementClient.class); Page page = new Page<>(1, 10); page.setRecords(Collections.singletonList(vehicle())); Mockito.when(client.getVinPageByFleet(Mockito.any())).thenReturn(R.ok(page)); TGroupCompanyService groupCompanyService = Mockito.mock(TGroupCompanyService.class); TGroupCompany company = new TGroupCompany(); company.setId(7); company.setAccountMode(2); company.setAvailableBalance(new BigDecimal("500.00")); Mockito.when(groupCompanyService.getById(7)).thenReturn(company); TFleet fleet = fleet(4); fleet.setCompanyId(7); buildService(fleet, client, null, null, groupCompanyService).selectVinPage(4, 1, 10); // 分配模式展示 VIN 自身余额 assertEquals(0, new BigDecimal("30.00").compareTo(page.getRecords().get(0).getAvailableBalance())); } @Test void treatsNullAccountModeAsSharedAndKeepsNullWhenCompanyMissing() { VinManagementClient client = Mockito.mock(VinManagementClient.class); Page page = new Page<>(1, 10); TVinManagement first = vehicle(); page.setRecords(Collections.singletonList(first)); Mockito.when(client.getVinPageByFleet(Mockito.any())).thenReturn(R.ok(page)); TGroupCompanyService groupCompanyService = Mockito.mock(TGroupCompanyService.class); TGroupCompany company = new TGroupCompany(); company.setId(7); company.setAccountMode(null); company.setAvailableBalance(new BigDecimal("88.00")); Mockito.when(groupCompanyService.getById(7)).thenReturn(company); TFleet fleet = fleet(4); fleet.setCompanyId(7); buildService(fleet, client, null, null, groupCompanyService).selectVinPage(4, 1, 10); // 历史数据账户模式为 null 时按共享模式展示公司余额(与系统"仅 ==2 为分配"口径一致) assertEquals(0, new BigDecimal("88.00").compareTo(first.getAvailableBalance())); // 公司不存在(如已被删除)→ 保持 null,前端显示 '-' Mockito.when(groupCompanyService.getById(8)).thenReturn(null); TFleet other = fleet(6); other.setCompanyId(8); Page otherPage = new Page<>(1, 10); TVinManagement third = vehicle(); third.setId(3L); otherPage.setRecords(Collections.singletonList(third)); Mockito.when(client.getVinPageByFleet(Mockito.any())).thenReturn(R.ok(otherPage)); buildService(other, client, null, null, groupCompanyService).selectVinPage(6, 1, 10); assertNull(third.getAvailableBalance()); } @Test void fillAggregatesPopulatesAllFiveFields() { VinManagementClient vinClient = Mockito.mock(VinManagementClient.class); FleetOrderAggregateClient aggClient = Mockito.mock(FleetOrderAggregateClient.class); TChargeAuthorizationMapper authMapper = Mockito.mock(TChargeAuthorizationMapper.class); TFleet fleet = fleet(101); fleet.setCompanyId(7); Mockito.when(vinClient.countByFleet(101)).thenReturn(R.ok(8L)); FleetOrderAggregateVO vo = new FleetOrderAggregateVO(); vo.setCumulativeCharge(new BigDecimal("12.34")); vo.setCumulativeElectrovalence(new BigDecimal("56.78")); vo.setCumulativeServiceCharge(new BigDecimal("9.10")); Mockito.when(aggClient.aggregateByFleet(101)).thenReturn(R.ok(vo)); TChargeAuthorization auth = new TChargeAuthorization(); auth.setCompanyId(7); auth.setSiteIds("1,2,3,2,1"); Mockito.when(authMapper.selectList(Mockito.any())).thenReturn(Collections.singletonList(auth)); TFleetServiceImpl service = buildService(fleet, vinClient, aggClient, authMapper); service.fillAggregates(Arrays.asList(fleet)); assertEquals(8L, fleet.getVinCount()); assertEquals(3L, fleet.getStationCount()); assertNotNull(fleet.getCumulativeCharge()); assertEquals(0, new BigDecimal("12.34").compareTo(fleet.getCumulativeCharge())); assertEquals(0, new BigDecimal("56.78").compareTo(fleet.getCumulativeElectrovalence())); assertEquals(0, new BigDecimal("9.10").compareTo(fleet.getCumulativeServiceCharge())); } @Test void fillAggregatesHandlesNullFleetIdSafely() { VinManagementClient vinClient = Mockito.mock(VinManagementClient.class); FleetOrderAggregateClient aggClient = Mockito.mock(FleetOrderAggregateClient.class); TChargeAuthorizationMapper authMapper = Mockito.mock(TChargeAuthorizationMapper.class); TFleetServiceImpl service = buildService(null, vinClient, aggClient, authMapper); // 传入空集合 / null 不抛异常 service.fillAggregates(null); service.fillAggregates(Collections.emptyList()); Mockito.verifyNoInteractions(vinClient, aggClient, authMapper); } @Test void fillAggregatesFallsBackToZeroOnRemoteFailure() { VinManagementClient vinClient = Mockito.mock(VinManagementClient.class); FleetOrderAggregateClient aggClient = Mockito.mock(FleetOrderAggregateClient.class); TChargeAuthorizationMapper authMapper = Mockito.mock(TChargeAuthorizationMapper.class); TFleet fleet = fleet(202); fleet.setCompanyId(null); Mockito.when(vinClient.countByFleet(202)).thenReturn(R.fail("down")); Mockito.when(aggClient.aggregateByFleet(202)).thenReturn(null); Mockito.when(authMapper.selectList(Mockito.any())).thenReturn(Collections.emptyList()); TFleetServiceImpl service = buildService(fleet, vinClient, aggClient, authMapper); service.fillAggregates(Arrays.asList(fleet)); assertEquals(0L, fleet.getVinCount()); assertEquals(0L, fleet.getStationCount()); assertEquals(BigDecimal.ZERO, fleet.getCumulativeCharge()); assertEquals(BigDecimal.ZERO, fleet.getCumulativeElectrovalence()); assertEquals(BigDecimal.ZERO, fleet.getCumulativeServiceCharge()); } private TFleetServiceImpl buildService(TFleet fleet, VinManagementClient client, FleetOrderAggregateClient aggClient, TChargeAuthorizationMapper authMapper) { return buildService(fleet, client, aggClient, authMapper, Mockito.mock(TGroupCompanyService.class)); } private TFleetServiceImpl buildService(TFleet fleet, VinManagementClient client, FleetOrderAggregateClient aggClient, TChargeAuthorizationMapper authMapper, TGroupCompanyService groupCompanyService) { TFleetServiceImpl service = Mockito.spy(new TFleetServiceImpl()); Mockito.doReturn(fleet).when(service).getById(Mockito.any()); ReflectionTestUtils.setField(service, "vinManagementClient", client); ReflectionTestUtils.setField(service, "fleetOrderAggregateClient", aggClient); ReflectionTestUtils.setField(service, "chargeAuthorizationMapper", authMapper); ReflectionTestUtils.setField(service, "groupCompanyService", groupCompanyService); return service; } @Test void deleteFleetsUnbindsThenRemoves() { FleetUnbindClient unbindClient = Mockito.mock(FleetUnbindClient.class); Mockito.when(unbindClient.unbindByFleetIds(Mockito.anyList())).thenReturn(R.ok(3)); TFleetServiceImpl service = Mockito.spy(new TFleetServiceImpl()); ReflectionTestUtils.setField(service, "fleetUnbindClient", unbindClient); Mockito.doReturn(Arrays.asList(fleet(1), fleet(2))).when(service).listByIds(Mockito.anyList()); Mockito.doReturn(true).when(service).removeByIds(Mockito.anyList()); assertTrue(service.deleteFleets(Arrays.asList(1, 2))); @SuppressWarnings("unchecked") ArgumentCaptor> idsCaptor = ArgumentCaptor.forClass(List.class); Mockito.verify(unbindClient).unbindByFleetIds(idsCaptor.capture()); assertEquals(Arrays.asList(1, 2), idsCaptor.getValue()); Mockito.verify(service).removeByIds(Mockito.anyList()); } @Test void deleteFleetsDeduplicatesAndFiltersNullIds() { FleetUnbindClient unbindClient = Mockito.mock(FleetUnbindClient.class); Mockito.when(unbindClient.unbindByFleetIds(Mockito.anyList())).thenReturn(R.ok(0)); TFleetServiceImpl service = Mockito.spy(new TFleetServiceImpl()); ReflectionTestUtils.setField(service, "fleetUnbindClient", unbindClient); Mockito.doReturn(Collections.singletonList(fleet(1))).when(service).listByIds(Mockito.anyList()); Mockito.doReturn(true).when(service).removeByIds(Mockito.anyList()); assertTrue(service.deleteFleets(Arrays.asList(1, null, 1))); @SuppressWarnings("unchecked") ArgumentCaptor> idsCaptor = ArgumentCaptor.forClass(List.class); Mockito.verify(unbindClient).unbindByFleetIds(idsCaptor.capture()); assertEquals(Collections.singletonList(1), idsCaptor.getValue()); Mockito.verify(service).removeByIds(Collections.singletonList(1)); } @Test void deleteFleetsRejectsNullOrEmptyIds() { TFleetServiceImpl service = Mockito.spy(new TFleetServiceImpl()); ReflectionTestUtils.setField(service, "fleetUnbindClient", Mockito.mock(FleetUnbindClient.class)); ServiceException nullEx = assertThrows(ServiceException.class, () -> service.deleteFleets(null)); assertTrue(nullEx.getMessage().contains("请选择需要删除的车队"), nullEx.getMessage()); ServiceException emptyEx = assertThrows(ServiceException.class, () -> service.deleteFleets(Arrays.asList(null, null))); assertTrue(emptyEx.getMessage().contains("请选择需要删除的车队"), emptyEx.getMessage()); } @Test void deleteFleetsRejectsMissingFleetWithoutUnbinding() { FleetUnbindClient unbindClient = Mockito.mock(FleetUnbindClient.class); TFleetServiceImpl service = Mockito.spy(new TFleetServiceImpl()); ReflectionTestUtils.setField(service, "fleetUnbindClient", unbindClient); Mockito.doReturn(Collections.emptyList()).when(service).listByIds(Mockito.anyList()); ServiceException ex = assertThrows(ServiceException.class, () -> service.deleteFleets(Collections.singletonList(9))); assertTrue(ex.getMessage().contains("部分车队不存在"), ex.getMessage()); Mockito.verifyNoInteractions(unbindClient); Mockito.verify(service, Mockito.never()).removeByIds(Mockito.anyList()); } @Test void deleteFleetsAbortsWhenUnbindFails() { FleetUnbindClient unbindClient = Mockito.mock(FleetUnbindClient.class); Mockito.when(unbindClient.unbindByFleetIds(Mockito.anyList())).thenReturn(R.fail("车队关联数据解绑失败:超时")); TFleetServiceImpl service = Mockito.spy(new TFleetServiceImpl()); ReflectionTestUtils.setField(service, "fleetUnbindClient", unbindClient); Mockito.doReturn(Collections.singletonList(fleet(1))).when(service).listByIds(Mockito.anyList()); ServiceException ex = assertThrows(ServiceException.class, () -> service.deleteFleets(Collections.singletonList(1))); // 解绑失败中止删除:车队与绑定关系均保留,两侧数据保持一致 assertTrue(ex.getMessage().contains("车队关联数据解绑失败"), ex.getMessage()); Mockito.verify(service, Mockito.never()).removeByIds(Mockito.anyList()); } @Test void deleteFleetsAbortsWhenRemoteThrows() { FleetUnbindClient unbindClient = Mockito.mock(FleetUnbindClient.class); Mockito.when(unbindClient.unbindByFleetIds(Mockito.anyList())) .thenThrow(new RuntimeException("connect refused")); TFleetServiceImpl service = Mockito.spy(new TFleetServiceImpl()); ReflectionTestUtils.setField(service, "fleetUnbindClient", unbindClient); Mockito.doReturn(Collections.singletonList(fleet(1))).when(service).listByIds(Mockito.anyList()); ServiceException ex = assertThrows(ServiceException.class, () -> service.deleteFleets(Collections.singletonList(1))); assertTrue(ex.getMessage().contains("车队关联数据解绑失败"), ex.getMessage()); Mockito.verify(service, Mockito.never()).removeByIds(Mockito.anyList()); } private TFleet fleet(Integer id) { TFleet fleet = new TFleet(); fleet.setId(id); return fleet; } private TVinManagement vehicle() { TVinManagement item = new TVinManagement(); item.setId(1L); item.setVin("12345678900000000"); item.setPlateNumber("川A123456"); item.setFleetId(4); item.setBalance(new BigDecimal("30.00")); item.setStatus(1); return item; } }