RecommendServiceImplTest.java 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. package com.mangoo.rating.recommend.cart;
  2. import com.mangoo.rating.recommend.AjaxResult;
  3. import com.mangoo.rating.recommend.client.feign.PreOrderFeignClient;
  4. import com.mangoo.rating.recommend.config.RecommendProperties;
  5. import com.mangoo.rating.recommend.manager.ActiveBatchManager;
  6. import com.mangoo.rating.recommend.manager.RatingRecommendRuleManager;
  7. import com.mangoo.rating.recommend.po.RatingRecommendRulePO;
  8. import com.mangoo.rating.recommend.request.recommend.PreOrderCardDTO;
  9. import com.mangoo.rating.recommend.request.recommend.PreOrderSaveDTO;
  10. import com.mangoo.rating.recommend.request.recommend.RecommendCardDTO;
  11. import com.mangoo.rating.recommend.request.recommend.RecommendRequest;
  12. import com.mangoo.rating.recommend.service.impl.RecommendServiceImpl;
  13. import com.mangoo.rating.recommend.service.recommend.EfficiencyPriceProvider;
  14. import com.mangoo.rating.recommend.service.recommend.LoginUserProvider;
  15. import org.junit.jupiter.api.Test;
  16. import org.junit.jupiter.api.extension.ExtendWith;
  17. import org.mockito.ArgumentCaptor;
  18. import org.mockito.InjectMocks;
  19. import org.mockito.Mock;
  20. import org.mockito.junit.jupiter.MockitoExtension;
  21. import java.math.BigDecimal;
  22. import java.util.ArrayList;
  23. import java.util.Arrays;
  24. import java.util.HashMap;
  25. import java.util.Map;
  26. import static org.junit.jupiter.api.Assertions.assertEquals;
  27. import static org.junit.jupiter.api.Assertions.assertNotEquals;
  28. import static org.junit.jupiter.api.Assertions.assertTrue;
  29. import static org.mockito.ArgumentMatchers.any;
  30. import static org.mockito.ArgumentMatchers.anyString;
  31. import static org.mockito.Mockito.lenient;
  32. import static org.mockito.Mockito.never;
  33. import static org.mockito.Mockito.verify;
  34. import static org.mockito.Mockito.when;
  35. /**
  36. * RecommendServiceImpl 单测(预订单落库改造后:返回 preOrderNo + Feign 落库)
  37. *
  38. * 覆盖的目标场景:
  39. * 1) 空入参 / null → 非 0 code
  40. * 2) 超上限 → 非 0 code
  41. * 3) 正常,Feign 返回 preOrderNo → code=0,data=preOrderNo
  42. * 4) userId 为 null(未登录) → 非 0 code,且不调用 Feign
  43. * 5) Feign 抛异常 → 非 0 code
  44. * 6) Feign 返回 code!=0 → 非 0 code(下游 msg 不再透传,改为固定文案)
  45. * 7) 落库包字段映射:10 个识别字段 + userId/totalCards/packageId 全透传
  46. *
  47. * @author gengjintao
  48. */
  49. @ExtendWith(MockitoExtension.class)
  50. class RecommendServiceImplTest {
  51. @Mock
  52. private RatingRecommendRuleManager ratingRecommendRuleManager;
  53. @Mock
  54. private EfficiencyPriceProvider efficiencyPriceProvider;
  55. @Mock
  56. private RecommendProperties recommendProperties;
  57. @Mock
  58. private ActiveBatchManager activeBatchManager;
  59. @Mock
  60. private PreOrderFeignClient preOrderFeignClient;
  61. @Mock
  62. private LoginUserProvider loginUserProvider;
  63. @InjectMocks
  64. private RecommendServiceImpl recommendService;
  65. /** 落库失败对外统一文案(生产代码不再透传下游 msg) */
  66. private static final String FAIL_MSG = "预订单生成失败,请稍后重试";
  67. /** 造最简卡入参:仅带识别 4 字段 */
  68. private RecommendCardDTO card(String player, String year, String series, String cardSet) {
  69. RecommendCardDTO d = new RecommendCardDTO();
  70. d.setPlayer(player);
  71. d.setYear(year);
  72. d.setSeries(series);
  73. d.setCardSet(cardSet);
  74. return d;
  75. }
  76. /** 造一个 request(浅拷贝入参列表,避免测试之间共享同一 List) */
  77. private RecommendRequest req(RecommendCardDTO... cards) {
  78. RecommendRequest r = new RecommendRequest();
  79. r.setCards(new ArrayList<>(Arrays.asList(cards)));
  80. return r;
  81. }
  82. /**
  83. * 构造一条"合法且能通过 isLegalRule 校验"的 EXACT 命中规则。
  84. * 注意 evaluateEfficiencyName 在 PO 中是 Long(非 String),此处按真实签名给 3L。
  85. */
  86. private RatingRecommendRulePO exactRule() {
  87. RatingRecommendRulePO po = new RatingRecommendRulePO();
  88. po.setEvaluateEfficiencyId(3L);
  89. po.setEvaluateEfficiencyName(3L);
  90. po.setCardId("DW-CARD-1");
  91. return po;
  92. }
  93. /** 构造 Feign 成功返回:data 里带 preOrderNo */
  94. private AjaxResult okWith(String preOrderNo) {
  95. Map<String, Object> data = new HashMap<>();
  96. data.put("preOrderNo", preOrderNo);
  97. return AjaxResult.success(data);
  98. }
  99. /** 默认让匹配命中、价格可取、用户已登录(各用例可覆盖) */
  100. private void defaultHappyStubs() {
  101. lenient().when(recommendProperties.getBatchMax()).thenReturn(50);
  102. lenient().when(activeBatchManager.selectActiveBatchNo()).thenReturn("B1");
  103. lenient().when(ratingRecommendRuleManager.selectByExact(any(), any(), any(), any(), any()))
  104. .thenReturn(exactRule());
  105. Map<Long, BigDecimal> prices = new HashMap<>();
  106. prices.put(3L, new BigDecimal("200.00"));
  107. lenient().when(efficiencyPriceProvider.loadEfficiencyPrices()).thenReturn(prices);
  108. lenient().when(loginUserProvider.currentUserId()).thenReturn(1001L);
  109. lenient().when(loginUserProvider.currentUserHeader()).thenReturn("base64header");
  110. }
  111. private int code(AjaxResult r) {
  112. return r.getCode();
  113. }
  114. @Test
  115. void 空入参_返回error() {
  116. // null / 空列表:直接拦截,不调用 Feign
  117. assertNotEquals(0, code(recommendService.recommendEfficiency(null)));
  118. assertNotEquals(0, code(recommendService.recommendEfficiency(new RecommendRequest())));
  119. }
  120. @Test
  121. void 超限_返回error() {
  122. // 上限=1,传 2 张 → 拦截
  123. when(recommendProperties.getBatchMax()).thenReturn(1);
  124. AjaxResult result = recommendService.recommendEfficiency(
  125. req(card("p", "y", "s", "cs"), card("p", "y", "s", "cs")));
  126. assertNotEquals(0, code(result));
  127. }
  128. @Test
  129. void 正常_落库成功_返回preOrderNo() {
  130. defaultHappyStubs();
  131. when(preOrderFeignClient.savePreOrder(anyString(), any(PreOrderSaveDTO.class)))
  132. .thenReturn(okWith("PRE123456"));
  133. AjaxResult result = recommendService.recommendEfficiency(req(card("Jordan", "1986", "Fleer", "Base")));
  134. assertEquals(0, code(result));
  135. // 生产代码:成功时 data 为 String preOrderNo
  136. assertEquals("PRE123456", result.get("data"));
  137. }
  138. @Test
  139. void 用户未登录_不调用Feign_返回error() {
  140. defaultHappyStubs();
  141. // 覆盖 defaultHappyStubs 里的登录用户
  142. when(loginUserProvider.currentUserId()).thenReturn(null);
  143. AjaxResult result = recommendService.recommendEfficiency(req(card("p", "y", "s", "cs")));
  144. assertNotEquals(0, code(result));
  145. // 关键:未登录时不得触发远程调用
  146. verify(preOrderFeignClient, never()).savePreOrder(any(), any());
  147. }
  148. @Test
  149. void Feign抛异常_返回生成失败() {
  150. defaultHappyStubs();
  151. when(preOrderFeignClient.savePreOrder(anyString(), any(PreOrderSaveDTO.class)))
  152. .thenThrow(new RuntimeException("timeout"));
  153. AjaxResult result = recommendService.recommendEfficiency(req(card("p", "y", "s", "cs")));
  154. assertNotEquals(0, code(result));
  155. assertEquals(FAIL_MSG, result.getMsg());
  156. }
  157. @Test
  158. void Feign返回非0_透传错误() {
  159. defaultHappyStubs();
  160. // 下游返回 error(含具体 msg "下游库满"),生产代码经质量审查后已不再透传下游 msg,
  161. // 对外统一为固定文案 FAIL_MSG,避免下游信息泄露。故只断言 code 非 0 + 固定文案。
  162. when(preOrderFeignClient.savePreOrder(anyString(), any(PreOrderSaveDTO.class)))
  163. .thenReturn(AjaxResult.error("下游库满"));
  164. AjaxResult result = recommendService.recommendEfficiency(req(card("p", "y", "s", "cs")));
  165. assertNotEquals(0, code(result));
  166. assertEquals(FAIL_MSG, result.getMsg());
  167. }
  168. @Test
  169. void 落库包_识别字段与关键字段全部透传() {
  170. defaultHappyStubs();
  171. when(preOrderFeignClient.savePreOrder(anyString(), any(PreOrderSaveDTO.class)))
  172. .thenReturn(okWith("PRE999"));
  173. // 构造含 10 个补齐识别字段 + id/price 的入参卡
  174. RecommendCardDTO c = card("Jordan", "1986", "Fleer", "Base");
  175. c.setSportEvent("篮球");
  176. c.setCardType(2);
  177. c.setRole("SG");
  178. c.setCardNo("NO-1");
  179. c.setTeam("Bulls");
  180. c.setLimitId("/99");
  181. c.setIpName("NBA");
  182. c.setRule("MVP");
  183. c.setRarity("SSR");
  184. c.setMaterial("金箔");
  185. c.setId(88L);
  186. c.setPrice(new BigDecimal("120"));
  187. recommendService.recommendEfficiency(req(c));
  188. // 抓取实际传给 Feign 的 PreOrderSaveDTO
  189. ArgumentCaptor<PreOrderSaveDTO> captor = ArgumentCaptor.forClass(PreOrderSaveDTO.class);
  190. verify(preOrderFeignClient).savePreOrder(anyString(), captor.capture());
  191. PreOrderSaveDTO sent = captor.getValue();
  192. // 主表汇总字段
  193. assertEquals(1001L, sent.getUserId());
  194. assertEquals(1, sent.getTotalCards());
  195. // 卡片明细:10 识别字段全部透传(sportEvent/cardType/role/cardNo/team/limitId/ipName/rule/rarity/material)
  196. PreOrderCardDTO card = sent.getGroups().get(0).getCards().get(0);
  197. assertEquals("篮球", card.getSportEvent());
  198. assertEquals(2, card.getCardType());
  199. assertEquals("SG", card.getRole());
  200. assertEquals("NO-1", card.getCardNo());
  201. assertEquals("Bulls", card.getTeam());
  202. assertEquals("/99", card.getLimitId());
  203. assertEquals("NBA", card.getIpName());
  204. // 入参 rule -> VO roleName -> PreOrderCardDTO.rule(往返映射)
  205. assertEquals("MVP", card.getRule());
  206. assertEquals("SSR", card.getRarity());
  207. assertEquals("金箔", card.getMaterial());
  208. // 套餐/命中:入参 id -> packageId;规则 cardId -> matchedCardId
  209. assertEquals(88L, card.getPackageId());
  210. assertEquals("DW-CARD-1", card.getMatchedCardId());
  211. // 时效价可取到时应大于 0
  212. assertTrue(card.getEfficiencyPrice().compareTo(BigDecimal.ZERO) > 0);
  213. }
  214. }