From c7476f594b36a67d2ec4c55462d226a2c5efa1fe Mon Sep 17 00:00:00 2001 From: gaoqr <13665037151@163.com> Date: Wed, 7 Jan 2026 17:34:38 +0800 Subject: [PATCH] =?UTF-8?q?1=E3=80=81=E8=B4=B4=E7=9A=AE=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E9=83=A8=E5=88=86=E6=9B=B4=E6=96=B0=E6=96=B9=E6=B3=95=E5=9B=9E?= =?UTF-8?q?=E8=B0=83=E7=B1=BB=E5=9E=8B=E4=BF=AE=E6=AD=A3=EF=BC=9B2?= =?UTF-8?q?=E3=80=81=E6=96=B0=E5=A2=9Eexec=E6=9C=8D=E5=8A=A1=E5=8D=95?= =?UTF-8?q?=E5=85=83=E6=B5=8B=E8=AF=95=E9=85=8D=E7=BD=AE=EF=BC=9B3?= =?UTF-8?q?=E5=A2=9E=E8=B4=B4=E7=9A=AE=E7=AE=A1=E7=90=86service=E3=80=81co?= =?UTF-8?q?ntroller=E5=8D=95=E5=85=83=E6=B5=8B=E8=AF=95=E5=AE=8C=E5=96=84?= =?UTF-8?q?=EF=BC=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/veneer/VeneerController.java | 11 +- .../service/veneer/VeneerService.java | 6 +- .../service/veneer/VeneerServiceImpl.java | 33 +- .../admin/veneer/VeneerControllerTest.java | 206 ++++++++ .../service/veneer/VeneerServiceImplTest.java | 446 ++++++++++++++++++ .../test/resources/application-unit-test.yaml | 59 +++ .../src/test/resources/logback.xml | 4 + .../src/test/resources/sql/clean.sql | 1 + .../src/test/resources/sql/create_tables.sql | 34 ++ 9 files changed, 774 insertions(+), 26 deletions(-) create mode 100644 cf-module-prod-executor/cf-module-prod-executor-biz/src/test/java/com/cf/imes/module/executor/controller/admin/veneer/VeneerControllerTest.java create mode 100644 cf-module-prod-executor/cf-module-prod-executor-biz/src/test/java/com/cf/imes/module/executor/service/veneer/VeneerServiceImplTest.java create mode 100644 cf-module-prod-executor/cf-module-prod-executor-biz/src/test/resources/application-unit-test.yaml create mode 100644 cf-module-prod-executor/cf-module-prod-executor-biz/src/test/resources/logback.xml create mode 100644 cf-module-prod-executor/cf-module-prod-executor-biz/src/test/resources/sql/clean.sql create mode 100644 cf-module-prod-executor/cf-module-prod-executor-biz/src/test/resources/sql/create_tables.sql diff --git a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/controller/admin/veneer/VeneerController.java b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/controller/admin/veneer/VeneerController.java index 8a85c9ef8..b5a545a23 100644 --- a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/controller/admin/veneer/VeneerController.java +++ b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/controller/admin/veneer/VeneerController.java @@ -104,16 +104,18 @@ public class VeneerController { @PutMapping("") @Operation(summary = "编辑贴皮") public CommonResult updateVeneer(@Valid @RequestBody VeneerSaveReqVO reqVO) { - return success(veneerService.update(reqVO) == 1); + veneerService.update(reqVO); + return success(true); } @DeleteMapping("/{id}") @Operation(summary = "删除贴皮") public CommonResult deleteVeneer(@PathVariable Long id) { - return success(veneerService.delete(id) == 1); + veneerService.delete(id); + return success(true); } - @PutMapping("/pic") + @PostMapping("/pic") @Operation(summary = "上传贴皮照片") @OperateLog(logArgs = false, type = CREATE) public CommonResult uploadVeneerPic(@RequestPart(value = "file") MultipartFile file) throws IOException { @@ -123,6 +125,7 @@ public class VeneerController { @PostMapping("/defect") @Operation(summary = "保存瑕疵数据") public CommonResult saveDefect(@Valid @RequestBody VeneerDefectSaveReqVO reqVO) { - return success(veneerService.saveDefect(reqVO) == 1); + veneerService.saveDefect(reqVO); + return success(true); } } diff --git a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/service/veneer/VeneerService.java b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/service/veneer/VeneerService.java index 940ed2538..c4bce888f 100644 --- a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/service/veneer/VeneerService.java +++ b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/service/veneer/VeneerService.java @@ -34,7 +34,7 @@ public interface VeneerService { * @param reqVO * @return */ - int update(VeneerSaveReqVO reqVO); + void update(VeneerSaveReqVO reqVO); /** * 删除贴皮 @@ -42,7 +42,7 @@ public interface VeneerService { * @param id * @return */ - int delete(Long id); + void delete(Long id); /** * 上传贴皮照片 @@ -95,5 +95,5 @@ public interface VeneerService { * @param defectSaveReqVO * @return */ - int saveDefect(VeneerDefectSaveReqVO defectSaveReqVO); + void saveDefect(VeneerDefectSaveReqVO defectSaveReqVO); } diff --git a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/service/veneer/VeneerServiceImpl.java b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/service/veneer/VeneerServiceImpl.java index 0df360975..948f8f265 100644 --- a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/service/veneer/VeneerServiceImpl.java +++ b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/service/veneer/VeneerServiceImpl.java @@ -31,6 +31,7 @@ import java.time.LocalDateTime; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Optional; import java.util.stream.Collectors; import static cn.hutool.core.date.DatePattern.PURE_DATETIME_PATTERN; @@ -73,7 +74,7 @@ public class VeneerServiceImpl implements VeneerService { } @Override - public int update(VeneerSaveReqVO reqVO) { + public void update(VeneerSaveReqVO reqVO) { Long id = reqVO.getId(); // 1、校验贴皮是否存在 @@ -90,18 +91,18 @@ public class VeneerServiceImpl implements VeneerService { //3、贴皮数据入库 VeneerDO veneerDO = BeanUtil.copyProperties(reqVO, VeneerDO.class); - return veneerMapper.updateById(veneerDO); + veneerMapper.updateById(veneerDO); } @Override - public int delete(Long id) { + public void delete(Long id) { // 1、校验贴皮是否存在 if (ObjectUtil.isNull(validateExist(id))) { throw new ServiceException(VENEER_NOT_EXIST); } //2、删除贴皮 - return veneerMapper.deleteById(id); + veneerMapper.deleteById(id); } @Override @@ -161,7 +162,7 @@ public class VeneerServiceImpl implements VeneerService { } @Override - public int saveDefect(VeneerDefectSaveReqVO defectSaveReqVO) { + public void saveDefect(VeneerDefectSaveReqVO defectSaveReqVO) { Long id = defectSaveReqVO.getId(); // 校验贴皮是否存在 if (ObjectUtil.isNull(validateExist(id))) { @@ -171,12 +172,11 @@ public class VeneerServiceImpl implements VeneerService { String defectData = defectSaveReqVO.getDefectData(); if(StringUtils.isNotEmpty(defectData)) { // 更新瑕疵数据和状态 - return veneerMapper.update(new LambdaUpdateWrapper() + veneerMapper.update(new LambdaUpdateWrapper() .eq(VeneerDO::getId, id) .set(VeneerDO::getDefect, true) .set(VeneerDO::getDefectData, JsonUtils.zipString(defectData))); } - return 1; } /** @@ -209,17 +209,12 @@ public class VeneerServiceImpl implements VeneerService { * @return */ public static String getFileExtension(String fileName) { - if (StringUtils.isEmpty(fileName)) { - return ""; - } - - int lastDotIndex = fileName.lastIndexOf('.'); - if (lastDotIndex < 0 || lastDotIndex == fileName.length() - 1) { - // 没有点号,或者点号在最后一位(例如 "file.") - return ""; - } - - // 截取后缀部分并转小写 - return "." + fileName.substring(lastDotIndex + 1).toLowerCase(Locale.ROOT); + return Optional.ofNullable(fileName) + .filter(StringUtils::isNotBlank) + .filter(name -> name.contains(".")) + .map(name -> name.substring(name.lastIndexOf('.'))) + .filter(ext -> ext.length() > 1) + .map(ext -> ext.toLowerCase(Locale.ROOT)) + .orElse(".jpg"); } } diff --git a/cf-module-prod-executor/cf-module-prod-executor-biz/src/test/java/com/cf/imes/module/executor/controller/admin/veneer/VeneerControllerTest.java b/cf-module-prod-executor/cf-module-prod-executor-biz/src/test/java/com/cf/imes/module/executor/controller/admin/veneer/VeneerControllerTest.java new file mode 100644 index 000000000..073921f15 --- /dev/null +++ b/cf-module-prod-executor/cf-module-prod-executor-biz/src/test/java/com/cf/imes/module/executor/controller/admin/veneer/VeneerControllerTest.java @@ -0,0 +1,206 @@ +package com.cf.imes.module.executor.controller.admin.veneer; + +import com.cf.imes.framework.common.pojo.PageResult; +import com.cf.imes.framework.security.test.WithMockLoginUser; +import com.cf.imes.framework.test.core.ut.BaseWebUnitTest; +import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerDefectSaveReqVO; +import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerRespVO; +import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerSaveReqVO; +import com.cf.imes.module.executor.enums.veneer.VeneerProductLevelEnum; +import com.cf.imes.module.executor.service.veneer.VeneerService; +import com.cf.imes.module.executor.util.file.FileHelperUtil; +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.servlet.http.HttpServletResponse; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +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.mock.web.MockMultipartFile; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.test.web.servlet.MockMvc; + +import java.math.BigDecimal; +import java.util.Collections; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart; +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; + +/** + * 贴皮管理controller 单元测试 + * + * @author Gqr + * @since 2026/1/7 16:58 + */ +@WebMvcTest(controllers = VeneerController.class, excludeAutoConfiguration = SecurityAutoConfiguration.class) +class VeneerControllerTest extends BaseWebUnitTest { + @Autowired + private MockMvc mockMvc; + + @MockitoBean + private VeneerService veneerService; + + @Autowired + private ObjectMapper objectMapper; + + @Test + void getVeneerPage_success() throws Exception { + PageResult page = new PageResult<>(); + page.setTotal(0L); + page.setList(Collections.emptyList()); + + when(veneerService.getPage(any())).thenReturn(page); + + mockMvc.perform(get("/executor/veneer/page")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(0)); + } + + @Test + void exportVeneerPage_success() throws Exception { + // mock service 返回 + PageResult page = new PageResult<>(); + page.setList(Collections.emptyList()); + page.setTotal(0L); + + when(veneerService.getPage(any())).thenReturn(page); + + mockMvc.perform(get("/executor/veneer/export")) + .andExpect(status().isOk()); + } + + @Test + void getVeneer_success() throws Exception { + VeneerRespVO resp = new VeneerRespVO(); + resp.setId(1L); + + when(veneerService.get(1L)).thenReturn(resp); + + mockMvc.perform(get("/executor/veneer/1")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data.id").value(1L)); + } + + @Test + void createVeneer_success() throws Exception { + // 准备参数 + VeneerSaveReqVO veneerSaveReqVO = new VeneerSaveReqVO(); + veneerSaveReqVO.setVeneerNo("SP14126"); + veneerSaveReqVO.setClassificationName("分类名称"); + veneerSaveReqVO.setProductLevel(VeneerProductLevelEnum.A.getCode()); + veneerSaveReqVO.setVeneerName("贴皮名称"); + veneerSaveReqVO.setNum(1); + veneerSaveReqVO.setWidth(new BigDecimal(10)); + veneerSaveReqVO.setLength(new BigDecimal(20)); + veneerSaveReqVO.setThickness(new BigDecimal(30)); + + when(veneerService.create(any())).thenReturn(1L); + + mockMvc.perform(post("/executor/veneer") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(veneerSaveReqVO))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data").value(1L)); + } + + @Test + void updateVeneer_success() throws Exception { + // 准备参数 + VeneerSaveReqVO veneerSaveReqVO = new VeneerSaveReqVO(); + veneerSaveReqVO.setId(999l); + veneerSaveReqVO.setVeneerNo("SP14126"); + veneerSaveReqVO.setClassificationName("分类名称"); + veneerSaveReqVO.setProductLevel(VeneerProductLevelEnum.A.getCode()); + veneerSaveReqVO.setVeneerName("贴皮名称"); + veneerSaveReqVO.setNum(1); + veneerSaveReqVO.setWidth(new BigDecimal(10)); + veneerSaveReqVO.setLength(new BigDecimal(20)); + veneerSaveReqVO.setThickness(new BigDecimal(30)); + + doNothing().when(veneerService).update(any()); + + mockMvc.perform(put("/executor/veneer") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(veneerSaveReqVO))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data").value(true)); + } + + @Test + void deleteVeneer_success() throws Exception { + doNothing().when(veneerService).delete(1L); + + mockMvc.perform(delete("/executor/veneer/1")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data").value(true)); + } + + @Test + void uploadVeneerPic_success() throws Exception { + MockMultipartFile file = new MockMultipartFile( + "file", "a.jpg", "image/jpeg", "123".getBytes() + ); + + when(veneerService.uploadPic(any())).thenReturn(10L); + + mockMvc.perform(multipart("/executor/veneer/pic") + .file(file)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data").value(10L)); + } + + @Test + @WithMockLoginUser + void importExcel_success() throws Exception { + MockMultipartFile file = new MockMultipartFile( + "file", "test.xlsx", + "application/vnd.ms-excel", + new byte[]{1, 2, 3} + ); + + mockMvc.perform(multipart("/executor/veneer/import").file(file)) + .andExpect(status().isOk()); + } + + @Test + void getVeneerImportTemplate_success() throws Exception { + try (MockedStatic mocked = + Mockito.mockStatic(FileHelperUtil.class)) { + + mocked.when(() -> + FileHelperUtil.getFirstFile( + anyString(), + any(HttpServletResponse.class)) + ).thenAnswer(invocation -> null); + + mockMvc.perform(get("/executor/veneer/import/template")) + .andExpect(status().isOk()); + } + } + + @Test + void saveDefect_success() throws Exception { + VeneerDefectSaveReqVO reqVO = new VeneerDefectSaveReqVO(); + reqVO.setId(1L); + + doNothing().when(veneerService).saveDefect(any()); + + mockMvc.perform(post("/executor/veneer/defect") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(reqVO))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data").value(true)); + } + +} diff --git a/cf-module-prod-executor/cf-module-prod-executor-biz/src/test/java/com/cf/imes/module/executor/service/veneer/VeneerServiceImplTest.java b/cf-module-prod-executor/cf-module-prod-executor-biz/src/test/java/com/cf/imes/module/executor/service/veneer/VeneerServiceImplTest.java new file mode 100644 index 000000000..e336422f3 --- /dev/null +++ b/cf-module-prod-executor/cf-module-prod-executor-biz/src/test/java/com/cf/imes/module/executor/service/veneer/VeneerServiceImplTest.java @@ -0,0 +1,446 @@ +package com.cf.imes.module.executor.service.veneer; + +import com.cf.imes.framework.common.exception.ServiceException; +import com.cf.imes.framework.common.pojo.CommonResult; +import com.cf.imes.framework.common.pojo.PageResult; +import com.cf.imes.framework.common.util.json.JsonUtils; +import com.cf.imes.framework.security.test.WithMockLoginUser; +import com.cf.imes.framework.test.core.ut.BaseDbAndRedisUnitTest; +import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerDefectSaveReqVO; +import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerPageReqVO; +import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerRespVO; +import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerSaveReqVO; +import com.cf.imes.module.executor.dal.dataobject.veneer.VeneerDO; +import com.cf.imes.module.executor.dal.mysql.veneer.VeneerMapper; +import com.cf.imes.module.executor.enums.veneer.VeneerProductLevelEnum; +import com.cf.imes.module.infra.api.file.FileApi; +import com.cf.imes.module.system.api.organ.OrganApi; +import com.cf.imes.module.system.api.organ.dto.OrganizationDTO; +import jakarta.annotation.Resource; +import org.junit.jupiter.api.Test; +import org.springframework.context.annotation.Import; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.test.context.bean.override.mockito.MockitoBean; + +import java.math.BigDecimal; +import java.util.List; +import java.util.Map; + +import static com.cf.imes.framework.test.core.util.AssertUtils.assertServiceException; +import static com.cf.imes.framework.test.core.util.RandomUtils.randomPojo; +import static com.cf.imes.module.executor.enums.ErrorCodeConstants.VENEER_NOT_EXIST; +import static com.cf.imes.module.executor.enums.ErrorCodeConstants.VENEER_NO_EXIST; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +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.anyString; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * + * + * @author Gqr + * @since 2026/1/7 15:08 + */ +@Import(VeneerServiceImpl.class) +class VeneerServiceImplTest extends BaseDbAndRedisUnitTest { + @Resource + private VeneerServiceImpl veneerService; + + @MockitoBean + private FileApi fileApi; + + @MockitoBean + private OrganApi organApi; + + @Resource + private VeneerMapper veneerMapper; + + @Test + void create_success_when_veneer_no_not_exists() { + VeneerSaveReqVO reqVO = new VeneerSaveReqVO(); + reqVO.setVeneerNo("VN-001"); + reqVO.setClassificationName("测试分类"); + reqVO.setVeneerName("测试贴皮"); + reqVO.setProductLevel(VeneerProductLevelEnum.A.getCode()); + + Long id = veneerService.create(reqVO); + + assertNotNull(id); + + VeneerDO db = veneerMapper.selectById(id); + assertEquals("VN-001", db.getVeneerNo()); + } + + @Test + void create_fail_when_veneer_no_exists() { + // 先插一条 + VeneerDO exist = new VeneerDO(); + exist.setVeneerNo("VN-002"); + exist.setClassificationName("分类"); + exist.setVeneerName("贴皮"); + exist.setProductLevel(VeneerProductLevelEnum.A.getCode()); + veneerMapper.insert(exist); + + VeneerSaveReqVO reqVO = new VeneerSaveReqVO(); + reqVO.setVeneerNo("VN-002"); + + assertServiceException(() -> veneerService.create(reqVO), + VENEER_NO_EXIST); + } + + @Test + void update_success_when_same_veneer_no() { + VeneerDO exist = new VeneerDO(); + exist.setVeneerNo("VN-100"); + exist.setClassificationName("分类"); + exist.setVeneerName("贴皮"); + exist.setProductLevel(VeneerProductLevelEnum.A.getCode()); + veneerMapper.insert(exist); + + VeneerSaveReqVO reqVO = new VeneerSaveReqVO(); + reqVO.setId(exist.getId()); + reqVO.setVeneerNo("VN-100"); // 不变 + reqVO.setClassificationName("新分类"); + reqVO.setVeneerName("新贴皮"); + reqVO.setProductLevel(VeneerProductLevelEnum.B.getCode()); + + assertDoesNotThrow(() -> veneerService.update(reqVO)); + } + + @Test + void update_fail_when_veneer_no_exists() { + VeneerDO v1 = new VeneerDO(); + v1.setVeneerNo("VN-101"); + v1.setClassificationName("分类1"); + v1.setVeneerName("贴皮1"); + v1.setProductLevel(VeneerProductLevelEnum.A.getCode()); + veneerMapper.insert(v1); + + VeneerDO v2 = new VeneerDO(); + v2.setVeneerNo("VN-102"); + v2.setClassificationName("分类2"); + v2.setVeneerName("贴皮2"); + v2.setProductLevel(VeneerProductLevelEnum.A.getCode()); + veneerMapper.insert(v2); + + VeneerSaveReqVO reqVO = new VeneerSaveReqVO(); + reqVO.setId(v2.getId()); + reqVO.setVeneerNo("VN-101"); // 冲突 + + assertServiceException(() -> veneerService.update(reqVO), + VENEER_NO_EXIST); + } + + @Test + void update_fail_when_not_exists() { + VeneerSaveReqVO reqVO = new VeneerSaveReqVO(); + reqVO.setId(999L); + reqVO.setVeneerNo("VN-999"); + + assertServiceException(() -> veneerService.update(reqVO), + VENEER_NOT_EXIST); + } + + @Test + void update_fail_when_id_null(){ + VeneerSaveReqVO reqVO = new VeneerSaveReqVO(); + reqVO.setId(null); + assertServiceException(() -> veneerService.update(reqVO), + VENEER_NOT_EXIST); + } + + @Test + void delete_success() { + VeneerDO exist = new VeneerDO(); + exist.setVeneerNo("VN-200"); + exist.setClassificationName("分类"); + exist.setVeneerName("贴皮"); + exist.setProductLevel(VeneerProductLevelEnum.A.getCode()); + veneerMapper.insert(exist); + + assertDoesNotThrow(() -> veneerService.delete(exist.getId())); + + assertNull(veneerMapper.selectById(exist.getId())); + } + + @Test + void delete_fail_when_not_exists() { + assertServiceException(() -> veneerService.delete(999L), + VENEER_NOT_EXIST); + } + + @Test + @WithMockLoginUser(organId = 1L) + void uploadPic_success() throws Exception { + + // mock organApi + OrganizationDTO dto = new OrganizationDTO(); + dto.setName("测试组织"); + + when(organApi.getOrganDetails(1L)) + .thenReturn(CommonResult.success(dto)); + + // mock fileApi + when(fileApi.createFileAndReturnId(any(byte[].class), anyString())) + .thenReturn(100L); + + // mock 文件 + MockMultipartFile file = new MockMultipartFile( + "file", + "test.png", + "image/png", + "test-content".getBytes() + ); + + Long fileId = veneerService.uploadPic(file); + + assertEquals(100L, fileId); + + // 验证 fileApi 调用 + verify(fileApi).createFileAndReturnId( + any(byte[].class), + argThat(name -> name.contains("测试组织-veneer-pic-file-")) + ); + } + + @Test + @WithMockLoginUser(organId = 1L) + void uploadPic_fail_when_organ_api_error() { + + when(organApi.getOrganDetails(1L)) + .thenReturn(CommonResult.error(500, "组织服务异常")); + + MockMultipartFile file = new MockMultipartFile( + "file", + "test.jpg", + "image/jpeg", + "content".getBytes() + ); + + ServiceException ex = assertThrows( + ServiceException.class, + () -> veneerService.uploadPic(file) + ); + + assertEquals(500, ex.getCode()); + assertEquals("组织服务异常", ex.getMessage()); + + verify(fileApi, never()).createFileAndReturnId(any(), any()); + } + + @Test + void getPage_success_when_has_data() { + VeneerDO exist = new VeneerDO(); + exist.setVeneerNo("VN-200"); + exist.setClassificationName("分类"); + exist.setVeneerName("贴皮名称"); + exist.setProductLevel(VeneerProductLevelEnum.A.getCode()); + exist.setRemark("备注"); + exist.setWidth(new BigDecimal(10)); + exist.setThickness(new BigDecimal(9)); + veneerMapper.insert(exist); + + VeneerPageReqVO pageReqVO = new VeneerPageReqVO(); + pageReqVO.setVeneerNo("VN-200"); + pageReqVO.setClassificationName("分类"); + pageReqVO.setVeneerName("贴皮名称"); + pageReqVO.setProductLevel(VeneerProductLevelEnum.A.getCode()); + pageReqVO.setWidth(new BigDecimal[]{new BigDecimal(0), new BigDecimal(20)}); + pageReqVO.setThickness(new BigDecimal[]{new BigDecimal(5)}); + pageReqVO.setRemark("注"); + + PageResult result = + veneerService.getPage(pageReqVO); + + assertEquals(1L, result.getTotal()); + assertEquals(1, result.getList().size()); + assertEquals("VN-200", result.getList().get(0).getVeneerNo()); + } + + @Test + void getPage_empty(){ + VeneerPageReqVO reqVO = new VeneerPageReqVO(); + PageResult result = veneerService.getPage(reqVO); + + assertNotNull(result); + assertEquals(0L, result.getTotal()); + } + + @Test + void get_success_with_defect_data() { + String json = "{\"a\":1}"; + String zipped = JsonUtils.zipString(json); + + VeneerDO veneer = new VeneerDO(); + veneer.setVeneerNo("VN-301"); + veneer.setClassificationName("分类"); + veneer.setVeneerName("贴皮"); + veneer.setProductLevel(1); + veneer.setDefectData(zipped); + veneer.setOrganId(1L); + + veneerMapper.insert(veneer); + + VeneerRespVO respVO = veneerService.get(veneer.getId()); + + assertNotNull(respVO); + assertEquals("VN-301", respVO.getVeneerNo()); + assertEquals(json, respVO.getDefectData()); // 已解压 + } + + @Test + void get_success_without_defect_data() { + VeneerDO veneer = new VeneerDO(); + veneer.setVeneerNo("VN-302"); + veneer.setClassificationName("分类"); + veneer.setVeneerName("贴皮"); + veneer.setProductLevel(1); + veneer.setOrganId(1L); + + veneerMapper.insert(veneer); + + VeneerRespVO respVO = veneerService.get(veneer.getId()); + + assertNotNull(respVO); + assertNull(respVO.getDefectData()); + } + + @Test + void get_fail_when_not_exist() { + assertServiceException(() -> veneerService.get(999L), + VENEER_NOT_EXIST); + } + + @Test + void getOrgVeneerNo_empty() { + Map map = veneerService.getOrgVeneerNo(); + assertNotNull(map); + assertTrue(map.isEmpty()); + } + + @Test + void getOrgVeneerNo_success() { + VeneerDO v1 = new VeneerDO(); + v1.setVeneerNo("VN-401"); + v1.setClassificationName("分类"); + v1.setVeneerName("贴皮"); + v1.setProductLevel(VeneerProductLevelEnum.A.getCode()); + v1.setOrganId(1L); + + VeneerDO v2 = new VeneerDO(); + v2.setVeneerNo("VN-402"); + v2.setClassificationName("分类"); + v2.setVeneerName("贴皮"); + v2.setProductLevel(VeneerProductLevelEnum.C.getCode()); + v2.setOrganId(1L); + + veneerMapper.insert(v1); + veneerMapper.insert(v2); + + Map map = veneerService.getOrgVeneerNo(); + + assertEquals(2, map.size()); + assertEquals(v1.getId(), map.get("VN-401")); + assertEquals(v2.getId(), map.get("VN-402")); + } + + @Test + void batch_success() { + VeneerDO v1 = randomPojo(VeneerDO.class, o -> { + o.setProductLevel(VeneerProductLevelEnum.A.getCode()); + }); + VeneerDO v2 = randomPojo(VeneerDO.class, o -> { + o.setProductLevel(VeneerProductLevelEnum.A.getCode()); + }); + List list = List.of( + v1, v2 + ); + + veneerService.batchInsert(list); + + // 验证数据真的进库了 + List dbList = veneerMapper.selectList(); + + assertEquals(2, dbList.size()); + + v1.setVeneerName("更新后贴皮名称1"); + v2.setVeneerName("更新后贴皮名称2"); + + veneerService.batchUpdate(List.of(v1, v2)); + + VeneerDO db1 = veneerMapper.selectById(v1.getId()); + VeneerDO db2 = veneerMapper.selectById(v2.getId()); + + assertEquals("更新后贴皮名称1", db1.getVeneerName()); + assertEquals("更新后贴皮名称2", db2.getVeneerName()); + } + + @Test + void saveDefect_success_whenDefectDataNotEmpty() { + String defectJson = "{\"x\":10,\"y\":20}"; + // 先插入贴皮 + VeneerDO veneer = randomPojo(VeneerDO.class, o -> { + o.setProductLevel(VeneerProductLevelEnum.A.getCode()); + o.setDefectData(defectJson); + }); + veneerMapper.insert(veneer); + + VeneerDefectSaveReqVO req = new VeneerDefectSaveReqVO(); + req.setId(veneer.getId()); + req.setDefectData(defectJson); + + assertDoesNotThrow(() -> veneerService.saveDefect(req)); + + // 再查数据库 + VeneerDO db = veneerMapper.selectById(veneer.getId()); + + assertTrue(db.getDefect()); + assertNotNull(db.getDefectData()); + assertEquals(defectJson, JsonUtils.unzipString(db.getDefectData())); + } + + @Test + void saveDefect_throwException_whenVeneerNotExist() { + VeneerDefectSaveReqVO req = new VeneerDefectSaveReqVO(); + req.setId(99999L); + + assertServiceException(() -> veneerService.saveDefect(req), + VENEER_NOT_EXIST); + } + + @Test + void saveDefect_doNothing_whenDefectDataEmpty() { + // 先插入贴皮 + VeneerDO veneer = randomPojo(VeneerDO.class, o -> { + o.setProductLevel(VeneerProductLevelEnum.A.getCode()); + o.setDefectData(null); + o.setDefect(false); + }); + veneerMapper.insert(veneer); + + VeneerDefectSaveReqVO req = new VeneerDefectSaveReqVO(); + req.setId(veneer.getId()); + req.setDefectData(""); + + assertDoesNotThrow(() -> veneerService.saveDefect(req)); + + VeneerDO db = veneerMapper.selectById(veneer.getId()); + + assertFalse(db.getDefect()); + assertNull(db.getDefectData()); + } + + @Test + void getFileExtension_whenEmpty() { + assertEquals(".jpg", veneerService.getFileExtension("file.")); + } +} diff --git a/cf-module-prod-executor/cf-module-prod-executor-biz/src/test/resources/application-unit-test.yaml b/cf-module-prod-executor/cf-module-prod-executor-biz/src/test/resources/application-unit-test.yaml new file mode 100644 index 000000000..3409847a1 --- /dev/null +++ b/cf-module-prod-executor/cf-module-prod-executor-biz/src/test/resources/application-unit-test.yaml @@ -0,0 +1,59 @@ +spring: + main: + lazy-initialization: true # 开启懒加载,加快速度 + banner-mode: off # 单元测试,禁用 Banner + +--- #################### 数据库相关配置 #################### + +spring: + # 数据源配置项 + datasource: + name: ruoyi-vue-pro + url: jdbc:h2:mem:testdb;MODE=MYSQL;DATABASE_TO_UPPER=false;NON_KEYWORDS=value; # MODE 使用 MySQL 模式;DATABASE_TO_UPPER 配置表和字段使用小写 + driver-class-name: org.h2.Driver + username: sa + password: + druid: + async-init: true # 单元测试,异步初始化 Druid 连接池,提升启动速度 + initial-size: 1 # 单元测试,配置为 1,提升启动速度 + sql: + init: + schema-locations: classpath:/sql/create_tables.sql + + # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优 + redis: + host: 127.0.0.1 # 地址 + port: 16379 # 端口(单元测试,使用 16379 端口) + database: 0 # 数据库索引 + +mybatis: + lazy-initialization: true # 单元测试,设置 MyBatis Mapper 延迟加载,加速每个单元测试 + +mybatis-plus: + global-config: + db-config: + id-type: AUTO # H2 主键递增 + +--- #################### 定时任务相关配置 #################### + +--- #################### 配置中心相关配置 #################### + +--- #################### 服务保障相关配置 #################### + +# Lock4j 配置项(单元测试,禁用 Lock4j) + +# Resilience4j 配置项 + +--- #################### 监控相关配置 #################### + +--- #################### 晨丰相关配置 #################### + +# 晨丰配置项,设置当前项目所有自定义的配置 +chenfeng: + info: + base-package: com.cf.imes.module + captcha: + timeout: 5m + width: 160 + height: 60 + enable: true diff --git a/cf-module-prod-executor/cf-module-prod-executor-biz/src/test/resources/logback.xml b/cf-module-prod-executor/cf-module-prod-executor-biz/src/test/resources/logback.xml new file mode 100644 index 000000000..daf756bff --- /dev/null +++ b/cf-module-prod-executor/cf-module-prod-executor-biz/src/test/resources/logback.xml @@ -0,0 +1,4 @@ + + + + diff --git a/cf-module-prod-executor/cf-module-prod-executor-biz/src/test/resources/sql/clean.sql b/cf-module-prod-executor/cf-module-prod-executor-biz/src/test/resources/sql/clean.sql new file mode 100644 index 000000000..c762028cc --- /dev/null +++ b/cf-module-prod-executor/cf-module-prod-executor-biz/src/test/resources/sql/clean.sql @@ -0,0 +1 @@ +DELETE FROM "veneers"; \ No newline at end of file diff --git a/cf-module-prod-executor/cf-module-prod-executor-biz/src/test/resources/sql/create_tables.sql b/cf-module-prod-executor/cf-module-prod-executor-biz/src/test/resources/sql/create_tables.sql new file mode 100644 index 000000000..826ea1333 --- /dev/null +++ b/cf-module-prod-executor/cf-module-prod-executor-biz/src/test/resources/sql/create_tables.sql @@ -0,0 +1,34 @@ +CREATE TABLE IF NOT EXISTS "veneers" +( + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, + "veneer_no" varchar(32) NOT NULL, + "source_veneer_no" varchar(32) DEFAULT NULL, + "batch_no" varchar(32) DEFAULT NULL, + "organ_id" bigint NOT NULL DEFAULT 1, + "classification_name" varchar(64) NOT NULL, + "veneer_name" varchar(64) NOT NULL, + "product_level" tinyint NOT NULL, + "brand" varchar(64) DEFAULT NULL, + "spec" varchar(64) DEFAULT NULL, + "material" varchar(64) DEFAULT NULL, + "color" varchar(32) DEFAULT NULL, + "factory" varchar(128) DEFAULT NULL, + "num" int DEFAULT NULL, + "defect" bit NOT NULL DEFAULT FALSE, + "defect_data" clob DEFAULT NULL, + "remain" bit NOT NULL DEFAULT FALSE, + "source_plan_no" bigint DEFAULT NULL, + "plan_no" bigint DEFAULT NULL, + "status" tinyint NOT NULL DEFAULT 0, + "width" decimal(8, 3) NOT NULL DEFAULT 0.000, + "length" decimal(8, 3) NOT NULL DEFAULT 0.000, + "thickness" decimal(8, 3) NOT NULL DEFAULT 0.000, + "remark" varchar(255) DEFAULT NULL, + "file_id" bigint DEFAULT NULL, + "creator" varchar(64) DEFAULT NULL, + "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updater" varchar(64) DEFAULT NULL, + "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "deleted" bit NOT NULL DEFAULT FALSE, + PRIMARY KEY ("id") +) COMMENT '贴皮表 veneers_{N}'; \ No newline at end of file