diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/funds/products/ProductsServiceImpl.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/funds/products/ProductsServiceImpl.java index 9c6f6bf0a..6e10f3e13 100644 --- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/funds/products/ProductsServiceImpl.java +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/funds/products/ProductsServiceImpl.java @@ -11,7 +11,6 @@ import com.cf.imes.module.system.controller.admin.funds.products.vo.product.Prod import com.cf.imes.module.system.dal.dataobject.funds.products.ProductsDO; import com.cf.imes.module.system.dal.mysql.funds.products.ProductsMapper; import com.cf.imes.module.system.enums.products.ProductStatusEnum; -import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; @@ -60,9 +59,7 @@ public class ProductsServiceImpl implements ProductsService { // 校验产品是否存在 Integer status = validateProductPlateExists(updateReqVOId).getStatus(); // 校验产品名称唯一 - if (StringUtils.isNotEmpty(productName)) { - validateProductNameUnique(updateReqVOId, productName); - } + validateProductNameUnique(updateReqVOId, productName); // 当前状态是上架状态 && 目标状态不是下架 的操作提示已上架 if (ProductStatusEnum.PRODUCT_UPDATES.getStatus().equals(status) && !ProductStatusEnum.isProductDelis(updateReqVOStatus)) { diff --git a/cf-module-system/cf-module-system-biz/src/test/java/com/cf/imes/module/system/controller/admin/funds/products/ProductsControllerTest.java b/cf-module-system/cf-module-system-biz/src/test/java/com/cf/imes/module/system/controller/admin/funds/products/ProductsControllerTest.java new file mode 100644 index 000000000..221df4aa4 --- /dev/null +++ b/cf-module-system/cf-module-system-biz/src/test/java/com/cf/imes/module/system/controller/admin/funds/products/ProductsControllerTest.java @@ -0,0 +1,216 @@ +package com.cf.imes.module.system.controller.admin.funds.products; + +import com.cf.imes.framework.common.pojo.PageParam; +import com.cf.imes.framework.common.pojo.PageResult; +import com.cf.imes.framework.security.core.util.SecurityFrameworkUtils; +import com.cf.imes.module.system.controller.admin.funds.products.vo.product.ProductPageReqVO; +import com.cf.imes.module.system.controller.admin.funds.products.vo.product.ProductSaveReqVO; +import com.cf.imes.module.system.dal.dataobject.funds.products.ProductsDO; +import com.cf.imes.module.system.enums.products.ProductStatusEnum; +import com.cf.imes.module.system.service.funds.products.ProductsService; +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.math.BigDecimal; +import java.util.List; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +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.put; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * @author Gqr + * @since 2025/10/14 11:16 + */ +@WebMvcTest(controllers = ProductsController.class, excludeAutoConfiguration = SecurityAutoConfiguration.class) +public class ProductsControllerTest { + @Autowired + private MockMvc mockMvc; + + @MockitoBean + private ProductsService productsService; + + @Autowired + private ObjectMapper objectMapper; + + @Test + void testCreateProducts() throws Exception { + ProductSaveReqVO vo = new ProductSaveReqVO(); + vo.setProductName("新建产品"); + vo.setPrice(new BigDecimal("199")); + vo.setStatus(ProductStatusEnum.PRODUCT_DELIS.getStatus()); + + // mock Service 返回 + when(productsService.create(any())).thenReturn(100L); + + mockMvc.perform(put("/system/products") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(vo))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data").value(100)); + + // 验证 Service 调用 + verify(productsService, times(1)).create(any()); + } + + @Test + void testUpdateProducts() throws Exception { + ProductSaveReqVO vo = new ProductSaveReqVO(); + vo.setId(1L); + vo.setPrice(new BigDecimal("199")); + vo.setProductName("更新名称"); + + // mock Service 方法 void 不做实际处理 + doNothing().when(productsService).update(any()); + + mockMvc.perform(post("/system/products") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(vo))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data").value(true)); + + verify(productsService, times(1)).update(any()); + } + + @Test + void testFreezeProducts() throws Exception { + Long id = 1L; + doNothing().when(productsService).update(any()); + + mockMvc.perform(post("/system/products/freeze") + .param("id", id.toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data").value(true)); + + verify(productsService, times(1)).update(argThat(vo -> vo.getId().equals(id) + && vo.getStatus().equals(ProductStatusEnum.PRODUCT_DELIS.getStatus()))); + } + + @Test + void testMeltProducts() throws Exception { + Long id = 2L; + doNothing().when(productsService).update(any()); + + mockMvc.perform(post("/system/products/melt") + .param("id", id.toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data").value(true)); + + verify(productsService, times(1)).update(argThat(vo -> vo.getId().equals(id) + && vo.getStatus().equals(ProductStatusEnum.PRODUCT_UPDATES.getStatus()))); + } + + @Test + void testDeleteProducts() throws Exception { + Long id = 3L; + doNothing().when(productsService).delete(anyLong()); + + mockMvc.perform(delete("/system/products") + .param("id", id.toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data").value(true)); + + verify(productsService, times(1)).delete(id); + } + + @Test + void testGetProducts() throws Exception { + Long id = 4L; + ProductsDO mockProduct = new ProductsDO() + .setId(id) + .setProductName("测试产品"); + when(productsService.get(id)).thenReturn(mockProduct); + + mockMvc.perform(get("/system/products/{id}", id)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data.productName").value("测试产品")); + + verify(productsService, times(1)).get(id); + } + + @Test + void testGetProductsPage() throws Exception { + ProductPageReqVO reqVO = new ProductPageReqVO(); + PageResult mockPage = new PageResult<>(); + mockPage.setTotal(1L); + mockPage.setList(List.of(new ProductsDO().setId(1L).setProductName("分页产品"))); + when(productsService.getPage(any())).thenReturn(mockPage); + + mockMvc.perform(get("/system/products/page") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(reqVO))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data.list[0].productName").value("分页产品")); + + verify(productsService, times(1)).getPage(any()); + } + + @Test + void testGetAvailableProductsPage() throws Exception { + ProductPageReqVO reqVO = new ProductPageReqVO(); + PageResult mockPage = new PageResult<>(); + mockPage.setTotal(1L); + mockPage.setList(List.of(new ProductsDO().setId(1L).setProductName("已上架产品"))); + when(productsService.getPage(any())).thenReturn(mockPage); + + // mock 管理端标识 + try (var ignored = mockStatic(SecurityFrameworkUtils.class)) { + when(SecurityFrameworkUtils.isManageEndPoint()).thenReturn(true); + + mockMvc.perform(get("/system/products/available") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(reqVO))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data.list[0].productName").value("已上架产品")); + + verify(productsService, times(1)).getPage(any()); + } + } + + @Test + void testGetAvailableProductsPage_nonManager() throws Exception { + ProductPageReqVO reqVO = new ProductPageReqVO(); + + // 准备 mock 分页数据 + PageResult mockPage = new PageResult<>(); + mockPage.setTotal(1L); + mockPage.setList(List.of(new ProductsDO().setId(123L).setProductName("非管理端产品"))); + when(productsService.getPage(any())).thenReturn(mockPage); + + // mock SecurityFrameworkUtils 的静态方法 + try (var securityMock = mockStatic(SecurityFrameworkUtils.class)) { + // 返回非管理端 + securityMock.when(SecurityFrameworkUtils::isManageEndPoint).thenReturn(false); + // 返回登录的产品 ID + securityMock.when(SecurityFrameworkUtils::getProductId).thenReturn(123L); + + mockMvc.perform(get("/system/products/available") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(reqVO))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data.list[0].productName").value("非管理端产品")); + + // 验证 Service 调用时 pageReqVO 已经被设置 ID + verify(productsService, times(1)).getPage(argThat(vo -> vo.getId().equals(123L) + && vo.getStatus().equals(ProductStatusEnum.PRODUCT_UPDATES.getStatus()) + && vo.getPageSize().equals(PageParam.PAGE_SIZE_NONE))); + } + } +} diff --git a/cf-module-system/cf-module-system-biz/src/test/java/com/cf/imes/module/system/service/funds/products/ProductsServiceImplTest.java b/cf-module-system/cf-module-system-biz/src/test/java/com/cf/imes/module/system/service/funds/products/ProductsServiceImplTest.java new file mode 100644 index 000000000..469395d02 --- /dev/null +++ b/cf-module-system/cf-module-system-biz/src/test/java/com/cf/imes/module/system/service/funds/products/ProductsServiceImplTest.java @@ -0,0 +1,235 @@ +package com.cf.imes.module.system.service.funds.products; + +import com.cf.imes.framework.common.pojo.PageResult; +import com.cf.imes.framework.test.core.ut.BaseDbAndRedisUnitTest; +import com.cf.imes.module.system.controller.admin.funds.products.vo.product.ProductPageReqVO; +import com.cf.imes.module.system.controller.admin.funds.products.vo.product.ProductSaveReqVO; +import com.cf.imes.module.system.dal.dataobject.funds.products.ProductsDO; +import com.cf.imes.module.system.dal.mysql.funds.products.ProductsMapper; +import com.cf.imes.module.system.enums.products.ProductStatusEnum; +import jakarta.annotation.Resource; +import org.junit.jupiter.api.Test; +import org.springframework.context.annotation.Import; + +import java.math.BigDecimal; + +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.randomLongId; +import static com.cf.imes.framework.test.core.util.RandomUtils.randomPojo; +import static com.cf.imes.framework.test.core.util.RandomUtils.randomString; +import static com.cf.imes.module.system.enums.ErrorCodeConstants.PRODUCTS_IS_EXIST; +import static com.cf.imes.module.system.enums.ErrorCodeConstants.PRODUCTS_NO_DELETE; +import static com.cf.imes.module.system.enums.ErrorCodeConstants.PRODUCTS_NO_EXIST; +import static com.cf.imes.module.system.enums.ErrorCodeConstants.PRODUCTS_NO_UPDATE; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +/** + * @author Gqr + * @since 2025/10/14 9:46 + */ +@Import(ProductsServiceImpl.class) +public class ProductsServiceImplTest extends BaseDbAndRedisUnitTest { + @Resource + private ProductsServiceImpl productsService; + + @Resource + private ProductsMapper productsMapper; + + @Test + void testCreateProductSuccess() { + // 构造请求对象 + String productName = randomString(); + ProductSaveReqVO req = new ProductSaveReqVO(); + req.setProductName(productName); + req.setPrice(new BigDecimal("88.88")); + req.setStatus(ProductStatusEnum.PRODUCT_UPDATES.getStatus()); + + // 调用真实 Service 方法 + Long id = productsService.create(req); + + // 校验数据库插入结果 + ProductsDO result = productsMapper.selectById(id); + assertNotNull(result); + assertEquals(productName, result.getProductName()); + assertEquals(new BigDecimal("88.88"), result.getPrice()); + assertEquals(ProductStatusEnum.PRODUCT_UPDATES.getStatus(), result.getStatus()); + } + + /** + * 下架更新为上架 + */ + @Test + void testUpdate_takeoff_to_listed() { + ProductSaveReqVO createVO = new ProductSaveReqVO(); + String productName = randomString(); + createVO.setProductName(productName); + createVO.setPrice(new BigDecimal("88.88")); + createVO.setStatus(ProductStatusEnum.PRODUCT_DELIS.getStatus()); + Long id = productsService.create(createVO); + + ProductSaveReqVO updateVO = new ProductSaveReqVO(); + updateVO.setId(id); + String newProductName = randomString(); + updateVO.setProductName(newProductName); + updateVO.setStatus(ProductStatusEnum.PRODUCT_UPDATES.getStatus()); + updateVO.setPrice(new BigDecimal("199.99")); + + productsService.update(updateVO); + + ProductsDO db = productsMapper.selectById(id); + assertThat(db.getProductName()).isEqualTo(newProductName); + assertThat(db.getPrice()).isEqualByComparingTo("199.99"); + assertEquals(ProductStatusEnum.PRODUCT_UPDATES.getStatus(), db.getStatus()); + } + + /** + * 已上架更新 + */ + @Test + void testUpdate_statusUpdatableThrowsException() { + ProductSaveReqVO dbProduct = new ProductSaveReqVO(); + dbProduct.setProductName("产品"); + dbProduct.setStatus(ProductStatusEnum.PRODUCT_UPDATES.getStatus()); + Long id = productsService.create(dbProduct); + + ProductSaveReqVO reqVO = new ProductSaveReqVO(); + reqVO.setId(id); + reqVO.setStatus(ProductStatusEnum.PRODUCT_UPDATES.getStatus()); // 非下架 + + assertServiceException(() -> productsService.update(reqVO), + PRODUCTS_NO_UPDATE); + } + + /** + * 已上架更新下架 + */ + @Test + void testUpdate_listed_to_takeoff() { + ProductSaveReqVO createVO = new ProductSaveReqVO(); + String productName = randomString(); + createVO.setProductName(productName); + createVO.setPrice(new BigDecimal("88.88")); + createVO.setStatus(ProductStatusEnum.PRODUCT_UPDATES.getStatus()); + Long id = productsService.create(createVO); + + ProductSaveReqVO updateVO = new ProductSaveReqVO(); + updateVO.setId(id); + String newProductName = randomString(); + updateVO.setProductName(newProductName); + updateVO.setStatus(ProductStatusEnum.PRODUCT_DELIS.getStatus()); + updateVO.setPrice(new BigDecimal("199.99")); + + productsService.update(updateVO); + + ProductsDO db = productsMapper.selectById(id); + assertThat(db.getProductName()).isEqualTo(newProductName); + assertThat(db.getPrice()).isEqualByComparingTo("199.99"); + assertEquals(ProductStatusEnum.PRODUCT_DELIS.getStatus(), db.getStatus()); + } + + + @Test + void testDelete() { + ProductSaveReqVO createVO = new ProductSaveReqVO(); + createVO.setProductName(randomString()); + createVO.setPrice(new BigDecimal("88.88")); + createVO.setStatus(ProductStatusEnum.PRODUCT_DELIS.getStatus()); + Long id = productsService.create(createVO); + + productsService.delete(id); + // 断言不存在了 + assertNull(productsMapper.selectById(id)); + } + + @Test + void testDelete_listed() { + ProductSaveReqVO createVO = new ProductSaveReqVO(); + createVO.setProductName(randomString()); + createVO.setPrice(new BigDecimal("88.88")); + createVO.setStatus(ProductStatusEnum.PRODUCT_UPDATES.getStatus()); + Long id = productsService.create(createVO); + + assertServiceException(() -> productsService.delete(id), + PRODUCTS_NO_DELETE); + } + + @Test + void testGet() { + ProductSaveReqVO createVO = new ProductSaveReqVO(); + String productName = randomString(); + createVO.setProductName(productName); + createVO.setPrice(new BigDecimal("88.88")); + createVO.setStatus(1); + Long id = productsService.create(createVO); + ProductsDO db = productsService.get(id); + + assertThat(db).isNotNull(); + assertThat(db.getProductName()).isEqualTo(productName); + } + + @Test + void testGetProductPage() { + // ============ mock 数据 ============ + ProductsDO dbProduct = randomPojo(ProductsDO.class, o -> { // 这个会被查到 + o.setProductName("晨丰-软件"); + o.setStatus(ProductStatusEnum.PRODUCT_UPDATES.getStatus()); // 假设0=上架 + o.setDeleted(false); + o.setPrice(new BigDecimal("99.99")); + }); + productsMapper.insert(dbProduct); + + // 不匹配 productName + productsMapper.insert(cloneIgnoreId(dbProduct, o -> o.setProductName("测试产品"))); + + // 不匹配 status + productsMapper.insert(cloneIgnoreId(dbProduct, o -> o.setStatus(ProductStatusEnum.PRODUCT_DELIS.getStatus()))); + + // 被逻辑删除 + productsMapper.insert(cloneIgnoreId(dbProduct, o -> o.setDeleted(true))); + + // ============ 构造查询参数 ============ + ProductPageReqVO reqVO = new ProductPageReqVO(); + reqVO.setProductName("晨丰"); + reqVO.setStatus(ProductStatusEnum.PRODUCT_UPDATES.getStatus()); + reqVO.setPageNo(1); + reqVO.setPageSize(10); + + // ============ 调用方法 ============ + PageResult pageResult = productsService.getPage(reqVO); + + // ============ 断言结果 ============ + assertEquals(1, pageResult.getTotal(), "分页结果数量应为1"); + assertEquals(1, pageResult.getList().size(), "分页列表长度应为1"); + assertPojoEquals(dbProduct, pageResult.getList().get(0)); + } + + + @Test + void testDuplicateNameThrowsException() { + ProductSaveReqVO createVO = new ProductSaveReqVO(); + String productName = randomString(); + createVO.setProductName(productName); + createVO.setPrice(new BigDecimal("88.88")); + createVO.setStatus(1); + productsService.create(createVO); + + ProductSaveReqVO dup = new ProductSaveReqVO(); + dup.setProductName(productName); + dup.setStatus(1); + dup.setPrice(new BigDecimal("50.00")); + + assertServiceException(() -> productsService.create(dup), + PRODUCTS_IS_EXIST, productName); + } + + @Test + void testGetNotExist() { + assertServiceException(() -> productsService.get(randomLongId()), + PRODUCTS_NO_EXIST); + } +} diff --git a/cf-module-system/cf-module-system-biz/src/test/resources/sql/create_tables.sql b/cf-module-system/cf-module-system-biz/src/test/resources/sql/create_tables.sql index 90d639330..fdef1cf76 100644 --- a/cf-module-system/cf-module-system-biz/src/test/resources/sql/create_tables.sql +++ b/cf-module-system/cf-module-system-biz/src/test/resources/sql/create_tables.sql @@ -683,4 +683,19 @@ CREATE TABLE IF NOT EXISTS "process_group" ( "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, "deleted" bit NOT NULL DEFAULT FALSE, PRIMARY KEY ("id") - ) COMMENT '工序组表 process_group'; \ No newline at end of file + ) COMMENT '工序组表 process_group'; + +CREATE TABLE IF NOT EXISTS "products" ( + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, + "product_name" varchar(64) NOT NULL, + "status" tinyint DEFAULT 1 NOT NULL, + "software_id" bigint NOT NULL, + "price" decimal(8,2), + "description" varchar, + "creator" varchar DEFAULT '', + "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updater" varchar DEFAULT '', + "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "deleted" bit NOT NULL DEFAULT FALSE, + PRIMARY KEY ("id") + ) COMMENT '产品表';