Commit 9d5d8c9527ca81c1a41633565d7cb62ee55d92c2

Authored by YunaiV
1 parent 6ed09714

fix(product): 修正虚拟评价创建契约并启用商品评价单测

yudao-module-mall/yudao-module-product/src/main/java/cn/iocoder/yudao/module/product/controller/admin/comment/vo/ProductCommentCreateReqVO.java
@@ -11,12 +11,6 @@ import java.util.List; @@ -11,12 +11,6 @@ import java.util.List;
11 @Data 11 @Data
12 public class ProductCommentCreateReqVO { 12 public class ProductCommentCreateReqVO {
13 13
14 - @Schema(description = "评价人", requiredMode = Schema.RequiredMode.REQUIRED, example = "16868")  
15 - private Long userId;  
16 -  
17 - @Schema(description = "评价订单项", requiredMode = Schema.RequiredMode.REQUIRED, example = "19292")  
18 - private Long orderItemId;  
19 -  
20 @Schema(description = "评价人名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "小姑凉") 14 @Schema(description = "评价人名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "小姑凉")
21 @NotNull(message = "评价人名称不能为空") 15 @NotNull(message = "评价人名称不能为空")
22 private String userNickname; 16 private String userNickname;
yudao-module-mall/yudao-module-product/src/main/java/cn/iocoder/yudao/module/product/convert/comment/ProductCommentConvert.java
@@ -40,6 +40,7 @@ public interface ProductCommentConvert { @@ -40,6 +40,7 @@ public interface ProductCommentConvert {
40 } 40 }
41 41
42 default ProductCommentDO convert(ProductCommentCreateReqVO createReq, ProductSpuDO spu, ProductSkuDO sku) { 42 default ProductCommentDO convert(ProductCommentCreateReqVO createReq, ProductSpuDO spu, ProductSkuDO sku) {
  43 + // 管理后台添加的是虚拟评价,不关联会员和订单,userId 固定为 0:https://github.com/YunaiV/ruoyi-vue-pro/issues/1277
43 ProductCommentDO comment = BeanUtils.toBean(createReq, ProductCommentDO.class) 44 ProductCommentDO comment = BeanUtils.toBean(createReq, ProductCommentDO.class)
44 .setVisible(true).setUserId(0L).setAnonymous(false) 45 .setVisible(true).setUserId(0L).setAnonymous(false)
45 .setScores(convertScores(createReq.getDescriptionScores(), createReq.getBenefitScores())); 46 .setScores(convertScores(createReq.getDescriptionScores(), createReq.getBenefitScores()));
yudao-module-mall/yudao-module-product/src/test/java/cn/iocoder/yudao/module/product/service/comment/ProductCommentServiceImplTest.java
1 package cn.iocoder.yudao.module.product.service.comment; 1 package cn.iocoder.yudao.module.product.service.comment;
2 2
3 -import cn.hutool.core.date.DateUtil;  
4 -import cn.hutool.core.util.RandomUtil;  
5 import cn.iocoder.yudao.framework.common.pojo.PageResult; 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
6 -import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 4 +import cn.iocoder.yudao.framework.common.util.collection.ArrayUtils;
7 import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; 5 import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
  6 +import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
  7 +import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
  8 +import cn.iocoder.yudao.module.product.api.comment.dto.ProductCommentCreateReqDTO;
  9 +import cn.iocoder.yudao.module.product.controller.admin.comment.vo.ProductCommentCreateReqVO;
8 import cn.iocoder.yudao.module.product.controller.admin.comment.vo.ProductCommentPageReqVO; 10 import cn.iocoder.yudao.module.product.controller.admin.comment.vo.ProductCommentPageReqVO;
9 import cn.iocoder.yudao.module.product.controller.admin.comment.vo.ProductCommentReplyReqVO; 11 import cn.iocoder.yudao.module.product.controller.admin.comment.vo.ProductCommentReplyReqVO;
10 -import cn.iocoder.yudao.module.product.controller.admin.comment.vo.ProductCommentRespVO;  
11 import cn.iocoder.yudao.module.product.controller.admin.comment.vo.ProductCommentUpdateVisibleReqVO; 12 import cn.iocoder.yudao.module.product.controller.admin.comment.vo.ProductCommentUpdateVisibleReqVO;
12 import cn.iocoder.yudao.module.product.controller.app.comment.vo.AppCommentPageReqVO; 13 import cn.iocoder.yudao.module.product.controller.app.comment.vo.AppCommentPageReqVO;
13 import cn.iocoder.yudao.module.product.dal.dataobject.comment.ProductCommentDO; 14 import cn.iocoder.yudao.module.product.dal.dataobject.comment.ProductCommentDO;
  15 +import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO;
  16 +import cn.iocoder.yudao.module.product.dal.dataobject.spu.ProductSpuDO;
14 import cn.iocoder.yudao.module.product.dal.mysql.comment.ProductCommentMapper; 17 import cn.iocoder.yudao.module.product.dal.mysql.comment.ProductCommentMapper;
15 import cn.iocoder.yudao.module.product.enums.comment.ProductCommentScoresEnum; 18 import cn.iocoder.yudao.module.product.enums.comment.ProductCommentScoresEnum;
16 import cn.iocoder.yudao.module.product.service.sku.ProductSkuService; 19 import cn.iocoder.yudao.module.product.service.sku.ProductSkuService;
17 import cn.iocoder.yudao.module.product.service.spu.ProductSpuService; 20 import cn.iocoder.yudao.module.product.service.spu.ProductSpuService;
18 import jakarta.annotation.Resource; 21 import jakarta.annotation.Resource;
19 -import org.junit.jupiter.api.Disabled;  
20 import org.junit.jupiter.api.Test; 22 import org.junit.jupiter.api.Test;
21 import org.springframework.context.annotation.Import; 23 import org.springframework.context.annotation.Import;
22 -import org.springframework.context.annotation.Lazy; 24 +import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
23 import org.springframework.test.context.bean.override.mockito.MockitoBean; 25 import org.springframework.test.context.bean.override.mockito.MockitoBean;
24 26
25 -import java.time.LocalDateTime;  
26 -import java.util.Date; 27 +import java.util.List;
  28 +import java.util.function.Consumer;
27 29
  30 +import static cn.hutool.core.util.RandomUtil.randomEle;
  31 +import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime;
  32 +import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildTime;
28 import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId; 33 import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
29 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; 34 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
30 -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;  
31 -import static org.junit.jupiter.api.Assertions.assertEquals;  
32 -import static org.junit.jupiter.api.Assertions.assertFalse; 35 +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
  36 +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
  37 +import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.*;
  38 +import static java.util.Collections.singletonList;
  39 +import static org.junit.jupiter.api.Assertions.*;
  40 +import static org.mockito.ArgumentMatchers.any;
  41 +import static org.mockito.ArgumentMatchers.eq;
  42 +import static org.mockito.Mockito.*;
33 43
34 // TODO 芋艿:单测详细 review 下 44 // TODO 芋艿:单测详细 review 下
35 /** 45 /**
@@ -37,159 +47,328 @@ import static org.junit.jupiter.api.Assertions.assertFalse; @@ -37,159 +47,328 @@ import static org.junit.jupiter.api.Assertions.assertFalse;
37 * 47 *
38 * @author wangzhs 48 * @author wangzhs
39 */ 49 */
40 -@Disabled // TODO 芋艿:后续 fix 补充的单测 50 +// DONE 芋艿:后续 fix 补充的单测
41 @Import(ProductCommentServiceImpl.class) 51 @Import(ProductCommentServiceImpl.class)
42 public class ProductCommentServiceImplTest extends BaseDbUnitTest { 52 public class ProductCommentServiceImplTest extends BaseDbUnitTest {
43 53
44 @Resource 54 @Resource
45 - private ProductCommentMapper productCommentMapper; 55 + private ProductCommentServiceImpl productCommentService;
46 56
47 @Resource 57 @Resource
48 - @Lazy  
49 - private ProductCommentServiceImpl productCommentService; 58 + private ProductCommentMapper productCommentMapper;
50 59
51 @MockitoBean 60 @MockitoBean
52 private ProductSpuService productSpuService; 61 private ProductSpuService productSpuService;
53 @MockitoBean 62 @MockitoBean
54 private ProductSkuService productSkuService; 63 private ProductSkuService productSkuService;
  64 + @MockitoBean
  65 + private MemberUserApi memberUserApi;
  66 +
  67 + // ========== 管理后台添加虚拟评价 ==========
  68 +
  69 + @Test
  70 + public void testCreateComment_success() {
  71 + // 准备参数
  72 + ProductCommentCreateReqVO reqVO = randomPojo(ProductCommentCreateReqVO.class, o -> o
  73 + .setDescriptionScores(ProductCommentScoresEnum.FIVE.getScores())
  74 + .setBenefitScores(ProductCommentScoresEnum.FOUR.getScores()));
  75 + // mock 方法
  76 + ProductSkuDO sku = mockSku(reqVO.getSkuId());
  77 + ProductSpuDO spu = mockSpu(sku.getSpuId());
55 78
56 - public String generateNo() {  
57 - return DateUtil.format(new Date(), "yyyyMMddHHmmss") + RandomUtil.randomInt(100000, 999999); 79 + // 调用
  80 + productCommentService.createComment(reqVO);
  81 + // 断言:虚拟评价不关联会员和订单
  82 + List<ProductCommentDO> comments = productCommentMapper.selectList();
  83 + assertEquals(1, comments.size());
  84 + ProductCommentDO comment = comments.get(0);
  85 + assertPojoEquals(reqVO, comment);
  86 + assertEquals(0L, comment.getUserId());
  87 + assertNull(comment.getOrderId());
  88 + assertNull(comment.getOrderItemId());
  89 + assertTrue(comment.getVisible());
  90 + assertFalse(comment.getAnonymous());
  91 + assertEquals(4, comment.getScores()); // (5 + 4) / 2 向下取整
  92 + // 断言:商品信息
  93 + assertEquals(spu.getId(), comment.getSpuId());
  94 + assertEquals(spu.getName(), comment.getSpuName());
  95 + assertEquals(sku.getPicUrl(), comment.getSkuPicUrl());
  96 + assertEquals(sku.getProperties(), comment.getSkuProperties());
  97 + }
  98 +
  99 + @Test
  100 + public void testCreateComment_ignoreUserIdAndOrderItemId() throws Exception {
  101 + // 准备参数:旧调用方仍传入 userId、orderItemId,使用 Spring MVC 默认的 Jackson2ObjectMapperBuilder 反序列化
  102 + String json = "{\"userId\":968,\"orderItemId\":1024,\"skuId\":1,\"userNickname\":\"芋艿\","
  103 + + "\"userAvatar\":\"https://www.iocoder.cn/1.png\",\"descriptionScores\":5,\"benefitScores\":5,"
  104 + + "\"content\":\"好评\",\"picUrls\":[]}";
  105 + ProductCommentCreateReqVO reqVO = Jackson2ObjectMapperBuilder.json().build()
  106 + .readValue(json, ProductCommentCreateReqVO.class);
  107 + // mock 方法
  108 + mockSpu(mockSku(reqVO.getSkuId()).getSpuId());
  109 +
  110 + // 调用
  111 + productCommentService.createComment(reqVO);
  112 + // 断言
  113 + ProductCommentDO comment = productCommentMapper.selectOne(ProductCommentDO::getSkuId, 1L);
  114 + assertEquals("芋艿", comment.getUserNickname());
  115 + assertEquals(0L, comment.getUserId());
  116 + assertNull(comment.getOrderItemId());
58 } 117 }
59 118
60 - public Long generateId() {  
61 - return RandomUtil.randomLong(100000, 999999); 119 + @Test
  120 + public void testCreateComment_skuNotExists() {
  121 + // 准备参数
  122 + ProductCommentCreateReqVO reqVO = randomPojo(ProductCommentCreateReqVO.class);
  123 +
  124 + // 调用,并断言异常
  125 + assertServiceException(() -> productCommentService.createComment(reqVO), SKU_NOT_EXISTS);
  126 + assertEquals(0L, productCommentMapper.selectCount());
62 } 127 }
63 128
  129 + // ========== 会员评价订单项 ==========
  130 +
64 @Test 131 @Test
65 - public void testCreateCommentAndGet_success() {  
66 - // mock 测试  
67 - ProductCommentDO productComment = randomPojo(ProductCommentDO.class);  
68 - productCommentMapper.insert(productComment); 132 + public void testCreateCommentByMember_success() {
  133 + // 准备参数
  134 + ProductCommentCreateReqDTO reqDTO = randomPojo(ProductCommentCreateReqDTO.class, o -> o
  135 + .setDescriptionScores(ProductCommentScoresEnum.THREE.getScores())
  136 + .setBenefitScores(ProductCommentScoresEnum.FOUR.getScores()));
  137 + // mock 数据:同一 SKU 已有虚拟评价,不影响会员评价
  138 + productCommentMapper.insert(randomCommentDO(o -> o.setUserId(0L).setOrderId(null).setOrderItemId(null)
  139 + .setSkuId(reqDTO.getSkuId())));
  140 + // mock 方法
  141 + ProductSkuDO sku = mockSku(reqDTO.getSkuId());
  142 + ProductSpuDO spu = mockSpu(sku.getSpuId());
  143 + MemberUserRespDTO user = randomPojo(MemberUserRespDTO.class, o -> o.setId(reqDTO.getUserId()));
  144 + when(memberUserApi.getUser(eq(reqDTO.getUserId()))).thenReturn(user);
69 145
  146 + // 调用
  147 + Long commentId = productCommentService.createComment(reqDTO);
70 // 断言 148 // 断言
71 - // 校验记录的属性是否正确  
72 - ProductCommentDO comment = productCommentMapper.selectById(productComment.getId());  
73 - assertPojoEquals(productComment, comment); 149 + ProductCommentDO comment = productCommentMapper.selectById(commentId);
  150 + assertPojoEquals(reqDTO, comment);
  151 + assertEquals(user.getNickname(), comment.getUserNickname());
  152 + assertEquals(user.getAvatar(), comment.getUserAvatar());
  153 + assertEquals(3, comment.getScores()); // (3 + 4) / 2 向下取整
  154 + assertEquals(spu.getId(), comment.getSpuId());
  155 + assertEquals(spu.getName(), comment.getSpuName());
  156 + assertEquals(sku.getPicUrl(), comment.getSkuPicUrl());
  157 + assertEquals(sku.getProperties(), comment.getSkuProperties());
74 } 158 }
75 159
76 @Test 160 @Test
77 - public void testGetCommentPage_success() { 161 + public void testCreateCommentByMember_orderExists() {
  162 + // mock 数据:该会员已评价该订单项
  163 + ProductCommentDO dbComment = randomCommentDO();
  164 + productCommentMapper.insert(dbComment);
78 // 准备参数 165 // 准备参数
79 - ProductCommentDO productComment = randomPojo(ProductCommentDO.class, o -> { 166 + ProductCommentCreateReqDTO reqDTO = randomPojo(ProductCommentCreateReqDTO.class, o -> o
  167 + .setUserId(dbComment.getUserId()).setOrderItemId(dbComment.getOrderItemId()));
  168 + // mock 方法
  169 + mockSpu(mockSku(reqDTO.getSkuId()).getSpuId());
  170 +
  171 + // 调用,并断言异常
  172 + assertServiceException(() -> productCommentService.createComment(reqDTO), COMMENT_ORDER_EXISTS);
  173 + assertEquals(1L, productCommentMapper.selectCount());
  174 + verify(memberUserApi, never()).getUser(any());
  175 + }
  176 +
  177 + // ========== 查询评价 ==========
  178 +
  179 + @Test
  180 + public void testGetComment_success() {
  181 + // mock 数据
  182 + ProductCommentDO dbComment = randomCommentDO();
  183 + productCommentMapper.insert(dbComment);
  184 +
  185 + // 调用
  186 + ProductCommentDO comment = productCommentService.getComment(dbComment.getId());
  187 + // 断言
  188 + assertPojoEquals(dbComment, comment);
  189 + }
  190 +
  191 + @Test
  192 + public void testGetComment_notExists() {
  193 + // 调用,并断言异常
  194 + assertServiceException(() -> productCommentService.getComment(randomLongId()), COMMENT_NOT_EXISTS);
  195 + }
  196 +
  197 + @Test
  198 + public void testGetCommentPage_success() {
  199 + // mock 数据
  200 + ProductCommentDO dbComment = randomCommentDO(o -> { // 等会查询到
80 o.setUserNickname("王二狗"); 201 o.setUserNickname("王二狗");
  202 + o.setOrderId(1L);
  203 + o.setSpuId(2L);
81 o.setSpuName("感冒药"); 204 o.setSpuName("感冒药");
82 o.setScores(ProductCommentScoresEnum.FOUR.getScores()); 205 o.setScores(ProductCommentScoresEnum.FOUR.getScores());
83 o.setReplyStatus(Boolean.TRUE); 206 o.setReplyStatus(Boolean.TRUE);
84 - o.setVisible(Boolean.TRUE);  
85 - o.setId(generateId());  
86 - o.setUserId(generateId());  
87 - o.setAnonymous(Boolean.TRUE);  
88 - o.setOrderId(generateId());  
89 - o.setOrderItemId(generateId());  
90 - o.setSpuId(generateId());  
91 - o.setSkuId(generateId());  
92 - o.setDescriptionScores(ProductCommentScoresEnum.FOUR.getScores());  
93 - o.setBenefitScores(ProductCommentScoresEnum.FOUR.getScores());  
94 - o.setContent("真好吃");  
95 - o.setReplyUserId(generateId());  
96 - o.setReplyContent("确实");  
97 - o.setReplyTime(LocalDateTime.now());  
98 - o.setCreateTime(LocalDateTime.now());  
99 - o.setUpdateTime(LocalDateTime.now()); 207 + o.setCreateTime(buildTime(2023, 2, 1));
  208 + o.setSkuProperties(null); // ProductSkuDO.Property 不可序列化,cloneIgnoreId 无法深拷贝
100 }); 209 });
101 - productCommentMapper.insert(productComment);  
102 -  
103 - Long orderId = productComment.getOrderId();  
104 - Long spuId = productComment.getSpuId();  
105 - 210 + productCommentMapper.insert(dbComment);
106 // 测试 userNickname 不匹配 211 // 测试 userNickname 不匹配
107 - productCommentMapper.insert(cloneIgnoreId(productComment, o -> o.setUserNickname("王三").setScores(ProductCommentScoresEnum.ONE.getScores()))); 212 + productCommentMapper.insert(cloneIgnoreId(dbComment, o -> o.setUserNickname("王三")));
108 // 测试 orderId 不匹配 213 // 测试 orderId 不匹配
109 - productCommentMapper.insert(cloneIgnoreId(productComment, o -> o.setOrderId(generateId()))); 214 + productCommentMapper.insert(cloneIgnoreId(dbComment, o -> o.setOrderId(10L)));
110 // 测试 spuId 不匹配 215 // 测试 spuId 不匹配
111 - productCommentMapper.insert(cloneIgnoreId(productComment, o -> o.setSpuId(generateId()))); 216 + productCommentMapper.insert(cloneIgnoreId(dbComment, o -> o.setSpuId(20L)));
112 // 测试 spuName 不匹配 217 // 测试 spuName 不匹配
113 - productCommentMapper.insert(cloneIgnoreId(productComment, o -> o.setSpuName("感康"))); 218 + productCommentMapper.insert(cloneIgnoreId(dbComment, o -> o.setSpuName("感康")));
114 // 测试 scores 不匹配 219 // 测试 scores 不匹配
115 - productCommentMapper.insert(cloneIgnoreId(productComment, o -> o.setScores(ProductCommentScoresEnum.ONE.getScores())));  
116 - // 测试 replied 不匹配  
117 - productCommentMapper.insert(cloneIgnoreId(productComment, o -> o.setReplyStatus(Boolean.FALSE)));  
118 - // 测试 visible 不匹配  
119 - productCommentMapper.insert(cloneIgnoreId(productComment, o -> o.setVisible(Boolean.FALSE))); 220 + productCommentMapper.insert(cloneIgnoreId(dbComment, o -> o.setScores(ProductCommentScoresEnum.ONE.getScores())));
  221 + // 测试 replyStatus 不匹配
  222 + productCommentMapper.insert(cloneIgnoreId(dbComment, o -> o.setReplyStatus(Boolean.FALSE)));
  223 + // 测试 createTime 不匹配
  224 + productCommentMapper.insert(cloneIgnoreId(dbComment, o -> o.setCreateTime(buildTime(2023, 3, 1))));
  225 + // 准备参数
  226 + ProductCommentPageReqVO reqVO = new ProductCommentPageReqVO();
  227 + reqVO.setUserNickname("王二");
  228 + reqVO.setOrderId(1L);
  229 + reqVO.setSpuId(2L);
  230 + reqVO.setSpuName("感冒");
  231 + reqVO.setScores(ProductCommentScoresEnum.FOUR.getScores());
  232 + reqVO.setReplyStatus(Boolean.TRUE);
  233 + reqVO.setCreateTime(buildBetweenTime(2023, 1, 1, 2023, 2, 28));
120 234
121 // 调用 235 // 调用
122 - ProductCommentPageReqVO productCommentPageReqVO = new ProductCommentPageReqVO();  
123 - productCommentPageReqVO.setUserNickname("王二");  
124 - productCommentPageReqVO.setOrderId(orderId);  
125 - productCommentPageReqVO.setSpuId(spuId);  
126 - productCommentPageReqVO.setSpuName("感冒药");  
127 - productCommentPageReqVO.setScores(ProductCommentScoresEnum.FOUR.getScores());  
128 - productCommentPageReqVO.setReplyStatus(Boolean.TRUE);  
129 -  
130 - PageResult<ProductCommentDO> commentPage = productCommentService.getCommentPage(productCommentPageReqVO);  
131 - PageResult<ProductCommentRespVO> result = BeanUtils.toBean(productCommentMapper.selectPage(productCommentPageReqVO),  
132 - ProductCommentRespVO.class);  
133 - assertEquals(result.getTotal(), commentPage.getTotal()); 236 + PageResult<ProductCommentDO> pageResult = productCommentService.getCommentPage(reqVO);
  237 + // 断言
  238 + assertEquals(1, pageResult.getTotal());
  239 + assertEquals(1, pageResult.getList().size());
  240 + assertPojoEquals(dbComment, pageResult.getList().get(0));
  241 + }
134 242
135 - PageResult<ProductCommentDO> all = productCommentService.getCommentPage(new ProductCommentPageReqVO());  
136 - assertEquals(8, all.getTotal()); 243 + @Test
  244 + public void testGetAppCommentPage_success() {
  245 + // mock 数据
  246 + ProductCommentDO goodComment = randomCommentDO(o -> o.setSpuId(1L).setVisible(Boolean.TRUE)
  247 + .setScores(ProductCommentScoresEnum.FIVE.getScores())
  248 + .setSkuProperties(null)); // ProductSkuDO.Property 不可序列化,cloneIgnoreId 无法深拷贝
  249 + productCommentMapper.insert(goodComment);
  250 + ProductCommentDO mediocreComment = cloneIgnoreId(goodComment, o -> o.setScores(ProductCommentScoresEnum.THREE.getScores()));
  251 + productCommentMapper.insert(mediocreComment);
  252 + ProductCommentDO negativeComment = cloneIgnoreId(goodComment, o -> o.setScores(ProductCommentScoresEnum.ONE.getScores()));
  253 + productCommentMapper.insert(negativeComment);
  254 + // 测试 visible 不匹配
  255 + productCommentMapper.insert(cloneIgnoreId(mediocreComment, o -> o.setVisible(Boolean.FALSE)));
  256 + // 测试 spuId 不匹配
  257 + productCommentMapper.insert(cloneIgnoreId(mediocreComment, o -> o.setSpuId(2L)));
  258 +
  259 + // 调用 + 断言:全部可见评价
  260 + PageResult<ProductCommentDO> allResult = productCommentService.getCommentPage(
  261 + new AppCommentPageReqVO().setSpuId(1L), Boolean.TRUE);
  262 + assertEquals(3, allResult.getTotal());
  263 + // 调用 + 断言:好评
  264 + PageResult<ProductCommentDO> goodResult = productCommentService.getCommentPage(
  265 + new AppCommentPageReqVO().setSpuId(1L).setType(AppCommentPageReqVO.GOOD_COMMENT), Boolean.TRUE);
  266 + assertEquals(1, goodResult.getTotal());
  267 + assertPojoEquals(goodComment, goodResult.getList().get(0));
  268 + // 调用 + 断言:中评
  269 + PageResult<ProductCommentDO> mediocreResult = productCommentService.getCommentPage(
  270 + new AppCommentPageReqVO().setSpuId(1L).setType(AppCommentPageReqVO.MEDIOCRE_COMMENT), Boolean.TRUE);
  271 + assertEquals(1, mediocreResult.getTotal());
  272 + assertPojoEquals(mediocreComment, mediocreResult.getList().get(0));
  273 + // 调用 + 断言:差评
  274 + PageResult<ProductCommentDO> negativeResult = productCommentService.getCommentPage(
  275 + new AppCommentPageReqVO().setSpuId(1L).setType(AppCommentPageReqVO.NEGATIVE_COMMENT), Boolean.TRUE);
  276 + assertEquals(1, negativeResult.getTotal());
  277 + assertPojoEquals(negativeComment, negativeResult.getList().get(0));
  278 + // 调用 + 断言:不过滤可见性时,包含隐藏的中评
  279 + PageResult<ProductCommentDO> mediocreAllResult = productCommentService.getCommentPage(
  280 + new AppCommentPageReqVO().setSpuId(1L).setType(AppCommentPageReqVO.MEDIOCRE_COMMENT), null);
  281 + assertEquals(2, mediocreAllResult.getTotal());
  282 + }
137 283
138 - // 测试获取所有商品分页评论数据  
139 - PageResult<ProductCommentDO> result1 = productCommentService.getCommentPage(new AppCommentPageReqVO(), Boolean.TRUE);  
140 - assertEquals(7, result1.getTotal()); 284 + // ========== 更新评价 ==========
141 285
142 - // 测试获取所有商品分页中评数据  
143 - PageResult<ProductCommentDO> result2 = productCommentService.getCommentPage(new AppCommentPageReqVO().setType(AppCommentPageReqVO.MEDIOCRE_COMMENT), Boolean.TRUE);  
144 - assertEquals(2, result2.getTotal()); 286 + @Test
  287 + public void testUpdateCommentVisible_success() {
  288 + // mock 数据
  289 + ProductCommentDO dbComment = randomCommentDO(o -> o.setVisible(Boolean.TRUE));
  290 + productCommentMapper.insert(dbComment);
  291 + // 准备参数
  292 + ProductCommentUpdateVisibleReqVO reqVO = new ProductCommentUpdateVisibleReqVO();
  293 + reqVO.setId(dbComment.getId());
  294 + reqVO.setVisible(Boolean.FALSE);
145 295
146 - // 测试获取指定 spuId 商品分页中评数据  
147 - PageResult<ProductCommentDO> result3 = productCommentService.getCommentPage(new AppCommentPageReqVO().setSpuId(spuId).setType(AppCommentPageReqVO.MEDIOCRE_COMMENT), Boolean.TRUE);  
148 - assertEquals(2, result3.getTotal()); 296 + // 调用
  297 + productCommentService.updateCommentVisible(reqVO);
  298 + // 断言
  299 + ProductCommentDO comment = productCommentMapper.selectById(dbComment.getId());
  300 + assertFalse(comment.getVisible());
  301 + assertPojoEquals(dbComment, comment, "visible", "updater", "updateTime");
  302 + }
149 303
150 - // 测试分页 tab count  
151 - //AppCommentStatisticsRespVO tabsCount = productCommentService.getCommentStatistics(spuId, Boolean.TRUE);  
152 - //assertEquals(4, tabsCount.getGoodCount());  
153 - //assertEquals(2, tabsCount.getMediocreCount());  
154 - //assertEquals(0, tabsCount.getNegativeCount()); 304 + @Test
  305 + public void testUpdateCommentVisible_notExists() {
  306 + // 准备参数
  307 + ProductCommentUpdateVisibleReqVO reqVO = new ProductCommentUpdateVisibleReqVO();
  308 + reqVO.setId(randomLongId());
  309 + reqVO.setVisible(Boolean.FALSE);
155 310
  311 + // 调用,并断言异常
  312 + assertServiceException(() -> productCommentService.updateCommentVisible(reqVO), COMMENT_NOT_EXISTS);
156 } 313 }
157 314
158 @Test 315 @Test
159 - public void testUpdateCommentVisible_success() {  
160 - // mock 测试  
161 - ProductCommentDO productComment = randomPojo(ProductCommentDO.class, o -> {  
162 - o.setVisible(Boolean.TRUE);  
163 - });  
164 - productCommentMapper.insert(productComment); 316 + public void testReplyComment_success() {
  317 + // mock 数据
  318 + ProductCommentDO dbComment = randomCommentDO(o -> o.setReplyStatus(Boolean.FALSE)
  319 + .setReplyUserId(null).setReplyContent(null).setReplyTime(null));
  320 + productCommentMapper.insert(dbComment);
  321 + // 准备参数
  322 + ProductCommentReplyReqVO reqVO = new ProductCommentReplyReqVO();
  323 + reqVO.setId(dbComment.getId());
  324 + reqVO.setReplyContent("测试");
  325 + Long userId = randomLongId();
165 326
166 - Long productCommentId = productComment.getId(); 327 + // 调用
  328 + productCommentService.replyComment(reqVO, userId);
  329 + // 断言
  330 + ProductCommentDO comment = productCommentMapper.selectById(dbComment.getId());
  331 + assertTrue(comment.getReplyStatus());
  332 + assertEquals(userId, comment.getReplyUserId());
  333 + assertEquals("测试", comment.getReplyContent());
  334 + assertNotNull(comment.getReplyTime());
  335 + }
167 336
168 - ProductCommentUpdateVisibleReqVO updateReqVO = new ProductCommentUpdateVisibleReqVO();  
169 - updateReqVO.setId(productCommentId);  
170 - updateReqVO.setVisible(Boolean.FALSE);  
171 - productCommentService.updateCommentVisible(updateReqVO); 337 + @Test
  338 + public void testReplyComment_notExists() {
  339 + // 准备参数
  340 + ProductCommentReplyReqVO reqVO = new ProductCommentReplyReqVO();
  341 + reqVO.setId(randomLongId());
  342 + reqVO.setReplyContent("测试");
172 343
173 - ProductCommentDO productCommentDO = productCommentMapper.selectById(productCommentId);  
174 - assertFalse(productCommentDO.getVisible()); 344 + // 调用,并断言异常
  345 + assertServiceException(() -> productCommentService.replyComment(reqVO, randomLongId()), COMMENT_NOT_EXISTS);
175 } 346 }
176 347
  348 + // ========== 随机对象 ==========
177 349
178 - @Test  
179 - public void testCommentReply_success() {  
180 - // mock 测试  
181 - ProductCommentDO productComment = randomPojo(ProductCommentDO.class);  
182 - productCommentMapper.insert(productComment);  
183 -  
184 - Long productCommentId = productComment.getId(); 350 + private ProductSkuDO mockSku(Long skuId) {
  351 + ProductSkuDO sku = randomPojo(ProductSkuDO.class, o -> o.setId(skuId)
  352 + .setProperties(singletonList(new ProductSkuDO.Property(1L, "颜色", 2L, "红色"))));
  353 + when(productSkuService.getSku(eq(skuId), eq(true))).thenReturn(sku);
  354 + return sku;
  355 + }
185 356
186 - ProductCommentReplyReqVO replyVO = new ProductCommentReplyReqVO();  
187 - replyVO.setId(productCommentId);  
188 - replyVO.setReplyContent("测试");  
189 - productCommentService.replyComment(replyVO, 1L); 357 + private ProductSpuDO mockSpu(Long spuId) {
  358 + ProductSpuDO spu = randomPojo(ProductSpuDO.class, o -> o.setId(spuId));
  359 + when(productSpuService.getSpu(eq(spuId), eq(true))).thenReturn(spu);
  360 + return spu;
  361 + }
190 362
191 - ProductCommentDO productCommentDO = productCommentMapper.selectById(productCommentId);  
192 - assertEquals("测试", productCommentDO.getReplyContent()); 363 + @SafeVarargs
  364 + private static ProductCommentDO randomCommentDO(Consumer<ProductCommentDO>... consumers) {
  365 + Consumer<ProductCommentDO> consumer = (o) -> {
  366 + o.setScores(randomEle(ProductCommentScoresEnum.values()).getScores()); // 保证 scores 的范围
  367 + o.setDescriptionScores(randomEle(ProductCommentScoresEnum.values()).getScores());
  368 + o.setBenefitScores(randomEle(ProductCommentScoresEnum.values()).getScores());
  369 + o.setSkuProperties(singletonList(new ProductSkuDO.Property(1L, "颜色", 2L, "红色")));
  370 + };
  371 + return randomPojo(ProductCommentDO.class, ArrayUtils.append(consumer, consumers));
193 } 372 }
194 373
195 } 374 }
yudao-module-mall/yudao-module-product/src/test/resources/sql/create_tables.sql
@@ -126,32 +126,34 @@ CREATE TABLE IF NOT EXISTS `product_property_value` ( @@ -126,32 +126,34 @@ CREATE TABLE IF NOT EXISTS `product_property_value` (
126 PRIMARY KEY("id") 126 PRIMARY KEY("id")
127 ) COMMENT '规格值'; 127 ) COMMENT '规格值';
128 128
129 -DROP TABLE IF EXISTS `product_comment` (  
130 - `id` bigint NOT NULL AUTO_INCREMENT COMMENT '评论编号,主键自增', 129 +CREATE TABLE IF NOT EXISTS `product_comment` (
  130 + `id` bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
131 `user_id` bigint DEFAULT NULL COMMENT '评价人的用户编号关联 MemberUserDO 的 id 编号', 131 `user_id` bigint DEFAULT NULL COMMENT '评价人的用户编号关联 MemberUserDO 的 id 编号',
132 `user_nickname` varchar(255) DEFAULT NULL COMMENT '评价人名称', 132 `user_nickname` varchar(255) DEFAULT NULL COMMENT '评价人名称',
133 - `user_avatar` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '评价人头像',  
134 - `anonymous` bit(1) DEFAULT NULL COMMENT '是否匿名', 133 + `user_avatar` varchar(1024) DEFAULT NULL COMMENT '评价人头像',
  134 + `anonymous` bit DEFAULT NULL COMMENT '是否匿名',
135 `order_id` bigint DEFAULT NULL COMMENT '交易订单编号关联 TradeOrderDO 的 id 编号', 135 `order_id` bigint DEFAULT NULL COMMENT '交易订单编号关联 TradeOrderDO 的 id 编号',
136 `order_item_id` bigint DEFAULT NULL COMMENT '交易订单项编号关联 TradeOrderItemDO 的 id 编号', 136 `order_item_id` bigint DEFAULT NULL COMMENT '交易订单项编号关联 TradeOrderItemDO 的 id 编号',
137 `spu_id` bigint DEFAULT NULL COMMENT '商品 SPU 编号关联 ProductSpuDO 的 id', 137 `spu_id` bigint DEFAULT NULL COMMENT '商品 SPU 编号关联 ProductSpuDO 的 id',
138 `spu_name` varchar(255) DEFAULT NULL COMMENT '商品 SPU 名称', 138 `spu_name` varchar(255) DEFAULT NULL COMMENT '商品 SPU 名称',
139 `sku_id` bigint DEFAULT NULL COMMENT '商品 SKU 编号关联 ProductSkuDO 的 id 编号', 139 `sku_id` bigint DEFAULT NULL COMMENT '商品 SKU 编号关联 ProductSkuDO 的 id 编号',
140 - `visible` bit(1) DEFAULT NULL COMMENT '是否可见true:显示false:隐藏', 140 + `sku_pic_url` varchar(256) DEFAULT NULL COMMENT '商品 SKU 图片地址',
  141 + `sku_properties` varchar(512) DEFAULT NULL COMMENT '属性数组,JSON 格式',
  142 + `visible` bit DEFAULT NULL COMMENT '是否可见true:显示false:隐藏',
141 `scores` tinyint DEFAULT NULL COMMENT '评分星级1-5分', 143 `scores` tinyint DEFAULT NULL COMMENT '评分星级1-5分',
142 `description_scores` tinyint DEFAULT NULL COMMENT '描述星级1-5 星', 144 `description_scores` tinyint DEFAULT NULL COMMENT '描述星级1-5 星',
143 `benefit_scores` tinyint DEFAULT NULL COMMENT '服务星级1-5 星', 145 `benefit_scores` tinyint DEFAULT NULL COMMENT '服务星级1-5 星',
144 - `content` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '评论内容', 146 + `content` varchar(1024) DEFAULT NULL COMMENT '评论内容',
145 `pic_urls` varchar(4096) DEFAULT NULL COMMENT '评论图片地址数组', 147 `pic_urls` varchar(4096) DEFAULT NULL COMMENT '评论图片地址数组',
146 - `reply_status` bit(1) DEFAULT NULL COMMENT '商家是否回复', 148 + `reply_status` bit DEFAULT NULL COMMENT '商家是否回复',
147 `reply_user_id` bigint DEFAULT NULL COMMENT '回复管理员编号关联 AdminUserDO 的 id 编号', 149 `reply_user_id` bigint DEFAULT NULL COMMENT '回复管理员编号关联 AdminUserDO 的 id 编号',
148 - `reply_content` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '商家回复内容',  
149 - `reply_time` datetime DEFAULT NULL COMMENT '商家回复时间',  
150 - `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '创建者',  
151 - `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',  
152 - `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '更新者',  
153 - `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',  
154 - `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',  
155 - `tenant_id` bigint NOT NULL DEFAULT '0' COMMENT '租户编号',  
156 - PRIMARY KEY (`id`) USING BTREE  
157 -) ENGINE = InnoDB AUTO_INCREMENT = 26 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin COMMENT = '商品评论';  
158 \ No newline at end of file 150 \ No newline at end of file
  151 + `reply_content` varchar(1024) DEFAULT NULL COMMENT '商家回复内容',
  152 + `reply_time` timestamp DEFAULT NULL COMMENT '商家回复时间',
  153 + "creator" varchar(64) DEFAULT '',
  154 + "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  155 + "updater" varchar(64) DEFAULT '',
  156 + "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  157 + "deleted" bit NOT NULL DEFAULT FALSE,
  158 + "tenant_id" bigint not null default '0',
  159 + PRIMARY KEY("id")
  160 +) COMMENT '商品评论';
159 \ No newline at end of file 161 \ No newline at end of file