|
@@ -0,0 +1,234 @@
|
|
|
|
|
+package com.etotem.cfc.unit;
|
|
|
|
|
+
|
|
|
|
|
+import com.baomidou.mybatisplus.core.MybatisConfiguration;
|
|
|
|
|
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
|
|
+import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
|
|
|
|
|
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
|
|
|
|
+import com.etotem.cfc.common.Result;
|
|
|
|
|
+import com.etotem.cfc.controller.CfBalanceController;
|
|
|
|
|
+import com.etotem.cfc.dto.CfBalanceLogVO;
|
|
|
|
|
+import com.etotem.cfc.dto.CfBalanceView;
|
|
|
|
|
+import com.etotem.cfc.entity.CfBalanceLog;
|
|
|
|
|
+import com.etotem.cfc.entity.FamilyMember;
|
|
|
|
|
+import com.etotem.cfc.mapper.FamilyMemberMapper;
|
|
|
|
|
+import com.etotem.cfc.service.CfBalanceService;
|
|
|
|
|
+import com.etotem.cfc.service.CouponService;
|
|
|
|
|
+import org.apache.ibatis.builder.MapperBuilderAssistant;
|
|
|
|
|
+import org.junit.jupiter.api.BeforeAll;
|
|
|
|
|
+import org.junit.jupiter.api.BeforeEach;
|
|
|
|
|
+import org.junit.jupiter.api.Test;
|
|
|
|
|
+import org.mockito.ArgumentCaptor;
|
|
|
|
|
+
|
|
|
|
|
+import java.lang.reflect.Field;
|
|
|
|
|
+import java.util.Arrays;
|
|
|
|
|
+import java.util.Collections;
|
|
|
|
|
+import java.util.Date;
|
|
|
|
|
+import java.util.HashMap;
|
|
|
|
|
+import java.util.List;
|
|
|
|
|
+import java.util.Map;
|
|
|
|
|
+
|
|
|
|
|
+import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
|
|
|
+import static org.junit.jupiter.api.Assertions.assertNotNull;
|
|
|
|
|
+import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
|
|
|
+import static org.mockito.ArgumentMatchers.any;
|
|
|
|
|
+import static org.mockito.ArgumentMatchers.anyInt;
|
|
|
|
|
+import static org.mockito.ArgumentMatchers.anyLong;
|
|
|
|
|
+import static org.mockito.Mockito.mock;
|
|
|
|
|
+import static org.mockito.Mockito.never;
|
|
|
|
|
+import static org.mockito.Mockito.verify;
|
|
|
|
|
+import static org.mockito.Mockito.when;
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * C 端 CF 查询接口单测(F-7a)。
|
|
|
|
|
+ *
|
|
|
|
|
+ * <p>覆盖:登录 userId → memberId 解析(命中/未命中/查询口径 LIMIT 1)、
|
|
|
|
|
+ * balance / logs 响应结构、家庭券列表透传。</p>
|
|
|
|
|
+ */
|
|
|
|
|
+class CfBalanceControllerTest {
|
|
|
|
|
+
|
|
|
|
|
+ private CfBalanceController controller;
|
|
|
|
|
+ private CfBalanceService cfBalanceService;
|
|
|
|
|
+ private CouponService couponService;
|
|
|
|
|
+ private FamilyMemberMapper familyMemberMapper;
|
|
|
|
|
+
|
|
|
|
|
+ @BeforeAll
|
|
|
|
|
+ static void initMybatisPlusTableInfo() {
|
|
|
|
|
+ // 初始化 MP TableInfo 缓存,使 LambdaQueryWrapper.getSqlSegment() 在无 Spring 上下文中可用
|
|
|
|
|
+ TableInfoHelper.initTableInfo(
|
|
|
|
|
+ new MapperBuilderAssistant(new MybatisConfiguration(), ""), FamilyMember.class);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @BeforeEach
|
|
|
|
|
+ void setUp() throws Exception {
|
|
|
|
|
+ controller = new CfBalanceController();
|
|
|
|
|
+ cfBalanceService = mock(CfBalanceService.class);
|
|
|
|
|
+ couponService = mock(CouponService.class);
|
|
|
|
|
+ familyMemberMapper = mock(FamilyMemberMapper.class);
|
|
|
|
|
+ inject(controller, "cfBalanceService", cfBalanceService);
|
|
|
|
|
+ inject(controller, "couponService", couponService);
|
|
|
|
|
+ inject(controller, "familyMemberMapper", familyMemberMapper);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static void inject(Object target, String field, Object value) throws Exception {
|
|
|
|
|
+ Field f = target.getClass().getDeclaredField(field);
|
|
|
|
|
+ f.setAccessible(true);
|
|
|
|
|
+ f.set(target, value);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private FamilyMember member(Long id, Long userId, Long familyId) {
|
|
|
|
|
+ FamilyMember m = new FamilyMember();
|
|
|
|
|
+ m.setId(id);
|
|
|
|
|
+ m.setUserId(userId);
|
|
|
|
|
+ m.setFamilyId(familyId);
|
|
|
|
|
+ return m;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // ==================== memberId 解析(命中/未命中/口径) ====================
|
|
|
|
|
+
|
|
|
|
|
+ @Test
|
|
|
|
|
+ void resolveMemberId_命中_按user_id取最小id() {
|
|
|
|
|
+ when(familyMemberMapper.selectOne(any())).thenReturn(member(42L, 7L, 9L));
|
|
|
|
|
+ CfBalanceView view = new CfBalanceView();
|
|
|
|
|
+ view.setMemberId(42L);
|
|
|
|
|
+ when(cfBalanceService.getBalance(42L)).thenReturn(view);
|
|
|
|
|
+
|
|
|
|
|
+ Result<CfBalanceView> r = controller.balance(7L);
|
|
|
|
|
+
|
|
|
|
|
+ assertEquals(200, r.getCode());
|
|
|
|
|
+ assertEquals(42L, r.getData().getMemberId());
|
|
|
|
|
+
|
|
|
|
|
+ // 口径与 CfEarnDispatchService.resolveMemberId 一致:user_id 匹配 + LIMIT 1
|
|
|
|
|
+ ArgumentCaptor<LambdaQueryWrapper<FamilyMember>> captor =
|
|
|
|
|
+ ArgumentCaptor.forClass(LambdaQueryWrapper.class);
|
|
|
|
|
+ verify(familyMemberMapper).selectOne(captor.capture());
|
|
|
|
|
+ String sql = captor.getValue().getSqlSegment();
|
|
|
|
|
+ assertTrue(sql.contains("user_id"), "应按 user_id 匹配,实际: " + sql);
|
|
|
|
|
+ assertTrue(sql.toUpperCase().contains("LIMIT 1"), "应带 LIMIT 1,实际: " + sql);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Test
|
|
|
|
|
+ void resolveMemberId_未命中_返回404且不查账本() {
|
|
|
|
|
+ when(familyMemberMapper.selectOne(any())).thenReturn(null);
|
|
|
|
|
+
|
|
|
|
|
+ Result<CfBalanceView> r = controller.balance(99L);
|
|
|
|
|
+
|
|
|
|
|
+ assertEquals(404, r.getCode());
|
|
|
|
|
+ verify(cfBalanceService, never()).getBalance(anyLong());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Test
|
|
|
|
|
+ void resolveMemberId_userId非法_返回404() {
|
|
|
|
|
+ Result<CfBalanceView> r = controller.balance(-1L);
|
|
|
|
|
+ assertEquals(404, r.getCode());
|
|
|
|
|
+ verify(familyMemberMapper, never()).selectOne(any());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // ==================== balance 响应结构 ====================
|
|
|
|
|
+
|
|
|
|
|
+ @Test
|
|
|
|
|
+ void balance_命中_快照字段透传() {
|
|
|
|
|
+ when(familyMemberMapper.selectOne(any())).thenReturn(member(42L, 7L, 9L));
|
|
|
|
|
+ CfBalanceView view = new CfBalanceView();
|
|
|
|
|
+ view.setMemberId(42L);
|
|
|
|
|
+ view.setAvailable(100);
|
|
|
|
|
+ view.setFrozen(10);
|
|
|
|
|
+ view.setTotalEarned(500);
|
|
|
|
|
+ view.setTotalWithdrawn(20);
|
|
|
|
|
+ view.setExchanged(370);
|
|
|
|
|
+ when(cfBalanceService.getBalance(42L)).thenReturn(view);
|
|
|
|
|
+
|
|
|
|
|
+ Result<CfBalanceView> r = controller.balance(7L);
|
|
|
|
|
+ CfBalanceView data = r.getData();
|
|
|
|
|
+
|
|
|
|
|
+ assertEquals(200, r.getCode());
|
|
|
|
|
+ assertEquals(42L, data.getMemberId());
|
|
|
|
|
+ assertEquals(100, data.getAvailable());
|
|
|
|
|
+ assertEquals(10, data.getFrozen());
|
|
|
|
|
+ assertEquals(500, data.getTotalEarned());
|
|
|
|
|
+ assertEquals(20, data.getTotalWithdrawn());
|
|
|
|
|
+ assertEquals(370, data.getExchanged());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // ==================== logs 响应结构 ====================
|
|
|
|
|
+
|
|
|
|
|
+ @Test
|
|
|
|
|
+ void logs_命中_分页与流水字段映射() {
|
|
|
|
|
+ when(familyMemberMapper.selectOne(any())).thenReturn(member(42L, 7L, 9L));
|
|
|
|
|
+ CfBalanceLog log = new CfBalanceLog();
|
|
|
|
|
+ log.setId(1L);
|
|
|
|
|
+ log.setType("earn");
|
|
|
|
|
+ log.setRefType("F02");
|
|
|
|
|
+ log.setAmount(100);
|
|
|
|
|
+ log.setBalanceAfter(200);
|
|
|
|
|
+ log.setRemark("消费返CF");
|
|
|
|
|
+ log.setCreatedAt(new Date());
|
|
|
|
|
+ Page<CfBalanceLog> raw = new Page<>(1, 20, 1L);
|
|
|
|
|
+ raw.setRecords(Collections.singletonList(log));
|
|
|
|
|
+ when(cfBalanceService.getLogs(42L, 1, 20)).thenReturn(raw);
|
|
|
|
|
+
|
|
|
|
|
+ Result<Page<CfBalanceLogVO>> r = controller.logs(7L, 1, 20);
|
|
|
|
|
+
|
|
|
|
|
+ assertEquals(200, r.getCode());
|
|
|
|
|
+ Page<CfBalanceLogVO> data = r.getData();
|
|
|
|
|
+ assertEquals(1L, data.getTotal());
|
|
|
|
|
+ assertEquals(1, data.getRecords().size());
|
|
|
|
|
+ CfBalanceLogVO vo = data.getRecords().get(0);
|
|
|
|
|
+ assertEquals(1L, vo.getId());
|
|
|
|
|
+ assertEquals("earn", vo.getType());
|
|
|
|
|
+ assertEquals("F02", vo.getRefType());
|
|
|
|
|
+ assertEquals(100, vo.getAmount());
|
|
|
|
|
+ assertEquals(200, vo.getBalanceAfter());
|
|
|
|
|
+ assertEquals("消费返CF", vo.getRemark());
|
|
|
|
|
+ assertNotNull(vo.getCreatedAt());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Test
|
|
|
|
|
+ void logs_未命中_返回404且不查流水() {
|
|
|
|
|
+ when(familyMemberMapper.selectOne(any())).thenReturn(null);
|
|
|
|
|
+
|
|
|
|
|
+ Result<Page<CfBalanceLogVO>> r = controller.logs(99L, 1, 20);
|
|
|
|
|
+
|
|
|
|
|
+ assertEquals(404, r.getCode());
|
|
|
|
|
+ verify(cfBalanceService, never()).getLogs(anyLong(), anyInt(), anyInt());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Test
|
|
|
|
|
+ void logs_空流水_返回空列表() {
|
|
|
|
|
+ when(familyMemberMapper.selectOne(any())).thenReturn(member(42L, 7L, 9L));
|
|
|
|
|
+ Page<CfBalanceLog> raw = new Page<>(1, 20, 0L);
|
|
|
|
|
+ raw.setRecords(Collections.emptyList());
|
|
|
|
|
+ when(cfBalanceService.getLogs(42L, 1, 20)).thenReturn(raw);
|
|
|
|
|
+
|
|
|
|
|
+ Result<Page<CfBalanceLogVO>> r = controller.logs(7L, 1, 20);
|
|
|
|
|
+
|
|
|
|
|
+ assertEquals(200, r.getCode());
|
|
|
|
|
+ assertNotNull(r.getData().getRecords());
|
|
|
|
|
+ assertEquals(0, r.getData().getRecords().size());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // ==================== family-coupons ====================
|
|
|
|
|
+
|
|
|
|
|
+ @Test
|
|
|
|
|
+ void familyCoupons_返回家庭共享券列表() {
|
|
|
|
|
+ Map<String, Object> coupon1 = new HashMap<>();
|
|
|
|
|
+ coupon1.put("id", 11L);
|
|
|
|
|
+ coupon1.put("name", "满100减10");
|
|
|
|
|
+ coupon1.put("status", "AVAILABLE");
|
|
|
|
|
+ coupon1.put("value", 1000);
|
|
|
|
|
+ coupon1.put("validUntil", new Date());
|
|
|
|
|
+ Map<String, Object> coupon2 = new HashMap<>();
|
|
|
|
|
+ coupon2.put("id", 12L);
|
|
|
|
|
+ coupon2.put("name", "9折券");
|
|
|
|
|
+ coupon2.put("status", "USED");
|
|
|
|
|
+ when(couponService.listMyCoupons(7L))
|
|
|
|
|
+ .thenReturn(Arrays.asList(coupon1, coupon2));
|
|
|
|
|
+
|
|
|
|
|
+ Result<List<Map<String, Object>>> r = controller.familyCoupons(7L);
|
|
|
|
|
+
|
|
|
|
|
+ assertEquals(200, r.getCode());
|
|
|
|
|
+ assertEquals(2, r.getData().size());
|
|
|
|
|
+ assertEquals("满100减10", r.getData().get(0).get("name"));
|
|
|
|
|
+ assertEquals("USED", r.getData().get(1).get("status"));
|
|
|
|
|
+ verify(couponService).listMyCoupons(7L);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|