diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/framework/trans/config/TransConfiguration.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/framework/trans/config/TransConfiguration.java index ffd64eb97..8ad344d8f 100644 --- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/framework/trans/config/TransConfiguration.java +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/framework/trans/config/TransConfiguration.java @@ -17,4 +17,9 @@ public class TransConfiguration { resolver.setDefaultLocale(Locale.SIMPLIFIED_CHINESE); return resolver; } + + @Bean + public TransApi transApi(TransProperties transProperties) { + return new TransApi(transProperties.getAppId(), transProperties.getSecurityKey(), transProperties.getApiHost()); + } } diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/dict/DictDataServiceImpl.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/dict/DictDataServiceImpl.java index d22cbb901..cd24ec46e 100644 --- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/dict/DictDataServiceImpl.java +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/dict/DictDataServiceImpl.java @@ -26,7 +26,6 @@ import com.cf.imes.module.system.dal.mysql.dict.DictDataMapper; import com.cf.imes.module.system.enums.ErrorCodeConstants; import com.cf.imes.module.system.enums.common.LanguageEnum; import com.cf.imes.module.system.framework.trans.config.TransApi; -import com.cf.imes.module.system.framework.trans.config.TransProperties; import com.cf.imes.module.system.util.locale.DictDataI18nCacheUtils; import com.google.common.annotations.VisibleForTesting; import lombok.extern.slf4j.Slf4j; @@ -77,7 +76,7 @@ public class DictDataServiceImpl implements DictDataService { private DictDataI18nMapper dictDatai18nMapper; @Resource - private TransProperties transProperties; + private TransApi transApi; @Resource private DictDataI18nVersionMapper dictDataI18nVersionMapper; @@ -245,7 +244,6 @@ public class DictDataServiceImpl implements DictDataService { .collect(Collectors.joining("\n")); // 请求翻译api - TransApi transApi = new TransApi(transProperties.getAppId(), transProperties.getSecurityKey(), transProperties.getApiHost()); String transResult = transApi.getTransResult(namesForQuery, "zh", to); JSONObject obj = JSON.parseObject(transResult); diff --git a/cf-module-system/cf-module-system-biz/src/test/java/com/cf/imes/module/system/controller/admin/dict/DictDataControllerTest.java b/cf-module-system/cf-module-system-biz/src/test/java/com/cf/imes/module/system/controller/admin/dict/DictDataControllerTest.java new file mode 100644 index 000000000..e4cd85c7b --- /dev/null +++ b/cf-module-system/cf-module-system-biz/src/test/java/com/cf/imes/module/system/controller/admin/dict/DictDataControllerTest.java @@ -0,0 +1,207 @@ +package com.cf.imes.module.system.controller.admin.dict; + +import com.cf.imes.framework.common.enums.CommonStatusEnum; +import com.cf.imes.framework.common.pojo.PageResult; +import com.cf.imes.module.system.controller.admin.dict.vo.data.DictDataSaveReqVO; +import com.cf.imes.module.system.controller.admin.dict.vo.data.DictDataSimpleRespVO; +import com.cf.imes.module.system.controller.admin.dict.vo.data.DictDataTransReqVO; +import com.cf.imes.module.system.dal.dataobject.dict.DictDataDO; +import com.cf.imes.module.system.service.dict.DictDataService; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.http.MediaType; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.test.web.servlet.MockMvc; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * 字典数据管理单元测试 + * + * @author Gqr + * @since 2026/1/14 15:38 + */ +@WebMvcTest(controllers = DictDataController.class, excludeAutoConfiguration = SecurityAutoConfiguration.class) +class DictDataControllerTest { + @Autowired + private MockMvc mockMvc; + + @MockitoBean + private DictDataService dictDataService; + + @Autowired + private ObjectMapper objectMapper; + + @Test + void createDictData_success() throws Exception { + DictDataSaveReqVO reqVO = new DictDataSaveReqVO(); + reqVO.setSort(1); + reqVO.setLabel("启用"); + reqVO.setDictType("status"); + reqVO.setValue("1"); + reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus()); + + when(dictDataService.createDictData(any())) + .thenReturn(100L); + + mockMvc.perform(post("/system/dict-data/create") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(reqVO))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(0)) + .andExpect(jsonPath("$.data").value(100L)); + } + + @Test + void updateDictData_success() throws Exception { + DictDataSaveReqVO reqVO = new DictDataSaveReqVO(); + reqVO.setId(1L); + reqVO.setDictType("status"); + reqVO.setValue("1"); + reqVO.setLabel("启用"); + reqVO.setSort(1); + reqVO.setStatus(1); + + mockMvc.perform(put("/system/dict-data/update") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(reqVO))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(0)) + .andExpect(jsonPath("$.data").value(true)); + + // 核心断言:Service 被调用 + verify(dictDataService).updateDictData(any(DictDataSaveReqVO.class)); + } + + @Test + void deleteDictData_success() throws Exception { + mockMvc.perform(delete("/system/dict-data/delete") + .param("id", "1")) + .andExpect(status().isOk()); + + verify(dictDataService).deleteDictData(1L); + } + + @Test + void getSimpleDictDataList_success() throws Exception { + Map> mockResult = new HashMap<>(); + mockResult.put("zh", List.of(new DictDataSimpleRespVO().setLabel("启用"))); + + when(dictDataService.getAllLanguageDictData()).thenReturn(mockResult); + + mockMvc.perform(get("/system/dict-data/list-all-simple")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(0)) + .andExpect(jsonPath("$.data.zh[0].label").value("启用")); + + verify(dictDataService).getAllLanguageDictData(); + } + + @Test + void getDictTypePage_success() throws Exception { + DictDataDO dictData = new DictDataDO(); + dictData.setId(1L); + dictData.setLabel("启用"); + + PageResult pageResult = new PageResult<>(); + pageResult.setTotal(1L); + pageResult.setList(List.of(dictData)); + + when(dictDataService.getDictDataPage(any())) + .thenReturn(pageResult); + + mockMvc.perform(get("/system/dict-data/page") + .param("pageNo", "1") + .param("pageSize", "10")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(0)) + .andExpect(jsonPath("$.data.total").value(1)) + .andExpect(jsonPath("$.data.list[0].label").value("启用")); + + verify(dictDataService).getDictDataPage(any()); + } + + @Test + void getDictData_success() throws Exception { + DictDataDO dictData = new DictDataDO(); + dictData.setId(1L); + dictData.setLabel("启用"); + + when(dictDataService.getDictData(1L)).thenReturn(dictData); + + mockMvc.perform(get("/system/dict-data/get") + .param("id", "1")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(0)) + .andExpect(jsonPath("$.data.label").value("启用")); + + verify(dictDataService).getDictData(1L); + } + + @Test + void export_success() throws Exception { + when(dictDataService.getDictDataPage(any())) + .thenReturn(new PageResult<>(List.of(), 0L)); + + mockMvc.perform(get("/system/dict-data/export") + .param("pageNo", "1") + .param("pageSize", "10")) + .andExpect(status().isOk()); + + verify(dictDataService).getDictDataPage(any()); + } + + @Test + void trans_success() throws Exception { + DictDataTransReqVO reqVO = new DictDataTransReqVO(); + reqVO.setTargetLanguage("en"); + + mockMvc.perform(post("/system/dict-data/trans") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(reqVO))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(0)) + .andExpect(jsonPath("$.data").value(true)); + + verify(dictDataService).trans(any(DictDataTransReqVO.class)); + } + + @Test + void getVersion_success() throws Exception { + when(dictDataService.getI18nVersion()).thenReturn(3); + + mockMvc.perform(get("/system/dict-data/i18n/version")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(0)) + .andExpect(jsonPath("$.data").value(3)); + + verify(dictDataService).getI18nVersion(); + } + + @Test + void updateVersion_success() throws Exception { + mockMvc.perform(post("/system/dict-data/i18n/version")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(0)) + .andExpect(jsonPath("$.data").value(true)); + + verify(dictDataService).updateI18nVersion(); + } + + +} diff --git a/cf-module-system/cf-module-system-biz/src/test/java/com/cf/imes/module/system/service/dept/DeptServiceImplTest.java b/cf-module-system/cf-module-system-biz/src/test/java/com/cf/imes/module/system/service/dept/DeptServiceImplTest.java index 1a0a7ca5e..703814149 100644 --- a/cf-module-system/cf-module-system-biz/src/test/java/com/cf/imes/module/system/service/dept/DeptServiceImplTest.java +++ b/cf-module-system/cf-module-system-biz/src/test/java/com/cf/imes/module/system/service/dept/DeptServiceImplTest.java @@ -660,7 +660,7 @@ public class DeptServiceImplTest extends BaseDbAndRedisUnitTest { () -> deptService.validUserLoginDept(dept.getId())); assertEquals(ErrorCodeConstants.DEPT_NOT_ALLOWED_LOGIN.getCode(), ex.getCode()); - assertTrue(ex.getMessage().contains("测试部-禁用")); + assertTrue(ex.getArgs()[0].toString().contains("测试部-禁用")); } @Test @@ -693,7 +693,7 @@ public class DeptServiceImplTest extends BaseDbAndRedisUnitTest { () -> deptService.validUserLoginDept(child.getId())); assertEquals(ErrorCodeConstants.DEPT_DISABLE.getCode(), ex.getCode()); - assertTrue(ex.getMessage().contains("上级部门-禁用")); + assertTrue(ex.getArgs()[0].toString().contains("上级部门-禁用")); } @Test @@ -740,7 +740,7 @@ public class DeptServiceImplTest extends BaseDbAndRedisUnitTest { () -> deptService.validUserLoginDept(child.getId())); assertEquals(ErrorCodeConstants.DEPT_DISABLE.getCode(), ex.getCode()); - assertTrue(ex.getMessage().contains("顶级部门-禁用")); + assertTrue(ex.getArgs()[0].toString().contains("顶级部门-禁用")); } @Test diff --git a/cf-module-system/cf-module-system-biz/src/test/java/com/cf/imes/module/system/service/dict/DictDataServiceImplTest.java b/cf-module-system/cf-module-system-biz/src/test/java/com/cf/imes/module/system/service/dict/DictDataServiceImplTest.java index 75b708a74..635cf8d35 100644 --- a/cf-module-system/cf-module-system-biz/src/test/java/com/cf/imes/module/system/service/dict/DictDataServiceImplTest.java +++ b/cf-module-system/cf-module-system-biz/src/test/java/com/cf/imes/module/system/service/dict/DictDataServiceImplTest.java @@ -3,60 +3,90 @@ package com.cf.imes.module.system.service.dict; import com.cf.imes.framework.common.enums.CommonStatusEnum; import com.cf.imes.framework.common.pojo.PageResult; import com.cf.imes.framework.common.util.collection.ArrayUtils; -import com.cf.imes.framework.test.core.ut.BaseDbUnitTest; +import com.cf.imes.framework.test.core.ut.BaseMockitoUnitTest; import com.cf.imes.module.system.controller.admin.dict.vo.data.DictDataPageReqVO; import com.cf.imes.module.system.controller.admin.dict.vo.data.DictDataSaveReqVO; +import com.cf.imes.module.system.controller.admin.dict.vo.data.DictDataSimpleRespVO; +import com.cf.imes.module.system.controller.admin.dict.vo.data.DictDataTransReqVO; import com.cf.imes.module.system.dal.dataobject.dict.DictDataDO; +import com.cf.imes.module.system.dal.dataobject.dict.DictDatai18nVersionDO; import com.cf.imes.module.system.dal.dataobject.dict.DictTypeDO; +import com.cf.imes.module.system.dal.mysql.dict.DictDataI18nMapper; +import com.cf.imes.module.system.dal.mysql.dict.DictDataI18nVersionMapper; import com.cf.imes.module.system.dal.mysql.dict.DictDataMapper; +import com.cf.imes.module.system.framework.trans.config.TransApi; +import com.cf.imes.module.system.util.locale.DictDataI18nCacheUtils; import org.junit.jupiter.api.Test; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.context.annotation.Import; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.Mockito; -import jakarta.annotation.Resource; +import org.springframework.data.redis.core.StringRedisTemplate; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.function.Consumer; -import static com.cf.imes.framework.common.util.object.ObjectUtils.cloneIgnoreId; import static com.cf.imes.framework.test.core.util.AssertUtils.assertPojoEquals; import static com.cf.imes.framework.test.core.util.AssertUtils.assertServiceException; import static com.cf.imes.framework.test.core.util.RandomUtils.*; import static com.cf.imes.module.system.enums.ErrorCodeConstants.*; -import static java.util.Collections.singletonList; import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyCollection; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -@Import(DictDataServiceImpl.class) -public class DictDataServiceImplTest extends BaseDbUnitTest { +class DictDataServiceImplTest extends BaseMockitoUnitTest { - @Resource + @InjectMocks private DictDataServiceImpl dictDataService; - @Resource + @Mock private DictDataMapper dictDataMapper; - @MockBean + + @Mock + private DictDataI18nMapper dictDatai18nMapper; + + @Mock + private DictDataI18nVersionMapper dictDataI18nVersionMapper; + + @Mock private DictTypeService dictTypeService; + @Mock + private StringRedisTemplate stringRedisTemplate; + + @Mock + private TransApi transApi; + @Test - public void testGetDictDataList() { + void testGetDictDataList() { // mock 数据 DictDataDO dictDataDO01 = randomDictDataDO().setDictType("yunai").setSort(2) .setStatus(CommonStatusEnum.ENABLE.getStatus()); - dictDataMapper.insert(dictDataDO01); DictDataDO dictDataDO02 = randomDictDataDO().setDictType("yunai").setSort(1) .setStatus(CommonStatusEnum.ENABLE.getStatus()); - dictDataMapper.insert(dictDataDO02); - DictDataDO dictDataDO03 = randomDictDataDO().setDictType("yunai").setSort(3) - .setStatus(CommonStatusEnum.DISABLE.getStatus()); - dictDataMapper.insert(dictDataDO03); - DictDataDO dictDataDO04 = randomDictDataDO().setDictType("yunai2").setSort(3) - .setStatus(CommonStatusEnum.DISABLE.getStatus()); - dictDataMapper.insert(dictDataDO04); + // 准备参数 Integer status = CommonStatusEnum.ENABLE.getStatus(); String dictType = "yunai"; + when(dictDataMapper.selectListByStatusAndDictType(status, dictType)).thenReturn(Arrays.asList(dictDataDO01, dictDataDO02)); + // 调用 List dictDataDOList = dictDataService.getDictDataList(status, dictType); // 断言 @@ -66,268 +96,415 @@ public class DictDataServiceImplTest extends BaseDbUnitTest { } @Test - public void testGetDictDataPage() { - // mock 数据 - DictDataDO dbDictData = randomPojo(DictDataDO.class, o -> { // 等会查询到 + void testGetDictDataPage() { + // 准备模拟数据 + DictDataDO dbDictData = randomPojo(DictDataDO.class, o -> { o.setLabel("晨丰"); o.setDictType("yunai"); o.setStatus(CommonStatusEnum.ENABLE.getStatus()); }); - dictDataMapper.insert(dbDictData); - // 测试 label 不匹配 - dictDataMapper.insert(cloneIgnoreId(dbDictData, o -> o.setLabel("艿"))); - // 测试 dictType 不匹配 - dictDataMapper.insert(cloneIgnoreId(dbDictData, o -> o.setDictType("nai"))); - // 测试 status 不匹配 - dictDataMapper.insert(cloneIgnoreId(dbDictData, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus()))); - // 准备参数 + + List mockList = List.of(dbDictData); + DictDataPageReqVO reqVO = new DictDataPageReqVO(); - reqVO.setLabel("芋"); + reqVO.setLabel("晨"); reqVO.setDictType("yunai"); reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus()); - // 调用 + // mock Mapper 返回值 + PageResult dictDataDOPageResult = new PageResult<>(); + dictDataDOPageResult.setList(mockList); + dictDataDOPageResult.setTotal(1L); + when(dictDataMapper.selectPage(reqVO)).thenReturn(dictDataDOPageResult); + + // 调用 Service PageResult pageResult = dictDataService.getDictDataPage(reqVO); + // 断言 assertEquals(1, pageResult.getTotal()); assertEquals(1, pageResult.getList().size()); assertPojoEquals(dbDictData, pageResult.getList().get(0)); + + // 验证 Mapper 方法被调用 + verify(dictDataMapper, times(1)).selectPage(reqVO); } @Test - public void testGetDictData() { + void testGetDictData() { + Long id = randomLongId(); // mock 数据 DictDataDO dbDictData = randomDictDataDO(); - dictDataMapper.insert(dbDictData); - // 准备参数 - Long id = dbDictData.getId(); + when(dictDataMapper.selectById(id)).thenReturn(dbDictData); // 调用 - DictDataDO dictData = dictDataService.getDictData(id); + DictDataDO queryData = dictDataService.getDictData(id); // 断言 - assertPojoEquals(dbDictData, dictData); + assertPojoEquals(dbDictData, queryData); } @Test - public void testCreateDictData_success() { - // 准备参数 - DictDataSaveReqVO reqVO = randomPojo(DictDataSaveReqVO.class, - o -> o.setStatus(randomCommonStatus())) - .setId(null); // 防止 id 被赋值 + void testCreateDictData_success() { + // 准备请求对象 + DictDataSaveReqVO reqVO = new DictDataSaveReqVO(); + reqVO.setDictType("gender"); + reqVO.setValue("1"); + reqVO.setLabel("男"); + // mock 方法 when(dictTypeService.getDictType(eq(reqVO.getDictType()))).thenReturn(randomDictTypeDO(reqVO.getDictType())); - // 调用 - Long dictDataId = dictDataService.createDictData(reqVO); - // 断言 - assertNotNull(dictDataId); - // 校验记录的属性是否正确 - DictDataDO dictData = dictDataMapper.selectById(dictDataId); - assertPojoEquals(reqVO, dictData, "id"); + // 模拟 Mapper 插入时生成 ID + doAnswer(invocation -> { + DictDataDO arg = invocation.getArgument(0); + arg.setId(100L); // 模拟自增主键 + return null; + }).when(dictDataMapper).insert(any(DictDataDO.class)); + + // 调用 Service + Long id = dictDataService.createDictData(reqVO); + + // 断言返回的 ID 是模拟的 + assertEquals(100L, id); + + // 验证 Mapper 方法被调用一次,且参数是根据请求对象生成的 DictDataDO + verify(dictDataMapper, times(1)).insert(argThat((DictDataDO dict) -> + "gender".equals(dict.getDictType()) && + "1".equals(dict.getValue()) && + "男".equals(dict.getLabel()) + )); } @Test - public void testUpdateDictData_success() { - // mock 数据 - DictDataDO dbDictData = randomDictDataDO(); - dictDataMapper.insert(dbDictData);// @Sql: 先插入出一条存在的数据 - // 准备参数 - DictDataSaveReqVO reqVO = randomPojo(DictDataSaveReqVO.class, o -> { - o.setId(dbDictData.getId()); // 设置更新的 ID - o.setStatus(randomCommonStatus()); - }); + void testUpdateDictData_success() { + // 准备请求对象 + DictDataSaveReqVO reqVO = new DictDataSaveReqVO(); + reqVO.setId(100L); + reqVO.setDictType("gender"); + reqVO.setValue("1"); + reqVO.setLabel("男"); + // mock 方法,字典类型 + when(dictDataMapper.selectById(anyLong())).thenReturn(randomDictDataDO()); when(dictTypeService.getDictType(eq(reqVO.getDictType()))).thenReturn(randomDictTypeDO(reqVO.getDictType())); - // 调用 + // Mock Mapper 的 updateById 方法 + when(dictDataMapper.updateById(any(DictDataDO.class))).thenReturn(1); + + // 调用 Service dictDataService.updateDictData(reqVO); - // 校验是否更新正确 - DictDataDO dictData = dictDataMapper.selectById(reqVO.getId()); // 获取最新的 - assertPojoEquals(reqVO, dictData); + + // 验证 Mapper 被调用 + verify(dictDataMapper, times(1)).updateById(argThat((DictDataDO dict) -> + dict.getId().equals(100L) && + "gender".equals(dict.getDictType()) && + "1".equals(dict.getValue()) && + "男".equals(dict.getLabel()) + )); } @Test - public void testDeleteDictData_success() { - // mock 数据 - DictDataDO dbDictData = randomDictDataDO(); - dictDataMapper.insert(dbDictData);// @Sql: 先插入出一条存在的数据 - // 准备参数 - Long id = dbDictData.getId(); + void testDeleteDictData_success() { + Long id = 100L; + + // mock 方法,字典类型 + when(dictDataMapper.selectById(anyLong())).thenReturn(randomDictDataDO()); + + // mock mapper 删除行为(deleteById 返回 int) + when(dictDataMapper.deleteById(anyLong())).thenReturn(1); // 调用 dictDataService.deleteDictData(id); - // 校验数据不存在了 - assertNull(dictDataMapper.selectById(id)); + + // 验证 mapper 被正确调用 + verify(dictDataMapper, times(1)).deleteById(id); } @Test - public void testValidateDictDataExists_success() { - // mock 数据 - DictDataDO dbDictData = randomDictDataDO(); - dictDataMapper.insert(dbDictData);// @Sql: 先插入出一条存在的数据 + void testValidateDictDataExists_success() { + Long id = 1L; + DictDataDO dictData = new DictDataDO(); + dictData.setId(id); - // 调用成功 - dictDataService.validateDictDataExists(dbDictData.getId()); + when(dictDataMapper.selectById(id)).thenReturn(dictData); + + dictDataService.validateDictDataExists(id); + + verify(dictDataMapper).selectById(id); } @Test - public void testValidateDictDataExists_notExists() { - assertServiceException(() -> dictDataService.validateDictDataExists(randomLongId()), DICT_DATA_NOT_EXISTS); + void testValidateDictDataExists_notExists() { + Long id = 1L; + when(dictDataMapper.selectById(id)).thenReturn(null); + + // 异常 + assertServiceException(() -> dictDataService.validateDictDataExists(id), DICT_DATA_NOT_EXISTS); } @Test - public void testValidateDictTypeExists_success() { - // mock 方法,数据类型被禁用 - String type = randomString(); - when(dictTypeService.getDictType(eq(type))).thenReturn(randomDictTypeDO(type)); + void testValidateDictDataExists_null_notExists() { + // 不抛异常即可 + dictDataService.validateDictDataExists(null); - // 调用, 成功 + // mapper 不应被调用 + verify(dictDataMapper, never()).selectById(any()); + } + + @Test + void testValidateDictTypeExists_success() { + String type = "gender"; + DictTypeDO dictType = new DictTypeDO(); + dictType.setType(type); + dictType.setStatus(CommonStatusEnum.ENABLE.getStatus()); + + when(dictTypeService.getDictType(type)).thenReturn(dictType); + + // 不抛异常即成功 dictDataService.validateDictTypeExists(type); + + verify(dictTypeService).getDictType(type); } @Test - public void testValidateDictTypeExists_notExists() { - assertServiceException(() -> dictDataService.validateDictTypeExists(randomString()), DICT_TYPE_NOT_EXISTS); + void testValidateDictTypeExists_notExists() { + String type = "gender"; + when(dictTypeService.getDictType(type)).thenReturn(null); + + assertServiceException(() -> dictDataService.validateDictTypeExists(type), DICT_TYPE_NOT_EXISTS); } @Test - public void testValidateDictTypeExists_notEnable() { - // mock 方法,数据类型被禁用 - String dictType = randomString(); - when(dictTypeService.getDictType(eq(dictType))).thenReturn( - randomPojo(DictTypeDO.class, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus()))); + void testValidateDictTypeExists_notEnable() { + String type = "gender"; + DictTypeDO dictType = new DictTypeDO(); + dictType.setType(type); + dictType.setStatus(CommonStatusEnum.DISABLE.getStatus()); + + when(dictTypeService.getDictType(type)).thenReturn(dictType); // 调用, 并断言异常 - assertServiceException(() -> dictDataService.validateDictTypeExists(dictType), DICT_TYPE_NOT_ENABLE); + assertServiceException(() -> dictDataService.validateDictTypeExists(type), DICT_TYPE_NOT_ENABLE); } @Test - public void testValidateDictDataValueUnique_success() { - // 调用,成功 - dictDataService.validateDictDataValueUnique(randomLongId(), randomString(), randomString()); + void testValidateDictDataValueUnique_success() { + DictDataDO exist = new DictDataDO(); + exist.setId(10L); + + when(dictDataMapper.selectByDictTypeAndValue("gender", "1")) + .thenReturn(exist); + + dictDataService.validateDictDataValueUnique(10L, "gender", "1"); + + verify(dictDataMapper).selectByDictTypeAndValue("gender", "1"); } @Test - public void testValidateDictDataValueUnique_valueDuplicateForCreate() { - // 准备参数 - String dictType = randomString(); - String value = randomString(); - // mock 数据 - dictDataMapper.insert(randomDictDataDO(o -> { - o.setDictType(dictType); - o.setValue(value); - })); + void testValidateDictDataValueUnique_exist_success() { + when(dictDataMapper.selectByDictTypeAndValue(anyString(), anyString())) + .thenReturn(null); + + dictDataService.validateDictDataValueUnique(1L, "gender", "1"); + + verify(dictDataMapper).selectByDictTypeAndValue("gender", "1"); + } + + @Test + void testValidateDictDataValueUnique_valueDuplicateForCreate() { + DictDataDO exist = new DictDataDO(); + exist.setId(10L); + + when(dictDataMapper.selectByDictTypeAndValue("gender", "1")) + .thenReturn(exist); // 调用,校验异常 - assertServiceException(() -> dictDataService.validateDictDataValueUnique(null, dictType, value), + assertServiceException(() -> dictDataService.validateDictDataValueUnique(null, "gender", "1"), DICT_DATA_VALUE_DUPLICATE); } @Test - public void testValidateDictDataValueUnique_valueDuplicateForUpdate() { - // 准备参数 - Long id = randomLongId(); - String dictType = randomString(); - String value = randomString(); - // mock 数据 - dictDataMapper.insert(randomDictDataDO(o -> { - o.setDictType(dictType); - o.setValue(value); - })); + void testValidateDictDataValueUnique_valueDuplicateForUpdate() { + DictDataDO exist = new DictDataDO(); + exist.setId(10L); + + when(dictDataMapper.selectByDictTypeAndValue("gender", "1")) + .thenReturn(exist); // 调用,校验异常 - assertServiceException(() -> dictDataService.validateDictDataValueUnique(id, dictType, value), + assertServiceException(() -> dictDataService.validateDictDataValueUnique(20L, "gender", "1"), DICT_DATA_VALUE_DUPLICATE); } @Test - public void testGetDictDataCountByDictType() { - // mock 数据 - dictDataMapper.insert(randomDictDataDO(o -> o.setDictType("yunai"))); - dictDataMapper.insert(randomDictDataDO(o -> o.setDictType("tudou"))); - dictDataMapper.insert(randomDictDataDO(o -> o.setDictType("yunai"))); - // 准备参数 - String dictType = "yunai"; + void testGetDictDataCountByDictType() { + when(dictDataMapper.selectCountByDictType("gender")).thenReturn(5L); - // 调用 - long count = dictDataService.getDictDataCountByDictType(dictType); - // 校验 - assertEquals(2L, count); + long count = dictDataService.getDictDataCountByDictType("gender"); + + assertEquals(5L, count); + verify(dictDataMapper).selectCountByDictType("gender"); } @Test - public void testValidateDictDataList_success() { - // mock 数据 - DictDataDO dictDataDO = randomDictDataDO().setStatus(CommonStatusEnum.ENABLE.getStatus()); - dictDataMapper.insert(dictDataDO); - // 准备参数 - String dictType = dictDataDO.getDictType(); - List values = singletonList(dictDataDO.getValue()); + void testValidateDictDataList_success() { + String dictType = "gender"; + List values = List.of("1", "2"); + + DictDataDO male = new DictDataDO(); + male.setValue("1"); + male.setStatus(CommonStatusEnum.ENABLE.getStatus()); + + DictDataDO female = new DictDataDO(); + female.setValue("2"); + female.setStatus(CommonStatusEnum.ENABLE.getStatus()); + + when(dictDataMapper.selectByDictTypeAndValues(dictType, values)) + .thenReturn(List.of(male, female)); - // 调用,无需断言 dictDataService.validateDictDataList(dictType, values); + + verify(dictDataMapper).selectByDictTypeAndValues(dictType, values); } @Test - public void testValidateDictDataList_notFound() { - // 准备参数 - String dictType = randomString(); - List values = singletonList(randomString()); + void testValidateDictDataList_notFound() { + String dictType = "gender"; + List values = List.of("1", "2"); + + DictDataDO onlyOne = new DictDataDO(); + onlyOne.setValue("1"); + onlyOne.setStatus(CommonStatusEnum.ENABLE.getStatus()); + + when(dictDataMapper.selectByDictTypeAndValues(dictType, values)) + .thenReturn(List.of(onlyOne)); // 调用, 并断言异常 assertServiceException(() -> dictDataService.validateDictDataList(dictType, values), DICT_DATA_NOT_EXISTS); } @Test - public void testValidateDictDataList_notEnable() { - // mock 数据 - DictDataDO dictDataDO = randomDictDataDO().setStatus(CommonStatusEnum.DISABLE.getStatus()); - dictDataMapper.insert(dictDataDO); - // 准备参数 - String dictType = dictDataDO.getDictType(); - List values = singletonList(dictDataDO.getValue()); + void testValidateDictDataList_notEnable() { + String dictType = "gender"; + List values = List.of("1"); + + DictDataDO disabled = new DictDataDO(); + disabled.setValue("1"); + disabled.setStatus(CommonStatusEnum.DISABLE.getStatus()); + disabled.setLabel("男"); + + when(dictDataMapper.selectByDictTypeAndValues(dictType, values)) + .thenReturn(List.of(disabled)); // 调用, 并断言异常 assertServiceException(() -> dictDataService.validateDictDataList(dictType, values), - DICT_DATA_NOT_ENABLE, dictDataDO.getLabel()); + DICT_DATA_NOT_ENABLE, "男"); } @Test - public void testGetDictData_dictType() { - // mock 数据 - DictDataDO dictDataDO = randomDictDataDO().setDictType("yunai").setValue("1"); - dictDataMapper.insert(dictDataDO); - DictDataDO dictDataDO02 = randomDictDataDO().setDictType("yunai").setValue("2"); - dictDataMapper.insert(dictDataDO02); - // 准备参数 - String dictType = "yunai"; + void testValidateDictDataList_empty() { + dictDataService.validateDictDataList("gender", Collections.emptyList()); + + verify(dictDataMapper, never()) + .selectByDictTypeAndValues(anyString(), anyCollection()); + } + + @Test + void testGetDictData_dictType() { + String dictType = "gender"; String value = "1"; - // 调用 - DictDataDO dbDictData = dictDataService.getDictData(dictType, value); - // 断言 - assertEquals(dictDataDO, dbDictData); + DictDataDO dictData = new DictDataDO(); + dictData.setDictType(dictType); + dictData.setValue(value); + + when(dictDataMapper.selectByDictTypeAndValue(dictType, value)) + .thenReturn(dictData); + + DictDataDO result = dictDataService.getDictData(dictType, value); + + assertSame(dictData, result); + verify(dictDataMapper).selectByDictTypeAndValue(dictType, value); } @Test - public void testParseDictData() { - // mock 数据 - DictDataDO dictDataDO = randomDictDataDO().setDictType("yunai").setLabel("1"); - dictDataMapper.insert(dictDataDO); - DictDataDO dictDataDO02 = randomDictDataDO().setDictType("yunai").setLabel("2"); - dictDataMapper.insert(dictDataDO02); - // 准备参数 - String dictType = "yunai"; - String label = "1"; + void testGetDictI18nData_success() { + String dictType = "gender"; + String value = "1"; + String language = "en_US"; - // 调用 - DictDataDO dbDictData = dictDataService.parseDictData(dictType, label); - // 断言 - assertEquals(dictDataDO, dbDictData); + DictDataDO dictData = new DictDataDO(); + dictData.setDictType(dictType); + dictData.setValue(value); + dictData.setLabel("Male"); + + when(dictDatai18nMapper + .selectI18nByDictTypeAndValue(dictType, value, language)) + .thenReturn(dictData); + + DictDataDO result = + dictDataService.getDictI18nData(dictType, value, language); + + assertSame(dictData, result); + verify(dictDatai18nMapper) + .selectI18nByDictTypeAndValue(dictType, value, language); } - // ========== 随机对象 ========== + @Test + void testParseDictData() { + String dictType = "gender"; + String label = "男"; + DictDataDO dictData = new DictDataDO(); + dictData.setDictType(dictType); + dictData.setLabel(label); + + when(dictDataMapper.selectByDictTypeAndLabel(dictType, label)) + .thenReturn(dictData); + + DictDataDO result = dictDataService.parseDictData(dictType, label); + + assertSame(dictData, result); + verify(dictDataMapper).selectByDictTypeAndLabel(dictType, label); + } + + @Test + void testGetI18nVersion_nonEmpty() { + DictDatai18nVersionDO versionDO = new DictDatai18nVersionDO(); + versionDO.setVersion(42); + + when(dictDataI18nVersionMapper.selectList()).thenReturn(List.of(versionDO)); + + Integer version = dictDataService.getI18nVersion(); + + assertEquals(42, version); + verify(dictDataI18nVersionMapper).selectList(); + } + + @Test + void testGetI18nVersion_emptyList() { + when(dictDataI18nVersionMapper.selectList()).thenReturn(Collections.emptyList()); + + Integer version = dictDataService.getI18nVersion(); + + assertEquals(0, version); + verify(dictDataI18nVersionMapper).selectList(); + } + + @Test + void testUpdateI18nVersion_success() { + // 调用方法 + dictDataService.updateI18nVersion(); + + // 验证 mapper 的 updateVersion 被调用 + verify(dictDataI18nVersionMapper).updateVersion(); + + // 验证 Redis 通知被发送 + verify(stringRedisTemplate).convertAndSend( + com.cf.imes.framework.redis.constants.RedisKeyConstants.DICT_DATA_I18N_CACHE_REFRESH, ""); + } + +// // ========== 随机对象 ========== +// @SafeVarargs private static DictDataDO randomDictDataDO(Consumer... consumers) { Consumer consumer = (o) -> { @@ -336,6 +513,87 @@ public class DictDataServiceImplTest extends BaseDbUnitTest { return randomPojo(DictDataDO.class, ArrayUtils.append(consumer, consumers)); } + @Test + void testGetAllLanguageDictData_withCache() { + Map> cache = new HashMap<>(); + cache.put("ZH", List.of(new DictDataSimpleRespVO())); + + try (MockedStatic mockedCache = Mockito.mockStatic(DictDataI18nCacheUtils.class)) { + mockedCache.when(DictDataI18nCacheUtils::getCache).thenReturn(cache); + + Map> result = dictDataService.getAllLanguageDictData(); + + assertSame(cache, result); + mockedCache.verify(DictDataI18nCacheUtils::getCache, times(1)); + } + } + + @Test + void testGetAllLanguageDictData_noCache() { + List dictDataList = Arrays.asList(new DictDataDO().setId(1L).setDictType("type")); + List enList = List.of(new DictDataDO().setId(1L)); + + try (MockedStatic mockedCache = Mockito.mockStatic(DictDataI18nCacheUtils.class)) { + mockedCache.when(DictDataI18nCacheUtils::getCache).thenReturn(Collections.emptyMap()); + + when(dictDataMapper.selectListByStatusAndDictType(CommonStatusEnum.ENABLE.getStatus(), null)) + .thenReturn(dictDataList); + when(dictDatai18nMapper.getLocaleDictData(List.of(1L), "en")).thenReturn(enList); + + Map> result = dictDataService.getAllLanguageDictData(); + + // 验证中文和英文都存在 + assertTrue(result.containsKey("zh")); + assertTrue(result.containsKey("en")); + + mockedCache.verify(() -> DictDataI18nCacheUtils.putCache(result), times(1)); + } + } + + @Test + void testTrans_success() { + DictDataDO dictDataDO1 = new DictDataDO().setId(1L).setLabel("苹果").setStatus(CommonStatusEnum.ENABLE.getStatus()); + List dictDataList = List.of(dictDataDO1); + + // mock 数据库 + when(dictDatai18nMapper.delete(any())).thenReturn(1); + when(dictDataMapper.selectList(any())).thenReturn(dictDataList); + + // mock TransApi + doReturn("{\"trans_result\":[{\"src\":\"苹果\",\"dst\":\"Apple\"}]}") + .when(transApi).getTransResult(any(), any(), any()); + + DictDataTransReqVO reqVO = new DictDataTransReqVO(); + reqVO.setTargetLanguage("en"); + dictDataService.trans(reqVO); + + // 验证删除和批量插入被调用 + verify(dictDatai18nMapper).delete(any()); + verify(dictDatai18nMapper).insertBatch(anyList()); + } + + @Test + void testTrans_request_error() { + DictDataDO dictDataDO1 = new DictDataDO().setId(1L).setLabel("苹果").setStatus(CommonStatusEnum.ENABLE.getStatus()); + List dictDataList = List.of(dictDataDO1); + + // mock 数据库 + when(dictDatai18nMapper.delete(any())).thenReturn(1); + when(dictDataMapper.selectList(any())).thenReturn(dictDataList); + + // mock TransApi + doReturn("{\"error_code\":\"52002\"}") + .when(transApi).getTransResult(any(), any(), any()); + + DictDataTransReqVO reqVO = new DictDataTransReqVO(); + reqVO.setTargetLanguage("en"); + + assertServiceException(() -> dictDataService.trans(reqVO), + DICT_DATA_TRANS_ERROR, "{\"error_code\":\"52002\"}"); + } + + + /** * 生成一个有效的字典类型 *