puzhibing
10 小时以前 88580dae55106dcf4a8f80693b90638e07af02fa
ruoyi-service/ruoyi-account/src/test/java/com/ruoyi/account/service/impl/TAppUserWhitelistServiceImplTest.java
@@ -1,22 +1,38 @@
package com.ruoyi.account.service.impl;
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.ruoyi.account.api.dto.AppUserWhitelistAllocateDTO;
import com.ruoyi.account.api.dto.AppUserWhitelistAllocateItemDTO;
import com.ruoyi.account.api.dto.AppUserWhitelistStatusDTO;
import com.ruoyi.account.api.model.TAppUser;
import com.ruoyi.account.api.model.TAppUserWhitelist;
import com.ruoyi.account.api.model.TCompanyAllocateRecord;
import com.ruoyi.account.api.vo.AppUserWhitelistAllocateVO;
import com.ruoyi.account.api.vo.AppUserWhitelistImportResultVO;
import com.ruoyi.account.api.vo.AppUserWhitelistMatchVO;
import com.ruoyi.account.mapper.TAppUserWhitelistMapper;
import com.ruoyi.account.service.PartnerAppUserAccessService;
import com.ruoyi.account.service.TAppUserService;
import com.ruoyi.account.service.TAppUserWhitelistSiteService;
import com.ruoyi.account.service.TCompanyAllocateRecordService;
import com.ruoyi.chargingPile.api.feignClient.PartnerClient;
import com.ruoyi.chargingPile.api.feignClient.SiteClient;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.core.exception.ServiceException;
import com.ruoyi.common.security.service.TokenService;
import com.ruoyi.other.api.domain.TGroupCompany;
import com.ruoyi.other.api.feignClient.GroupCompanyClient;
import com.ruoyi.system.api.feignClient.SysUserClient;
import org.apache.ibatis.builder.MapperBuilderAssistant;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.redisson.api.RLock;
@@ -27,15 +43,26 @@
import org.springframework.transaction.TransactionStatus;
import java.io.ByteArrayOutputStream;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.Collections;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyCollection;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -51,8 +78,17 @@
    @Mock private PlatformTransactionManager transactionManager;
    @Mock private GroupCompanyClient groupCompanyClient;
    @Mock private RedissonClient redissonClient;
    @Mock private TCompanyAllocateRecordService companyAllocateRecordService;
    @Mock private PartnerAppUserAccessService partnerAppUserAccessService;
    private TAppUserWhitelistServiceImpl service;
    @BeforeAll
    static void initTableInfo() {
        // MP 的 lambdaUpdate 会通过 TableInfoHelper.getTableInfo 取表元数据,
        // 单元测试不启动 Spring,需要手动初始化 TableInfo,否则抛 MybatisPlusException
        TableInfoHelper.initTableInfo(new MapperBuilderAssistant(new MybatisConfiguration(), ""), TAppUserWhitelist.class);
    }
    @BeforeEach
    void setUp() {
@@ -67,10 +103,16 @@
        ReflectionTestUtils.setField(service, "transactionManager", transactionManager);
        ReflectionTestUtils.setField(service, "groupCompanyClient", groupCompanyClient);
        ReflectionTestUtils.setField(service, "redissonClient", redissonClient);
        ReflectionTestUtils.setField(service, "companyAllocateRecordService", companyAllocateRecordService);
        ReflectionTestUtils.setField(service, "partnerAppUserAccessService", partnerAppUserAccessService);
        // 默认透传(模拟非公司账号),公司账号隔离场景由具体用例覆盖打桩
        lenient().when(partnerAppUserAccessService.resolveCompanyFilter(any())).thenAnswer(invocation -> invocation.getArgument(0));
        lenient().when(partnerAppUserAccessService.getCurrentCompanyId()).thenReturn(null);
        RLock lock = mock(RLock.class);
        when(redissonClient.getLock(any())).thenReturn(lock);
        // 并发锁仅在额度相关场景使用,宽松桩避免严格模式下未使用即报错
        lenient().when(redissonClient.getLock(any())).thenReturn(lock);
        try {
            when(lock.tryLock(anyLong(), anyLong(), any(TimeUnit.class))).thenReturn(true);
            lenient().when(lock.tryLock(anyLong(), anyLong(), any(TimeUnit.class))).thenReturn(true);
        } catch (InterruptedException exception) {
            throw new IllegalStateException(exception);
        }
@@ -78,7 +120,6 @@
    @Test
    void importsValidRowsAndReturnsOnlyInvalidRows() throws Exception {
        when(siteClient.getSiteAll()).thenReturn(R.ok(Collections.emptyList()));
        when(mapper.selectValidByPhone(any())).thenReturn(null);
        TransactionStatus transactionStatus = mock(TransactionStatus.class);
        when(transactionManager.getTransaction(any())).thenReturn(transactionStatus);
@@ -96,11 +137,440 @@
        AppUserWhitelistImportResultVO result = service.importWhitelist(importFile());
        assertEquals(3, result.getTotalCount());
        assertEquals(2, result.getSuccessCount());
        assertEquals(4, result.getTotalCount());
        assertEquals(3, result.getSuccessCount());
        assertEquals(1, result.getErrorCount());
        assertEquals("13540211237", result.getErrorMessage().get(0).getPhone());
        verify(mapper, org.mockito.Mockito.times(2)).insert(any(TAppUserWhitelist.class));
        assertEquals("1234", result.getErrorMessage().get(0).getPhone());
        org.mockito.ArgumentCaptor<TAppUserWhitelist> captor = org.mockito.ArgumentCaptor.forClass(TAppUserWhitelist.class);
        verify(mapper, org.mockito.Mockito.times(3)).insert(captor.capture());
        for (TAppUserWhitelist saved : captor.getAllValues()) {
            // 导入统一按默认值写入:全部电站 + 启用;白名单权益已下线,不再写入享受权益,忽略模板中的享受权益/白名单电站列
            assertEquals(null, saved.getBenefitType());
            assertEquals(1, saved.getScopeType().intValue());
            assertEquals(1, saved.getStatus().intValue());
        }
    }
    // ---------------- unbindFleet ----------------
    @Test
    void unbindFleetRejectsNullOrEmptyIds() {
        ServiceException nullEx = assertThrows(ServiceException.class, () -> service.unbindFleet(null));
        assertEquals("请选择需要解绑车队的白名单用户", nullEx.getMessage());
        ServiceException emptyEx = assertThrows(ServiceException.class, () -> service.unbindFleet(Collections.emptyList()));
        assertEquals("请选择需要解绑车队的白名单用户", emptyEx.getMessage());
    }
    @Test
    void unbindFleetRejectsMissingRecords() {
        TAppUserWhitelist existing = new TAppUserWhitelist();
        existing.setId(1L);
        when(mapper.selectBatchIds(anyCollection())).thenReturn(Collections.singletonList(existing));
        ServiceException exception = assertThrows(ServiceException.class,
                () -> service.unbindFleet(Arrays.asList(1L, 2L)));
        assertEquals("部分白名单用户不存在或已删除", exception.getMessage());
        verify(mapper, never()).update(isNull(), any(Wrapper.class));
    }
    @Test
    void unbindFleetClearsFleetIdAndIgnoresUnboundRecords() {
        TAppUserWhitelist bound = new TAppUserWhitelist();
        bound.setId(1L);
        bound.setFleetId(5);
        TAppUserWhitelist unbound = new TAppUserWhitelist();
        unbound.setId(2L);
        when(mapper.selectBatchIds(anyCollection())).thenReturn(Arrays.asList(bound, unbound));
        when(mapper.update(isNull(), any(Wrapper.class))).thenReturn(1);
        int result = service.unbindFleet(Arrays.asList(1L, 2L, 2L, null));
        assertEquals(1, result);
        ArgumentCaptor<Wrapper> wrapperCaptor = ArgumentCaptor.forClass(Wrapper.class);
        verify(mapper).update(isNull(), wrapperCaptor.capture());
        String sqlSet = wrapperCaptor.getValue().getSqlSet();
        assertTrue(sqlSet.contains("fleet_id="));
        assertTrue(sqlSet.contains("update_by="));
        // 实现使用 setSql("update_time = now()"),SQL 片段不带列名与等号紧邻的写法
        assertTrue(sqlSet.contains("update_time = now()"));
    }
    @Test
    void unbindFleetReturnsZeroWhenAllUnbound() {
        TAppUserWhitelist unbound = new TAppUserWhitelist();
        unbound.setId(1L);
        when(mapper.selectBatchIds(anyCollection())).thenReturn(Collections.singletonList(unbound));
        int result = service.unbindFleet(Collections.singletonList(1L));
        assertEquals(0, result);
        verify(mapper, never()).update(isNull(), any(Wrapper.class));
    }
    // ---------------- 白名单匹配(含公司/车队归属) ----------------
    @Test
    void matchWhitelistReturnsCompanyAndFleetOwnership() {
        TAppUser appUser = new TAppUser();
        appUser.setId(100L);
        appUser.setPhone("13540211236");
        when(appUserService.getById(100L)).thenReturn(appUser);
        TAppUserWhitelist whitelist = new TAppUserWhitelist();
        whitelist.setId(88L);
        whitelist.setPhone("13540211236");
        whitelist.setStatus(1);
        whitelist.setBenefitType(1);
        whitelist.setScopeType(1);
        whitelist.setCompanyId(12);
        whitelist.setFleetId(3);
        when(mapper.selectOne(any(Wrapper.class))).thenReturn(whitelist);
        AppUserWhitelistMatchVO result = service.matchWhitelist(100L, 2001);
        assertTrue(result.getMatched());
        assertEquals(12, result.getCompanyId());
        assertEquals(3, result.getFleetId());
        // 白名单权益已下线:历史数据的享受权益不再回传给调用方,充电完全按企业优惠结算
        assertEquals(null, result.getBenefitType());
        assertEquals(null, result.getBenefitName());
    }
    @Test
    void matchWhitelistReturnsNullOwnershipWhenUnassigned() {
        TAppUser appUser = new TAppUser();
        appUser.setId(100L);
        appUser.setPhone("13540211236");
        when(appUserService.getById(100L)).thenReturn(appUser);
        TAppUserWhitelist whitelist = new TAppUserWhitelist();
        whitelist.setId(88L);
        whitelist.setPhone("13540211236");
        whitelist.setStatus(1);
        whitelist.setBenefitType(2);
        whitelist.setScopeType(1);
        when(mapper.selectOne(any(Wrapper.class))).thenReturn(whitelist);
        AppUserWhitelistMatchVO result = service.matchWhitelist(100L, 2001);
        assertTrue(result.getMatched());
        assertEquals(null, result.getCompanyId());
        assertEquals(null, result.getFleetId());
    }
    // ---------------- 白名单账户可用余额解析(充电页余额显示) ----------------
    @Test
    void resolveWhitelistAccountBalanceReturnsCompanyBalanceForSharedMode() {
        TGroupCompany company = new TGroupCompany();
        company.setAccountMode(1);
        company.setAvailableBalance(new BigDecimal("500.50"));
        when(groupCompanyClient.getCompanyById(12)).thenReturn(R.ok(company));
        AppUserWhitelistMatchVO match = new AppUserWhitelistMatchVO();
        match.setMatched(true);
        match.setCompanyId(12);
        match.setBalance(new BigDecimal("100.00"));
        BigDecimal result = service.resolveWhitelistAccountBalance(match);
        assertEquals(new BigDecimal("500.50"), result);
    }
    @Test
    void resolveWhitelistAccountBalanceReturnsWhitelistBalanceForAllocationMode() {
        TGroupCompany company = new TGroupCompany();
        company.setAccountMode(2);
        company.setAvailableBalance(new BigDecimal("500.50"));
        when(groupCompanyClient.getCompanyById(12)).thenReturn(R.ok(company));
        AppUserWhitelistMatchVO match = new AppUserWhitelistMatchVO();
        match.setMatched(true);
        match.setCompanyId(12);
        match.setBalance(new BigDecimal("100.00"));
        BigDecimal result = service.resolveWhitelistAccountBalance(match);
        assertEquals(new BigDecimal("100.00"), result);
    }
    @Test
    void resolveWhitelistAccountBalanceTreatsNullAccountModeAsShared() {
        TGroupCompany company = new TGroupCompany();
        company.setAccountMode(null);
        company.setAvailableBalance(new BigDecimal("500.50"));
        when(groupCompanyClient.getCompanyById(12)).thenReturn(R.ok(company));
        AppUserWhitelistMatchVO match = new AppUserWhitelistMatchVO();
        match.setMatched(true);
        match.setCompanyId(12);
        match.setBalance(new BigDecimal("100.00"));
        BigDecimal result = service.resolveWhitelistAccountBalance(match);
        assertEquals(new BigDecimal("500.50"), result);
    }
    @Test
    void resolveWhitelistAccountBalanceReturnsNullWhenCompanyQueryFails() {
        when(groupCompanyClient.getCompanyById(12)).thenReturn(R.fail("公司信息查询失败"));
        AppUserWhitelistMatchVO match = new AppUserWhitelistMatchVO();
        match.setMatched(true);
        match.setCompanyId(12);
        match.setBalance(new BigDecimal("100.00"));
        BigDecimal result = service.resolveWhitelistAccountBalance(match);
        assertNull(result);
    }
    @Test
    void resolveWhitelistAccountBalanceReturnsNullWhenNotMatched() {
        AppUserWhitelistMatchVO match = new AppUserWhitelistMatchVO();
        match.setMatched(false);
        BigDecimal result = service.resolveWhitelistAccountBalance(match);
        assertNull(result);
    }
    // ---------------- 额度分配 ----------------
    @Test
    void getAllocateOverviewNormalizesAmounts() {
        TGroupCompany company = allocationCompany(9, "测试公司");
        company.setAvailableBalance(new BigDecimal("8420.5"));
        company.setTotalRechargeAmount(null);
        when(groupCompanyClient.getCompanyById(9)).thenReturn(R.ok(company));
        AppUserWhitelistAllocateVO overview = service.getAllocateOverview(9);
        assertEquals(Integer.valueOf(9), overview.getCompanyId());
        assertEquals("测试公司", overview.getCompanyName());
        assertEquals(Integer.valueOf(2), overview.getAccountMode());
        assertEquals(new BigDecimal("8420.50"), overview.getAvailableBalance());
        assertEquals(new BigDecimal("0.00"), overview.getTotalRechargeAmount());
    }
    @Test
    void getAllocateOverviewRejectsMissingCompanyId() {
        ServiceException exception = assertThrows(ServiceException.class, () -> service.getAllocateOverview(null));
        assertEquals("请选择公司", exception.getMessage());
    }
    @Test
    void allocateQuotaAddsWhitelistBalanceAndDeductsCompanyBalance() {
        when(groupCompanyClient.getCompanyById(9)).thenReturn(R.ok(allocationCompany(9, "测试公司")));
        when(mapper.selectBatchIds(anyCollection())).thenReturn(Arrays.asList(
                allocateWhitelist(1L, 9, "13540211236", "10.00"),
                allocateWhitelist(2L, 9, "13540211237", "20.00")));
        when(mapper.addBalance(anyLong(), any(BigDecimal.class), any())).thenReturn(1);
        when(groupCompanyClient.deductBalance(9, new BigDecimal("30.00"))).thenReturn(R.ok(new BigDecimal("970.00")));
        AppUserWhitelistAllocateVO overview = service.allocateQuota(allocateDto(9,
                allocateItem(1L, "10.00"), allocateItem(2L, "20.00")));
        assertEquals(new BigDecimal("970.00"), overview.getAvailableBalance());
        assertEquals("测试公司", overview.getCompanyName());
        verify(redissonClient).getLock("APP_USER_WHITELIST_QUOTA:COMPANY:9");
        verify(groupCompanyClient).deductBalance(9, new BigDecimal("30.00"));
        verify(mapper).addBalance(eq(1L), eq(new BigDecimal("10.00")), any());
        verify(mapper).addBalance(eq(2L), eq(new BigDecimal("20.00")), any());
        verify(companyAllocateRecordService).recordAllocate(TCompanyAllocateRecord.USER_TYPE_WHITELIST,
                "13540211236", null, 9, TCompanyAllocateRecord.SOURCE_ALLOCATE,
                new BigDecimal("10.00"), new BigDecimal("10.00"), "额度分配(公司:测试公司)");
    }
    @Test
    void allocateQuotaRejectsSharedModeCompany() {
        TGroupCompany shared = allocationCompany(9, "共享公司");
        shared.setAccountMode(1);
        when(groupCompanyClient.getCompanyById(9)).thenReturn(R.ok(shared));
        ServiceException exception = assertThrows(ServiceException.class,
                () -> service.allocateQuota(allocateDto(9, allocateItem(1L, "10.00"))));
        assertEquals("该公司为共享模式,不支持额度分配", exception.getMessage());
        verify(groupCompanyClient, never()).deductBalance(any(Integer.class), any(BigDecimal.class));
    }
    @Test
    void allocateQuotaRejectsWhitelistOfAnotherCompany() {
        when(groupCompanyClient.getCompanyById(9)).thenReturn(R.ok(allocationCompany(9, "测试公司")));
        when(mapper.selectBatchIds(anyCollection()))
                .thenReturn(Collections.singletonList(allocateWhitelist(1L, 8, "13540211236", "10.00")));
        ServiceException exception = assertThrows(ServiceException.class,
                () -> service.allocateQuota(allocateDto(9, allocateItem(1L, "10.00"))));
        assertTrue(exception.getMessage().contains("不属于所选公司"));
        verify(groupCompanyClient, never()).deductBalance(any(Integer.class), any(BigDecimal.class));
    }
    @Test
    void allocateQuotaRejectsDisabledWhitelist() {
        when(groupCompanyClient.getCompanyById(9)).thenReturn(R.ok(allocationCompany(9, "测试公司")));
        TAppUserWhitelist disabled = allocateWhitelist(1L, 9, "13540211236", "10.00");
        disabled.setStatus(2);
        when(mapper.selectBatchIds(anyCollection())).thenReturn(Collections.singletonList(disabled));
        ServiceException exception = assertThrows(ServiceException.class,
                () -> service.allocateQuota(allocateDto(9, allocateItem(1L, "10.00"))));
        assertTrue(exception.getMessage().contains("已停用"));
        verify(groupCompanyClient, never()).deductBalance(any(Integer.class), any(BigDecimal.class));
    }
    @Test
    void allocateQuotaRefundsCompanyBalanceWhenWhitelistUpdateFails() {
        when(groupCompanyClient.getCompanyById(9)).thenReturn(R.ok(allocationCompany(9, "测试公司")));
        when(mapper.selectBatchIds(anyCollection()))
                .thenReturn(Collections.singletonList(allocateWhitelist(1L, 9, "13540211236", "0")));
        when(mapper.addBalance(anyLong(), any(BigDecimal.class), any())).thenReturn(0);
        when(groupCompanyClient.deductBalance(9, new BigDecimal("10.00"))).thenReturn(R.ok(new BigDecimal("990.00")));
        when(groupCompanyClient.refundBalance(9, new BigDecimal("10.00"))).thenReturn(R.ok(new BigDecimal("1000.00")));
        ServiceException exception = assertThrows(ServiceException.class,
                () -> service.allocateQuota(allocateDto(9, allocateItem(1L, "10.00"))));
        assertTrue(exception.getMessage().contains("额度分配失败"));
        verify(groupCompanyClient).refundBalance(9, new BigDecimal("10.00"));
    }
    @Test
    void allocateQuotaRejectsAmountExceedingLimit() {
        ServiceException exception = assertThrows(ServiceException.class,
                () -> service.allocateQuota(allocateDto(9, allocateItem(1L, "999999999.00"))));
        assertTrue(exception.getMessage().contains("不能超过"));
        verify(groupCompanyClient, never()).getCompanyById(any(Integer.class));
    }
    @Test
    void allocateQuotaRejectsDuplicateWhitelistItems() {
        ServiceException exception = assertThrows(ServiceException.class,
                () -> service.allocateQuota(allocateDto(9, allocateItem(1L, "10.00"), allocateItem(1L, "5.00"))));
        assertEquals("分配明细中存在重复白名单用户", exception.getMessage());
    }
    // ---------------- updateStatus ----------------
    @Test
    void updateStatusRejectsInvalidParams() {
        assertThrows(ServiceException.class, () -> service.updateStatus(null));
        AppUserWhitelistStatusDTO noId = new AppUserWhitelistStatusDTO();
        noId.setStatus(1);
        assertThrows(ServiceException.class, () -> service.updateStatus(noId));
        AppUserWhitelistStatusDTO badStatus = new AppUserWhitelistStatusDTO();
        badStatus.setId(1L);
        badStatus.setStatus(3);
        ServiceException exception = assertThrows(ServiceException.class, () -> service.updateStatus(badStatus));
        assertEquals("白名单用户状态参数错误", exception.getMessage());
        verify(mapper, never()).update(isNull(), any(Wrapper.class));
    }
    @Test
    void updateStatusRejectsMissingRecord() {
        when(mapper.selectById(1L)).thenReturn(null);
        AppUserWhitelistStatusDTO dto = new AppUserWhitelistStatusDTO();
        dto.setId(1L);
        dto.setStatus(2);
        ServiceException exception = assertThrows(ServiceException.class, () -> service.updateStatus(dto));
        assertEquals("白名单用户不存在或已删除", exception.getMessage());
        verify(mapper, never()).update(isNull(), any(Wrapper.class));
    }
    @Test
    void updateStatusKeepsUnchangedStatusWithoutUpdate() {
        TAppUserWhitelist existing = new TAppUserWhitelist();
        existing.setId(1L);
        existing.setStatus(1);
        when(mapper.selectById(1L)).thenReturn(existing);
        AppUserWhitelistStatusDTO dto = new AppUserWhitelistStatusDTO();
        dto.setId(1L);
        dto.setStatus(1);
        assertTrue(service.updateStatus(dto));
        // 状态未变化时视为幂等成功,不产生更新语句,避免污染 update_time / update_by
        verify(mapper, never()).update(isNull(), any(Wrapper.class));
    }
    @Test
    void updateStatusDisablesWhitelist() {
        TAppUserWhitelist existing = new TAppUserWhitelist();
        existing.setId(1L);
        existing.setStatus(1);
        when(mapper.selectById(1L)).thenReturn(existing);
        when(mapper.update(isNull(), any(Wrapper.class))).thenReturn(1);
        AppUserWhitelistStatusDTO dto = new AppUserWhitelistStatusDTO();
        dto.setId(1L);
        dto.setStatus(2);
        assertTrue(service.updateStatus(dto));
        ArgumentCaptor<Wrapper> wrapperCaptor = ArgumentCaptor.forClass(Wrapper.class);
        verify(mapper).update(isNull(), wrapperCaptor.capture());
        String sqlSet = wrapperCaptor.getValue().getSqlSet();
        assertTrue(sqlSet.contains("status"));
        assertTrue(sqlSet.contains("update_time"));
    }
    @Test
    void updateStatusThrowsWhenUpdateAffectsNoRow() {
        TAppUserWhitelist existing = new TAppUserWhitelist();
        existing.setId(1L);
        existing.setStatus(1);
        when(mapper.selectById(1L)).thenReturn(existing);
        when(mapper.update(isNull(), any(Wrapper.class))).thenReturn(0);
        AppUserWhitelistStatusDTO dto = new AppUserWhitelistStatusDTO();
        dto.setId(1L);
        dto.setStatus(2);
        ServiceException exception = assertThrows(ServiceException.class, () -> service.updateStatus(dto));
        assertEquals("白名单用户状态更新失败,请重试", exception.getMessage());
    }
    private TGroupCompany allocationCompany(Integer id, String name) {
        TGroupCompany company = new TGroupCompany();
        company.setId(id);
        company.setName(name);
        company.setAccountMode(2);
        company.setStatus(1);
        return company;
    }
    private TAppUserWhitelist allocateWhitelist(Long id, Integer companyId, String phone, String balance) {
        TAppUserWhitelist whitelist = new TAppUserWhitelist();
        whitelist.setId(id);
        whitelist.setCompanyId(companyId);
        whitelist.setPhone(phone);
        whitelist.setStatus(1);
        whitelist.setBalance(new BigDecimal(balance));
        return whitelist;
    }
    private AppUserWhitelistAllocateDTO allocateDto(Integer companyId, AppUserWhitelistAllocateItemDTO... items) {
        AppUserWhitelistAllocateDTO dto = new AppUserWhitelistAllocateDTO();
        dto.setCompanyId(companyId);
        dto.setItems(Arrays.asList(items));
        return dto;
    }
    private AppUserWhitelistAllocateItemDTO allocateItem(Long whitelistId, String amount) {
        AppUserWhitelistAllocateItemDTO item = new AppUserWhitelistAllocateItemDTO();
        item.setWhitelistId(whitelistId);
        item.setAmount(new BigDecimal(amount));
        return item;
    }
    private MockMultipartFile importFile() throws Exception {
@@ -113,6 +583,7 @@
            addRow(sheet, 1, "13540211236", "1", "all");
            addRow(sheet, 2, "13540211238", "2", "all");
            addRow(sheet, 3, "13540211237", "", "all");
            addRow(sheet, 4, "1234", "", "all");
            workbook.write(output);
            return new MockMultipartFile("file", "whitelist.xlsx",
                    "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", output.toByteArray());
@@ -126,4 +597,42 @@
        row.createCell(1).setCellValue(benefitType);
        row.createCell(2).setCellValue(whitelistSite);
    }
    // ==================== 公司账号数据隔离 ====================
    @Test
    void getAllocateOverviewForcesCompanyFilterForCompanyAccount() {
        // 公司账号:额度概览的公司参数被强制覆盖为本公司
        when(partnerAppUserAccessService.resolveCompanyFilter(9)).thenReturn(2);
        TGroupCompany company = new TGroupCompany();
        company.setId(2);
        company.setName("本公司");
        company.setAccountMode(2);
        company.setAvailableBalance(new BigDecimal("100.00"));
        when(groupCompanyClient.getCompanyById(2)).thenReturn(R.ok(company));
        AppUserWhitelistAllocateVO vo = service.getAllocateOverview(9);
        assertEquals(Integer.valueOf(2), vo.getCompanyId());
        verify(groupCompanyClient).getCompanyById(2);
    }
    @Test
    void updateStatusRejectsCrossCompanyRecord() {
        // 公司账号:启停其他公司的白名单被越权校验拦截
        TAppUserWhitelist existing = new TAppUserWhitelist();
        existing.setId(1L);
        existing.setStatus(1);
        existing.setCompanyId(9);
        when(mapper.selectById(1L)).thenReturn(existing);
        doThrow(new ServiceException("无权操作其他公司的数据")).when(partnerAppUserAccessService).assertCompanyVisible(9);
        AppUserWhitelistStatusDTO dto = new AppUserWhitelistStatusDTO();
        dto.setId(1L);
        dto.setStatus(2);
        ServiceException exception = assertThrows(ServiceException.class, () -> service.updateStatus(dto));
        assertEquals("无权操作其他公司的数据", exception.getMessage());
        verify(mapper, never()).update(isNull(), any(Wrapper.class));
    }
}