package com.ruoyi.other.service.impl; import com.baomidou.mybatisplus.annotation.FieldStrategy; import com.baomidou.mybatisplus.core.MybatisConfiguration; import com.baomidou.mybatisplus.core.metadata.TableInfo; import com.baomidou.mybatisplus.core.metadata.TableInfoHelper; import com.ruoyi.other.api.domain.TCompanyBalanceWarning; import com.ruoyi.other.api.domain.TGroupCompany; import com.ruoyi.other.api.dto.BalanceWarningDTO; import com.ruoyi.other.mapper.TCompanyBalanceWarningMapper; import com.ruoyi.other.mapper.TGroupCompanyMapper; import org.apache.ibatis.builder.MapperBuilderAssistant; import org.junit.jupiter.api.BeforeAll; 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.Collections; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; /** * 公司余额预警配置保存/详情逻辑单测,不依赖 Spring 容器与数据库。 */ class TCompanyBalanceWarningServiceImplTest { @BeforeAll static void initTableInfo() { // 初始化 MyBatis-Plus 表元数据,使 LambdaQueryWrapper 与 saveOrUpdate 可在无 Spring/DB 环境下解析字段 TableInfoHelper.initTableInfo(new MapperBuilderAssistant(new MybatisConfiguration(), ""), TCompanyBalanceWarning.class); } @Test void subWarnAmountAlwaysWrittenOnUpdate() { // 共享模式保存需把子账户预警线清空为 NULL,字段更新策略必须为 IGNORED // (MyBatis-Plus 默认 NOT_NULL 会在 updateById 时忽略 null 字段,导致旧值残留) TableInfo tableInfo = TableInfoHelper.getTableInfo(TCompanyBalanceWarning.class); FieldStrategy strategy = tableInfo.getFieldList().stream() .filter(field -> "sub_warn_amount".equals(field.getColumn())) .findFirst() .orElseThrow(() -> new AssertionError("未找到 sub_warn_amount 字段元数据")) .getUpdateStrategy(); assertEquals(FieldStrategy.IGNORED, strategy); } @Test void insertsNewWarningWithNormalizedFields() { TCompanyBalanceWarningMapper mapper = Mockito.mock(TCompanyBalanceWarningMapper.class); Mockito.when(mapper.selectList(Mockito.any())).thenReturn(Collections.emptyList()); Mockito.when(mapper.insert(Mockito.any(TCompanyBalanceWarning.class))).thenAnswer(invocation -> { TCompanyBalanceWarning entity = invocation.getArgument(0); entity.setId(66); return 1; }); TGroupCompanyMapper companyMapper = Mockito.mock(TGroupCompanyMapper.class); // 分配模式:子账户预警线参与保存 Mockito.when(companyMapper.selectById(11)).thenReturn(company(11, 2)); Integer id = buildService(mapper, companyMapper).saveBalanceWarning(dto()); assertEquals(66, id); ArgumentCaptor captor = ArgumentCaptor.forClass(TCompanyBalanceWarning.class); Mockito.verify(mapper).insert(captor.capture()); TCompanyBalanceWarning saved = captor.getValue(); assertEquals(11, saved.getCompanyId()); assertEquals(new BigDecimal("500.00"), saved.getCompanyWarnAmount()); assertEquals(new BigDecimal("200.50"), saved.getSubWarnAmount()); assertEquals(2, saved.getNotifyObject()); assertEquals(1, saved.getNotifyType()); assertEquals(Boolean.FALSE, saved.getDelFlag()); } @Test void updatesExistingWarningKeepingId() { TCompanyBalanceWarning existing = new TCompanyBalanceWarning(); existing.setId(8); existing.setCompanyId(11); TCompanyBalanceWarningMapper mapper = Mockito.mock(TCompanyBalanceWarningMapper.class); Mockito.when(mapper.selectList(Mockito.any())).thenReturn(Collections.singletonList(existing)); Mockito.when(mapper.selectById(8)).thenReturn(existing); Mockito.when(mapper.updateById(Mockito.any(TCompanyBalanceWarning.class))).thenReturn(1); TGroupCompanyMapper companyMapper = Mockito.mock(TGroupCompanyMapper.class); Mockito.when(companyMapper.selectById(11)).thenReturn(company(11, 2)); Integer id = buildService(mapper, companyMapper).saveBalanceWarning(dto()); assertEquals(8, id); ArgumentCaptor captor = ArgumentCaptor.forClass(TCompanyBalanceWarning.class); Mockito.verify(mapper).updateById(captor.capture()); Mockito.verify(mapper, Mockito.never()).insert(Mockito.any(TCompanyBalanceWarning.class)); assertEquals(new BigDecimal("500.00"), captor.getValue().getCompanyWarnAmount()); assertEquals(new BigDecimal("200.50"), captor.getValue().getSubWarnAmount()); assertEquals(2, captor.getValue().getNotifyObject()); assertEquals(1, captor.getValue().getNotifyType()); } @Test void defaultsNotifyTypeToSmsWhenBlank() { TCompanyBalanceWarningMapper mapper = Mockito.mock(TCompanyBalanceWarningMapper.class); Mockito.when(mapper.selectList(Mockito.any())).thenReturn(Collections.emptyList()); Mockito.when(mapper.insert(Mockito.any(TCompanyBalanceWarning.class))).thenReturn(1); TGroupCompanyMapper companyMapper = Mockito.mock(TGroupCompanyMapper.class); Mockito.when(companyMapper.selectById(11)).thenReturn(company(11)); BalanceWarningDTO dto = dto(); dto.setNotifyType(null); buildService(mapper, companyMapper).saveBalanceWarning(dto); ArgumentCaptor captor = ArgumentCaptor.forClass(TCompanyBalanceWarning.class); Mockito.verify(mapper).insert(captor.capture()); assertEquals(1, captor.getValue().getNotifyType()); } @Test void rejectsBlankCompanyId() { TCompanyBalanceWarningMapper mapper = Mockito.mock(TCompanyBalanceWarningMapper.class); TGroupCompanyMapper companyMapper = Mockito.mock(TGroupCompanyMapper.class); BalanceWarningDTO dto = dto(); dto.setCompanyId(null); IllegalArgumentException error = assertThrows(IllegalArgumentException.class, () -> buildService(mapper, companyMapper).saveBalanceWarning(dto)); assertEquals("配置所属公司不能为空", error.getMessage()); Mockito.verifyNoInteractions(mapper, companyMapper); } @Test void rejectsWhenCompanyNotExists() { TCompanyBalanceWarningMapper mapper = Mockito.mock(TCompanyBalanceWarningMapper.class); TGroupCompanyMapper companyMapper = Mockito.mock(TGroupCompanyMapper.class); Mockito.when(companyMapper.selectById(11)).thenReturn(null); IllegalArgumentException error = assertThrows(IllegalArgumentException.class, () -> buildService(mapper, companyMapper).saveBalanceWarning(dto())); assertEquals("公司不存在", error.getMessage()); Mockito.verifyNoInteractions(mapper); } @Test void rejectsBlankCompanyWarnAmountWithoutPersist() { TCompanyBalanceWarningMapper mapper = Mockito.mock(TCompanyBalanceWarningMapper.class); TGroupCompanyMapper companyMapper = Mockito.mock(TGroupCompanyMapper.class); Mockito.when(companyMapper.selectById(11)).thenReturn(company(11)); BalanceWarningDTO dto = dto(); dto.setCompanyWarnAmount(null); IllegalArgumentException error = assertThrows(IllegalArgumentException.class, () -> buildService(mapper, companyMapper).saveBalanceWarning(dto)); assertEquals("请输入公司预警线", error.getMessage()); Mockito.verifyNoInteractions(mapper); } @Test void rejectsBlankSubWarnAmountForAllocationMode() { // 分配模式(accountMode=2)子账户预警线仍必填 TCompanyBalanceWarningMapper mapper = Mockito.mock(TCompanyBalanceWarningMapper.class); TGroupCompanyMapper companyMapper = Mockito.mock(TGroupCompanyMapper.class); Mockito.when(companyMapper.selectById(11)).thenReturn(company(11, 2)); BalanceWarningDTO dto = dto(); dto.setSubWarnAmount(null); IllegalArgumentException error = assertThrows(IllegalArgumentException.class, () -> buildService(mapper, companyMapper).saveBalanceWarning(dto)); assertEquals("请输入子账户预警线", error.getMessage()); Mockito.verifyNoInteractions(mapper); } @Test void savesSharedModeWarningWithoutSubWarnAmount() { // 共享模式(accountMode=1)无子账户:子账户预警线为空也可保存,落库为 NULL TCompanyBalanceWarningMapper mapper = Mockito.mock(TCompanyBalanceWarningMapper.class); Mockito.when(mapper.selectList(Mockito.any())).thenReturn(Collections.emptyList()); Mockito.when(mapper.insert(Mockito.any(TCompanyBalanceWarning.class))).thenAnswer(invocation -> { TCompanyBalanceWarning entity = invocation.getArgument(0); entity.setId(77); return 1; }); TGroupCompanyMapper companyMapper = Mockito.mock(TGroupCompanyMapper.class); Mockito.when(companyMapper.selectById(11)).thenReturn(company(11, 1)); BalanceWarningDTO dto = dto(); dto.setSubWarnAmount(null); Integer id = buildService(mapper, companyMapper).saveBalanceWarning(dto); assertEquals(77, id); ArgumentCaptor captor = ArgumentCaptor.forClass(TCompanyBalanceWarning.class); Mockito.verify(mapper).insert(captor.capture()); assertEquals(new BigDecimal("500.00"), captor.getValue().getCompanyWarnAmount()); assertNull(captor.getValue().getSubWarnAmount()); } @Test void ignoresSubWarnAmountInSharedMode() { // 共享模式即使传入子账户预警线也忽略,避免共享模式残留不适用配置 TCompanyBalanceWarningMapper mapper = Mockito.mock(TCompanyBalanceWarningMapper.class); Mockito.when(mapper.selectList(Mockito.any())).thenReturn(Collections.emptyList()); Mockito.when(mapper.insert(Mockito.any(TCompanyBalanceWarning.class))).thenAnswer(invocation -> { TCompanyBalanceWarning entity = invocation.getArgument(0); entity.setId(88); return 1; }); TGroupCompanyMapper companyMapper = Mockito.mock(TGroupCompanyMapper.class); Mockito.when(companyMapper.selectById(11)).thenReturn(company(11, 1)); buildService(mapper, companyMapper).saveBalanceWarning(dto()); ArgumentCaptor captor = ArgumentCaptor.forClass(TCompanyBalanceWarning.class); Mockito.verify(mapper).insert(captor.capture()); assertNull(captor.getValue().getSubWarnAmount()); } @Test void hidesSubWarnAmountInSharedModeDetail() { // 共享模式详情不回显历史子账户预警线 TCompanyBalanceWarning entity = new TCompanyBalanceWarning(); entity.setId(8); entity.setCompanyId(11); entity.setCompanyWarnAmount(new BigDecimal("1000.00")); entity.setSubWarnAmount(new BigDecimal("300.00")); TCompanyBalanceWarningMapper mapper = Mockito.mock(TCompanyBalanceWarningMapper.class); Mockito.when(mapper.selectList(Mockito.any())).thenReturn(Collections.singletonList(entity)); TGroupCompanyMapper companyMapper = Mockito.mock(TGroupCompanyMapper.class); Mockito.when(companyMapper.selectById(11)).thenReturn(company(11, 1)); BalanceWarningDTO detail = buildService(mapper, companyMapper).getBalanceWarning(11); assertEquals(1, detail.getAccountMode()); assertEquals(new BigDecimal("1000.00"), detail.getCompanyWarnAmount()); assertNull(detail.getSubWarnAmount()); } @Test void rejectsOutOfRangeWarnAmountWithoutPersist() { TCompanyBalanceWarningMapper mapper = Mockito.mock(TCompanyBalanceWarningMapper.class); TGroupCompanyMapper companyMapper = Mockito.mock(TGroupCompanyMapper.class); Mockito.when(companyMapper.selectById(11)).thenReturn(company(11)); BalanceWarningDTO dto = dto(); dto.setCompanyWarnAmount(new BigDecimal("-1")); IllegalArgumentException error = assertThrows(IllegalArgumentException.class, () -> buildService(mapper, companyMapper).saveBalanceWarning(dto)); assertEquals("公司预警线需在0~99999999.99之间", error.getMessage()); Mockito.verifyNoInteractions(mapper); } @Test void rejectsBlankNotifyObjectWithoutPersist() { TCompanyBalanceWarningMapper mapper = Mockito.mock(TCompanyBalanceWarningMapper.class); TGroupCompanyMapper companyMapper = Mockito.mock(TGroupCompanyMapper.class); Mockito.when(companyMapper.selectById(11)).thenReturn(company(11)); BalanceWarningDTO dto = dto(); dto.setNotifyObject(null); IllegalArgumentException error = assertThrows(IllegalArgumentException.class, () -> buildService(mapper, companyMapper).saveBalanceWarning(dto)); assertEquals("请选择通知对象", error.getMessage()); Mockito.verifyNoInteractions(mapper); } @Test void rejectsInvalidNotifyObjectAndTypeWithoutPersist() { TCompanyBalanceWarningMapper mapper = Mockito.mock(TCompanyBalanceWarningMapper.class); TGroupCompanyMapper companyMapper = Mockito.mock(TGroupCompanyMapper.class); Mockito.when(companyMapper.selectById(11)).thenReturn(company(11)); BalanceWarningDTO invalidObject = dto(); invalidObject.setNotifyObject(9); assertEquals("通知对象不合法", assertThrows(IllegalArgumentException.class, () -> buildService(mapper, companyMapper).saveBalanceWarning(invalidObject)).getMessage()); BalanceWarningDTO invalidType = dto(); invalidType.setNotifyType(9); assertEquals("通知方式不合法", assertThrows(IllegalArgumentException.class, () -> buildService(mapper, companyMapper).saveBalanceWarning(invalidType)).getMessage()); Mockito.verifyNoInteractions(mapper); } @Test void returnsDetailWithAccountModeOnlyWhenNotConfigured() { TCompanyBalanceWarningMapper mapper = Mockito.mock(TCompanyBalanceWarningMapper.class); Mockito.when(mapper.selectList(Mockito.any())).thenReturn(Collections.emptyList()); TGroupCompanyMapper companyMapper = Mockito.mock(TGroupCompanyMapper.class); Mockito.when(companyMapper.selectById(11)).thenReturn(company(11)); BalanceWarningDTO detail = buildService(mapper, companyMapper).getBalanceWarning(11); assertEquals(11, detail.getCompanyId()); assertEquals(1, detail.getAccountMode()); assertNull(detail.getCompanyWarnAmount()); assertNull(detail.getSubWarnAmount()); assertNull(detail.getNotifyObject()); assertNull(detail.getNotifyType()); } @Test void returnsSavedDetailWithDefaultsForBlankNotifyFields() { TCompanyBalanceWarning entity = new TCompanyBalanceWarning(); entity.setId(8); entity.setCompanyId(11); entity.setCompanyWarnAmount(new BigDecimal("1000.00")); entity.setSubWarnAmount(new BigDecimal("300.00")); entity.setNotifyObject(null); entity.setNotifyType(null); TCompanyBalanceWarningMapper mapper = Mockito.mock(TCompanyBalanceWarningMapper.class); Mockito.when(mapper.selectList(Mockito.any())).thenReturn(Collections.singletonList(entity)); TGroupCompanyMapper companyMapper = Mockito.mock(TGroupCompanyMapper.class); TGroupCompany company = company(11); company.setAccountMode(2); Mockito.when(companyMapper.selectById(11)).thenReturn(company); BalanceWarningDTO detail = buildService(mapper, companyMapper).getBalanceWarning(11); assertEquals(11, detail.getCompanyId()); assertEquals(2, detail.getAccountMode()); assertEquals(new BigDecimal("1000.00"), detail.getCompanyWarnAmount()); assertEquals(new BigDecimal("300.00"), detail.getSubWarnAmount()); assertEquals(1, detail.getNotifyObject()); assertEquals(1, detail.getNotifyType()); } @Test void returnsNullDetailWhenCompanyIdIsNull() { TCompanyBalanceWarningMapper mapper = Mockito.mock(TCompanyBalanceWarningMapper.class); assertNull(buildService(mapper, Mockito.mock(TGroupCompanyMapper.class)).getBalanceWarning(null)); Mockito.verifyNoInteractions(mapper); } private TCompanyBalanceWarningServiceImpl buildService(TCompanyBalanceWarningMapper mapper, TGroupCompanyMapper companyMapper) { TCompanyBalanceWarningServiceImpl service = new TCompanyBalanceWarningServiceImpl(); ReflectionTestUtils.setField(service, "baseMapper", mapper); ReflectionTestUtils.setField(service, "groupCompanyMapper", companyMapper); return service; } private TGroupCompany company(Integer id) { return company(id, 1); } private TGroupCompany company(Integer id, Integer accountMode) { TGroupCompany company = new TGroupCompany(); company.setId(id); company.setName("利普芯科技有限公司"); company.setAccountMode(accountMode); return company; } private BalanceWarningDTO dto() { BalanceWarningDTO dto = new BalanceWarningDTO(); dto.setCompanyId(11); dto.setCompanyWarnAmount(new BigDecimal("500")); dto.setSubWarnAmount(new BigDecimal("200.5")); dto.setNotifyObject(2); dto.setNotifyType(1); return dto; } }