|
|
@@ -1,306 +0,0 @@
|
|
|
-package com.mangoo.rating.recommend.cart;
|
|
|
-
|
|
|
-import com.mangoo.rating.recommend.AjaxResult;
|
|
|
-import com.mangoo.rating.recommend.client.feign.PreOrderFeignClient;
|
|
|
-import com.mangoo.rating.recommend.config.RecommendProperties;
|
|
|
-import com.mangoo.rating.recommend.dto.recommend.EfficiencyDictItem;
|
|
|
-import com.mangoo.rating.recommend.dto.recommend.SeriesCardSetKey;
|
|
|
-import com.mangoo.rating.recommend.manager.ActiveBatchManager;
|
|
|
-import com.mangoo.rating.recommend.manager.RatingRecommendRuleManager;
|
|
|
-import com.mangoo.rating.recommend.po.RatingRecommendRulePO;
|
|
|
-import com.mangoo.rating.recommend.request.recommend.PreOrderCardDTO;
|
|
|
-import com.mangoo.rating.recommend.request.recommend.PreOrderSaveDTO;
|
|
|
-import com.mangoo.rating.recommend.request.recommend.RecommendCardDTO;
|
|
|
-import com.mangoo.rating.recommend.request.recommend.RecommendRequest;
|
|
|
-import com.mangoo.rating.recommend.service.impl.RecommendServiceImpl;
|
|
|
-import com.mangoo.rating.recommend.service.recommend.EfficiencyPriceProvider;
|
|
|
-import com.mangoo.rating.recommend.service.recommend.LoginUserProvider;
|
|
|
-import org.junit.jupiter.api.Test;
|
|
|
-import org.junit.jupiter.api.extension.ExtendWith;
|
|
|
-import org.mockito.ArgumentCaptor;
|
|
|
-import org.mockito.InjectMocks;
|
|
|
-import org.mockito.Mock;
|
|
|
-import org.mockito.junit.jupiter.MockitoExtension;
|
|
|
-
|
|
|
-import java.math.BigDecimal;
|
|
|
-import java.util.ArrayList;
|
|
|
-import java.util.Arrays;
|
|
|
-import java.util.HashMap;
|
|
|
-import java.util.Map;
|
|
|
-
|
|
|
-import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
|
-import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
|
|
-import static org.mockito.ArgumentMatchers.any;
|
|
|
-import static org.mockito.ArgumentMatchers.anyString;
|
|
|
-import static org.mockito.Mockito.lenient;
|
|
|
-import static org.mockito.Mockito.never;
|
|
|
-import static org.mockito.Mockito.verify;
|
|
|
-import static org.mockito.Mockito.when;
|
|
|
-
|
|
|
-/**
|
|
|
- * RecommendServiceImpl 单测(预订单落库改造后:返回 preOrderNo + Feign 落库)
|
|
|
- *
|
|
|
- * 覆盖的目标场景:
|
|
|
- * 1) 空入参 / null → 非 0 code
|
|
|
- * 2) 超上限 → 非 0 code
|
|
|
- * 3) 正常,Feign 返回 preOrderNo → code=0,data=preOrderNo
|
|
|
- * 4) userId 为 null(未登录) → 非 0 code,且不调用 Feign
|
|
|
- * 5) Feign 抛异常 → 非 0 code
|
|
|
- * 6) Feign 返回 code!=0 → 非 0 code(下游 msg 不再透传,改为固定文案)
|
|
|
- * 7) 落库包字段映射:识别字段 + userId/totalCards/packageId 全透传
|
|
|
- * 8) 未命中 → 按 cardType 兜底默认档(落库卡 evaluateEfficiencyId 取兜底档)
|
|
|
- * 9) 批量查询:同 (series,cardSet) 卡去重后每次请求只查一次规则
|
|
|
- *
|
|
|
- * @author gengjintao
|
|
|
- */
|
|
|
-@ExtendWith(MockitoExtension.class)
|
|
|
-class RecommendServiceImplTest {
|
|
|
-
|
|
|
- @Mock
|
|
|
- private RatingRecommendRuleManager ratingRecommendRuleManager;
|
|
|
- @Mock
|
|
|
- private EfficiencyPriceProvider efficiencyPriceProvider;
|
|
|
- @Mock
|
|
|
- private RecommendProperties recommendProperties;
|
|
|
- @Mock
|
|
|
- private ActiveBatchManager activeBatchManager;
|
|
|
- @Mock
|
|
|
- private PreOrderFeignClient preOrderFeignClient;
|
|
|
- @Mock
|
|
|
- private LoginUserProvider loginUserProvider;
|
|
|
- @InjectMocks
|
|
|
- private RecommendServiceImpl recommendService;
|
|
|
-
|
|
|
- /** 落库失败对外统一文案(生产代码不再透传下游 msg) */
|
|
|
- private static final String FAIL_MSG = "预订单生成失败,请稍后重试";
|
|
|
-
|
|
|
- /** 造最简卡入参:仅带识别 4 字段 */
|
|
|
- private RecommendCardDTO card(String player, String year, String series, String cardSet) {
|
|
|
- RecommendCardDTO d = new RecommendCardDTO();
|
|
|
- d.setPlayer(player);
|
|
|
- d.setYear(year);
|
|
|
- d.setSeries(series);
|
|
|
- d.setCardSet(cardSet);
|
|
|
- return d;
|
|
|
- }
|
|
|
-
|
|
|
- /** 造一个 request(浅拷贝入参列表,避免测试之间共享同一 List) */
|
|
|
- private RecommendRequest req(RecommendCardDTO... cards) {
|
|
|
- RecommendRequest r = new RecommendRequest();
|
|
|
- r.setCards(new ArrayList<>(Arrays.asList(cards)));
|
|
|
- return r;
|
|
|
- }
|
|
|
-
|
|
|
- /** 造一条 EXACT 命中规则(放入池) */
|
|
|
- private RatingRecommendRulePO poolRule(long id, int eff, String player, String year,
|
|
|
- String series, String cardSet) {
|
|
|
- RatingRecommendRulePO po = new RatingRecommendRulePO();
|
|
|
- po.setId(id);
|
|
|
- // PO 字段为 recommendEfficiencyId(Long);这里将 int 参数窄化装箱
|
|
|
- po.setRecommendEfficiencyId((long) eff);
|
|
|
- po.setPlayer(player);
|
|
|
- po.setCardYear(year);
|
|
|
- po.setSeries(series);
|
|
|
- po.setCardSet(cardSet);
|
|
|
- po.setCardId("DW-CARD-1");
|
|
|
- return po;
|
|
|
- }
|
|
|
-
|
|
|
- /** 构造 Feign 成功返回:data 里带 preOrderNo */
|
|
|
- private AjaxResult okWith(String preOrderNo) {
|
|
|
- Map<String, Object> data = new HashMap<>();
|
|
|
- data.put("preOrderNo", preOrderNo);
|
|
|
- return AjaxResult.success(data);
|
|
|
- }
|
|
|
-
|
|
|
- /** 默认:批量池含一条 EXACT 命中、字典有价、用户已登录 */
|
|
|
- private void defaultHappyStubs() {
|
|
|
- lenient().when(recommendProperties.getBatchMax()).thenReturn(50);
|
|
|
- lenient().when(activeBatchManager.selectActiveBatchNo()).thenReturn("B1");
|
|
|
- lenient().when(ratingRecommendRuleManager.selectByBatchAndSeriesSets(anyString(), any()))
|
|
|
- .thenReturn(new ArrayList<>(Arrays.asList(
|
|
|
- poolRule(1L, 3, "Jordan", "1986", "Fleer", "Base"))));
|
|
|
- Map<Long, EfficiencyDictItem> dict = new HashMap<>();
|
|
|
- dict.put(3L, new EfficiencyDictItem("闪评", new BigDecimal("200.00")));
|
|
|
- lenient().when(efficiencyPriceProvider.loadEfficiencyDict()).thenReturn(dict);
|
|
|
- lenient().when(loginUserProvider.currentUserId()).thenReturn(1001L);
|
|
|
- lenient().when(loginUserProvider.currentUserHeader()).thenReturn("base64header");
|
|
|
- }
|
|
|
-
|
|
|
- private int code(AjaxResult r) {
|
|
|
- return r.getCode();
|
|
|
- }
|
|
|
-
|
|
|
- @Test
|
|
|
- void 空入参_返回error() {
|
|
|
- // null / 空列表:直接拦截,不调用 Feign
|
|
|
- assertNotEquals(0, code(recommendService.recommendEfficiency(null)));
|
|
|
- assertNotEquals(0, code(recommendService.recommendEfficiency(new RecommendRequest())));
|
|
|
- }
|
|
|
-
|
|
|
- @Test
|
|
|
- void 超限_返回error() {
|
|
|
- // 上限=1,传 2 张 → 拦截
|
|
|
- when(recommendProperties.getBatchMax()).thenReturn(1);
|
|
|
- AjaxResult result = recommendService.recommendEfficiency(
|
|
|
- req(card("p", "y", "s", "cs"), card("p", "y", "s", "cs")));
|
|
|
- assertNotEquals(0, code(result));
|
|
|
- }
|
|
|
-
|
|
|
- @Test
|
|
|
- void 正常_落库成功_返回preOrderNo() {
|
|
|
- defaultHappyStubs();
|
|
|
- when(preOrderFeignClient.savePreOrder(anyString(), any(PreOrderSaveDTO.class)))
|
|
|
- .thenReturn(okWith("PRE123456"));
|
|
|
- AjaxResult result = recommendService.recommendEfficiency(req(card("Jordan", "1986", "Fleer", "Base")));
|
|
|
- assertEquals(0, code(result));
|
|
|
- // 生产代码:成功时 data 为 String preOrderNo
|
|
|
- assertEquals("PRE123456", result.get("data"));
|
|
|
- }
|
|
|
-
|
|
|
- @Test
|
|
|
- void 用户未登录_不调用Feign_返回error() {
|
|
|
- defaultHappyStubs();
|
|
|
- // 覆盖 defaultHappyStubs 里的登录用户
|
|
|
- when(loginUserProvider.currentUserId()).thenReturn(null);
|
|
|
- AjaxResult result = recommendService.recommendEfficiency(req(card("p", "y", "s", "cs")));
|
|
|
- assertNotEquals(0, code(result));
|
|
|
- // 关键:未登录时不得触发远程调用
|
|
|
- verify(preOrderFeignClient, never()).savePreOrder(any(), any());
|
|
|
- }
|
|
|
-
|
|
|
- @Test
|
|
|
- void Feign抛异常_返回生成失败() {
|
|
|
- defaultHappyStubs();
|
|
|
- when(preOrderFeignClient.savePreOrder(anyString(), any(PreOrderSaveDTO.class)))
|
|
|
- .thenThrow(new RuntimeException("timeout"));
|
|
|
- AjaxResult result = recommendService.recommendEfficiency(req(card("p", "y", "s", "cs")));
|
|
|
- assertNotEquals(0, code(result));
|
|
|
- assertEquals(FAIL_MSG, result.getMsg());
|
|
|
- }
|
|
|
-
|
|
|
- @Test
|
|
|
- void Feign返回非0_透传错误() {
|
|
|
- defaultHappyStubs();
|
|
|
- // 下游返回 error(含具体 msg "下游库满"),生产代码经质量审查后已不再透传下游 msg,
|
|
|
- // 对外统一为固定文案 FAIL_MSG,避免下游信息泄露。故只断言 code 非 0 + 固定文案。
|
|
|
- when(preOrderFeignClient.savePreOrder(anyString(), any(PreOrderSaveDTO.class)))
|
|
|
- .thenReturn(AjaxResult.error("下游库满"));
|
|
|
- AjaxResult result = recommendService.recommendEfficiency(req(card("p", "y", "s", "cs")));
|
|
|
- assertNotEquals(0, code(result));
|
|
|
- assertEquals(FAIL_MSG, result.getMsg());
|
|
|
- }
|
|
|
-
|
|
|
- @Test
|
|
|
- void 落库包_识别字段与关键字段全部透传() {
|
|
|
- defaultHappyStubs();
|
|
|
- // 入参卡设满了全部分类前提字段(cardType/sportEvent/ipName/team/role/rule),
|
|
|
- // 规则须在这些前提字段上同值才能通过前提过滤命中(否则命中不了、matchedCardId 取不到)
|
|
|
- RatingRecommendRulePO ruleWithType = poolRule(1L, 3, "Jordan", "1986", "Fleer", "Base");
|
|
|
- ruleWithType.setCardType(2);
|
|
|
- ruleWithType.setSportEvent("篮球");
|
|
|
- ruleWithType.setIpName("NBA");
|
|
|
- ruleWithType.setTeam("Bulls");
|
|
|
- ruleWithType.setRole("SG");
|
|
|
- ruleWithType.setRule("MVP");
|
|
|
- when(ratingRecommendRuleManager.selectByBatchAndSeriesSets(anyString(), any()))
|
|
|
- .thenReturn(new ArrayList<>(Arrays.asList(ruleWithType)));
|
|
|
- when(preOrderFeignClient.savePreOrder(anyString(), any(PreOrderSaveDTO.class)))
|
|
|
- .thenReturn(okWith("PRE999"));
|
|
|
-
|
|
|
- // 构造含 10 个补齐识别字段 + id/price 的入参卡
|
|
|
- RecommendCardDTO c = card("Jordan", "1986", "Fleer", "Base");
|
|
|
- c.setSportEvent("篮球");
|
|
|
- c.setCardType(2);
|
|
|
- c.setRole("SG");
|
|
|
- c.setCardNo("NO-1");
|
|
|
- c.setTeam("Bulls");
|
|
|
- c.setLimitId("/99");
|
|
|
- c.setIpName("NBA");
|
|
|
- c.setRule("MVP");
|
|
|
- c.setRarity("SSR");
|
|
|
- c.setMaterial("金箔");
|
|
|
- // DTO 字段已重命名为 packageId/packagePrice(原 id/price),对齐真实签名
|
|
|
- c.setPackageId(88L);
|
|
|
- c.setPackagePrice(new BigDecimal("120"));
|
|
|
-
|
|
|
- recommendService.recommendEfficiency(req(c));
|
|
|
-
|
|
|
- // 抓取实际传给 Feign 的 PreOrderSaveResponseDTO
|
|
|
- ArgumentCaptor<PreOrderSaveDTO> captor = ArgumentCaptor.forClass(PreOrderSaveDTO.class);
|
|
|
- verify(preOrderFeignClient).savePreOrder(anyString(), captor.capture());
|
|
|
- PreOrderSaveDTO sent = captor.getValue();
|
|
|
-
|
|
|
- // 主表汇总字段
|
|
|
- assertEquals(1001L, sent.getUserId());
|
|
|
- assertEquals(1, sent.getTotalCards());
|
|
|
-
|
|
|
- // 卡片明细:10 识别字段全部透传(sportEvent/cardType/role/cardNo/team/limitId/ipName/rule/rarity/material)
|
|
|
- PreOrderCardDTO card = sent.getGroups().get(0).getCards().get(0);
|
|
|
- assertEquals("篮球", card.getSportEvent());
|
|
|
- assertEquals(2, card.getCardType());
|
|
|
- assertEquals("SG", card.getRole());
|
|
|
- assertEquals("NO-1", card.getCardNo());
|
|
|
- assertEquals("Bulls", card.getTeam());
|
|
|
- assertEquals("/99", card.getLimitId());
|
|
|
- assertEquals("NBA", card.getIpName());
|
|
|
- // 入参 rule -> VO roleName -> PreOrderCardDTO.rule(往返映射)
|
|
|
- assertEquals("MVP", card.getRule());
|
|
|
- assertEquals("SSR", card.getRarity());
|
|
|
- assertEquals("金箔", card.getMaterial());
|
|
|
- // 套餐/命中:入参 id -> packageId;规则 cardId -> matchedCardId
|
|
|
- assertEquals(88L, card.getPackageId());
|
|
|
- assertEquals("DW-CARD-1", card.getMatchedCardId());
|
|
|
- // 命中档位 3(闪评)应装入卡的 evaluateEfficiencyId
|
|
|
- assertEquals(3L, card.getEvaluateEfficiencyId());
|
|
|
- // 时效价精确断言:字典档 3 → 200.00(compareTo 规避 scale 差异)
|
|
|
- assertEquals(0, card.getEfficiencyPrice().compareTo(new BigDecimal("200.00")));
|
|
|
- }
|
|
|
-
|
|
|
- @Test
|
|
|
- void 未命中_按cardType兜底默认档() {
|
|
|
- defaultHappyStubs();
|
|
|
- // 池为空 → 全部兜底;配置 cardType=1 默认档=2
|
|
|
- when(ratingRecommendRuleManager.selectByBatchAndSeriesSets(anyString(), any()))
|
|
|
- .thenReturn(new ArrayList<>());
|
|
|
- Map<Integer, Long> byType = new HashMap<>();
|
|
|
- byType.put(1, 2L);
|
|
|
- when(recommendProperties.getDefaultEfficiencyByCardType()).thenReturn(byType);
|
|
|
- // 字典含档 2 的价,保证能报价、能落库
|
|
|
- Map<Long, EfficiencyDictItem> dict = new HashMap<>();
|
|
|
- dict.put(2L, new EfficiencyDictItem("快速", new BigDecimal("100.00")));
|
|
|
- when(efficiencyPriceProvider.loadEfficiencyDict()).thenReturn(dict);
|
|
|
- when(preOrderFeignClient.savePreOrder(anyString(), any(PreOrderSaveDTO.class)))
|
|
|
- .thenReturn(okWith("PRE-DEF"));
|
|
|
-
|
|
|
- RecommendCardDTO c = card("Nobody", "2099", "NoSeries", "NoSet");
|
|
|
- c.setCardType(1);
|
|
|
- recommendService.recommendEfficiency(req(c));
|
|
|
-
|
|
|
- ArgumentCaptor<PreOrderSaveDTO> captor = ArgumentCaptor.forClass(PreOrderSaveDTO.class);
|
|
|
- verify(preOrderFeignClient).savePreOrder(anyString(), captor.capture());
|
|
|
- PreOrderCardDTO sent = captor.getValue().getGroups().get(0).getCards().get(0);
|
|
|
- // 兜底档 2 应装入卡的 evaluateEfficiencyId
|
|
|
- assertEquals(2L, sent.getEvaluateEfficiencyId());
|
|
|
- }
|
|
|
-
|
|
|
- @Test
|
|
|
- void 批量查询_每次请求只查一次规则_且按seriesSet去重() {
|
|
|
- defaultHappyStubs();
|
|
|
- when(preOrderFeignClient.savePreOrder(anyString(), any(PreOrderSaveDTO.class)))
|
|
|
- .thenReturn(okWith("PRE-ONCE"));
|
|
|
- // 3 张同 (series,cardSet) 卡 → 去重后只有 1 个 key,且只调 1 次批量查询
|
|
|
- recommendService.recommendEfficiency(req(
|
|
|
- card("Jordan", "1986", "Fleer", "Base"),
|
|
|
- card("Pippen", "1986", "Fleer", "Base"),
|
|
|
- card("Rodman", "1986", "Fleer", "Base")));
|
|
|
- // 抓取批量查询的 keys 入参,锁住 distinct 去重行为(删掉生产的 .distinct() 本用例会失败)
|
|
|
- @SuppressWarnings("unchecked")
|
|
|
- ArgumentCaptor<java.util.List<SeriesCardSetKey>> keysCaptor =
|
|
|
- ArgumentCaptor.forClass(java.util.List.class);
|
|
|
- verify(ratingRecommendRuleManager, org.mockito.Mockito.times(1))
|
|
|
- .selectByBatchAndSeriesSets(anyString(), keysCaptor.capture());
|
|
|
- // 3 张同系列+卡种 → 去重后 keys 只剩 1 个
|
|
|
- assertEquals(1, keysCaptor.getValue().size());
|
|
|
- }
|
|
|
-}
|