فهرست منبع

实时红包测试

xw 1 هفته پیش
والد
کامیت
6bfbd5cbd2
2فایلهای تغییر یافته به همراه331 افزوده شده و 0 حذف شده
  1. 5 0
      fs-user-app/pom.xml
  2. 326 0
      fs-user-app/src/test/java/com/fs/app/course/RedPacketBalanceZeroTest.java

+ 5 - 0
fs-user-app/pom.xml

@@ -108,6 +108,11 @@
             <artifactId>rocketmq-spring-boot-starter</artifactId>
             <version>2.2.3</version>
         </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-test</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
     <build>

+ 326 - 0
fs-user-app/src/test/java/com/fs/app/course/RedPacketBalanceZeroTest.java

@@ -0,0 +1,326 @@
+package com.fs.app.course;
+
+import com.fs.FsUserAppApplication;
+import com.fs.common.constant.FsConstants;
+import com.fs.common.core.redis.RedisCache;
+import com.fs.common.core.domain.R;
+import com.fs.course.mapper.FsCourseRedPacketLogMapper;
+import com.fs.course.param.FsCourseSendRewardUParam;
+import com.fs.course.service.IFsUserCourseVideoService;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import java.math.BigDecimal;
+
+import static org.junit.Assert.*;
+
+/**
+ * 红包余额为0测试类
+ * 测试场景:公司红包余额为0时,不能发送红包
+ *
+ * @author test
+ * @date 2025-12-30
+ */
+@RunWith(SpringRunner.class)
+@SpringBootTest(classes = FsUserAppApplication.class)
+public class RedPacketBalanceZeroTest {
+
+    @Autowired
+    private IFsUserCourseVideoService courseVideoService;
+
+    @Autowired
+    private RedisCache redisCache;
+
+    @Autowired
+    private FsCourseRedPacketLogMapper redPacketLogMapper;
+
+    // ==================== 测试数据配置 ====================
+    private static final Long TEST_COMPANY_ID = 300L;        // 测试公司ID(请根据实际调整)
+    private static final Long TEST_COURSE_ID = 104L;       // 测试课程ID
+    private static final Long TEST_VIDEO_ID = 3877L;         // 测试视频ID
+    private static final Long TEST_USER_ID = 459809L;        // 测试用户ID
+    private static final Long TEST_COMPANY_USER_ID = 9587L; // 测试公司用户ID
+    private static final Long TEST_PERIOD_ID = 705L;         // 测试期数ID
+
+    private String companyMoneyKey;
+    private String originalBalance; // 保存原始余额,用于恢复
+
+    @Before
+    public void setUp() {
+        System.out.println("\n" + repeat("=", 60));
+        System.out.println("    开始测试:红包余额为0不能发放红包");
+        System.out.println(repeat("=", 60));
+
+        // 初始化Redis Key
+        companyMoneyKey = FsConstants.COMPANY_MONEY_KEY + TEST_COMPANY_ID;
+
+        // 保存原始余额
+        originalBalance = redisCache.getCacheObject(companyMoneyKey);
+        System.out.println("📌 测试准备:保存原始余额 = " + originalBalance);
+    }
+
+    /**
+     * 核心测试用例:余额为0时不能发送红包
+     * 验证点:
+     * 1. 设置余额为0后,发送红包应返回错误
+     * 2. 错误信息应包含"余额不足"
+     * 3. Redis余额应保持为0,未被扣减
+     * 4. 数据库不应生成成功的红包记录
+     */
+    @Test
+    public void testSendRedPacketWhenBalanceIsZero() {
+        System.out.println("\n🧪 测试用例:余额为0时不能发送红包");
+        System.out.println(repeat("-", 60));
+
+        // ==================== 步骤1:设置Redis余额为0 ====================
+        System.out.println("\n📍 步骤1:设置Redis余额为0");
+        redisCache.setCacheObject(companyMoneyKey, "0");
+
+        String currentBalance = redisCache.getCacheObject(companyMoneyKey);
+        System.out.println("   Redis Key: " + companyMoneyKey);
+        System.out.println("   当前余额: " + currentBalance);
+        assertEquals("初始余额应为0", "0", currentBalance);
+        System.out.println("   ✅ 余额设置成功");
+
+        // ==================== 步骤2:构造请求参数 ====================
+        System.out.println("\n📍 步骤2:构造发送红包请求参数");
+        FsCourseSendRewardUParam param = new FsCourseSendRewardUParam();
+        param.setCourseId(TEST_COURSE_ID);
+        param.setVideoId(TEST_VIDEO_ID);
+        param.setUserId(TEST_USER_ID);
+        param.setCompanyId(TEST_COMPANY_ID);
+        param.setCompanyUserId(TEST_COMPANY_USER_ID);
+        param.setPeriodId(TEST_PERIOD_ID);
+        param.setQwUserId("test_qw_user");
+        param.setAppId("test_app_id");
+        param.setQwExternalId(1L);
+
+        System.out.println("   参数信息:");
+        System.out.println("   - 公司ID: " + param.getCompanyId());
+        System.out.println("   - 课程ID: " + param.getCourseId());
+        System.out.println("   - 视频ID: " + param.getVideoId());
+        System.out.println("   - 用户ID: " + param.getUserId());
+        System.out.println("   ✅ 参数构造完成");
+
+        // ==================== 步骤3:调用发送红包接口 ====================
+        System.out.println("\n📍 步骤3:调用发送红包接口");
+        R result = null;
+        try {
+            result = courseVideoService.sendReward(param);
+        } catch (Exception e) {
+            System.out.println("   ⚠️  接口调用异常: " + e.getMessage());
+        }
+
+        // ==================== 步骤4:验证返回结果 ====================
+        System.out.println("\n📍 步骤4:验证返回结果");
+
+        assertNotNull("返回结果不应为空", result);
+        System.out.println("   返回码: " + result.get("code"));
+        System.out.println("   返回消息: " + result.get("msg"));
+
+        // 验证返回错误
+        Integer code = (Integer) result.get("code");
+        assertTrue("应返回错误状态码 (500或400)",
+            code == 500 || code == 400);
+
+        // 验证错误消息包含"余额不足"
+        String msg = String.valueOf(result.get("msg"));
+        assertTrue("错误信息应包含'余额不足'",
+            msg != null && msg.contains("余额不足"));
+
+        System.out.println("   ✅ 返回错误提示正确");
+
+        // ==================== 步骤5:验证Redis余额未被扣减 ====================
+        System.out.println("\n📍 步骤5:验证Redis余额未被扣减");
+
+        String finalBalance = redisCache.getCacheObject(companyMoneyKey);
+        System.out.println("   最终余额: " + finalBalance);
+
+        assertEquals("余额应保持为0,未被扣减", "0", finalBalance);
+        System.out.println("   ✅ 余额保持为0,未被扣减");
+
+        // ==================== 步骤6:验证数据库无成功记录 ====================
+        System.out.println("\n📍 步骤6:验证数据库日志");
+        System.out.println("   提示:请手动查询数据库验证");
+        System.out.println("   SQL: SELECT * FROM fs_course_red_packet_log");
+        System.out.println("        WHERE company_id = " + TEST_COMPANY_ID);
+        System.out.println("        ORDER BY create_time DESC LIMIT 5;");
+
+        // ==================== 测试结论 ====================
+        System.out.println("\n" + repeat("=", 60));
+        System.out.println("    ✅ 测试通过:余额为0时成功阻止红包发放");
+        System.out.println(repeat("=", 60));
+    }
+
+    /**
+     * 测试用例2:余额不足时不能发送红包
+     */
+    @Test
+    public void testSendRedPacketWhenBalanceNotEnough() {
+        System.out.println("\n🧪 测试用例:余额不足时不能发送红包");
+        System.out.println(repeat("-", 60));
+
+        // 设置余额为3元(假设红包金额为5元)
+        System.out.println("\n📍 步骤1:设置Redis余额为3元");
+        redisCache.setCacheObject(companyMoneyKey, "3");
+
+        String currentBalance = redisCache.getCacheObject(companyMoneyKey);
+        System.out.println("   当前余额: " + currentBalance);
+
+        // 构造请求
+        System.out.println("\n📍 步骤2:调用发送红包接口");
+        FsCourseSendRewardUParam param = new FsCourseSendRewardUParam();
+        param.setCourseId(TEST_COURSE_ID);
+        param.setVideoId(TEST_VIDEO_ID);
+        param.setUserId(TEST_USER_ID);
+        param.setCompanyId(TEST_COMPANY_ID);
+        param.setCompanyUserId(TEST_COMPANY_USER_ID);
+        param.setPeriodId(TEST_PERIOD_ID);
+        param.setQwUserId("test_qw_user");
+        param.setAppId("test_app_id");
+        param.setQwExternalId(1L);
+
+        R result = courseVideoService.sendReward(param);
+
+        // 验证结果
+        System.out.println("\n📍 步骤3:验证返回结果");
+        System.out.println("   返回码: " + result.get("code"));
+        System.out.println("   返回消息: " + result.get("msg"));
+
+        Integer code = (Integer) result.get("code");
+        assertTrue("应返回错误状态码", code == 500 || code == 400);
+
+        // 验证余额未变化
+        String finalBalance = redisCache.getCacheObject(companyMoneyKey);
+        System.out.println("   最终余额: " + finalBalance);
+
+        System.out.println("\n   ✅ 余额不足时成功阻止红包发放");
+    }
+
+    /**
+     * 测试用例3:余额充足时能正常发送红包(可选,需要真实支付配置)
+     */
+    @Test
+    public void testSendRedPacketWhenBalanceEnough() {
+        System.out.println("\n🧪 测试用例:余额充足时能正常发送红包");
+        System.out.println(repeat("-", 60));
+        System.out.println("⚠️  注意:此测试需要真实的支付配置和红包接口");
+        System.out.println("   建议在测试环境执行,避免产生真实交易");
+
+        // 设置充足余额
+        System.out.println("\n📍 步骤1:设置Redis余额为100元");
+        redisCache.setCacheObject(companyMoneyKey, "100");
+
+        String currentBalance = redisCache.getCacheObject(companyMoneyKey);
+        System.out.println("   当前余额: " + currentBalance);
+
+        // 构造请求
+        FsCourseSendRewardUParam param = new FsCourseSendRewardUParam();
+        param.setCourseId(TEST_COURSE_ID);
+        param.setVideoId(TEST_VIDEO_ID);
+        param.setUserId(TEST_USER_ID);
+        param.setCompanyId(TEST_COMPANY_ID);
+        param.setCompanyUserId(TEST_COMPANY_USER_ID);
+        param.setPeriodId(TEST_PERIOD_ID);
+        param.setQwUserId("test_qw_user");
+        param.setAppId("test_app_id");
+        param.setQwExternalId(1L);
+
+        System.out.println("\n📍 步骤2:调用发送红包接口(跳过真实发送)");
+        System.out.println("   提示:如需完整测试,请配置测试环境的支付接口");
+
+        // 注:此处可根据实际情况决定是否执行
+        // R result = courseVideoService.sendReward(param);
+    }
+
+    /**
+     * 测试用例4:并发场景测试(可选)
+     */
+    @Test
+    public void testConcurrentSendRedPacketWithZeroBalance() {
+        System.out.println("\n🧪 测试用例:并发场景下余额为0的保护");
+        System.out.println(repeat("-", 60));
+
+        // 设置余额为0
+        redisCache.setCacheObject(companyMoneyKey, "0");
+        System.out.println("   初始余额: 0");
+
+        // 模拟3个并发请求
+        int threadCount = 3;
+        System.out.println("\n📍 模拟" + threadCount + "个并发请求");
+
+        for (int i = 1; i <= threadCount; i++) {
+            System.out.println("\n   请求" + i + ":");
+
+            FsCourseSendRewardUParam param = new FsCourseSendRewardUParam();
+            param.setCourseId(TEST_COURSE_ID);
+            param.setVideoId(TEST_VIDEO_ID);
+            param.setUserId(TEST_USER_ID + i); // 不同用户
+            param.setCompanyId(TEST_COMPANY_ID);
+            param.setCompanyUserId(TEST_COMPANY_USER_ID + i);
+            param.setPeriodId(TEST_PERIOD_ID);
+            param.setQwUserId("test_qw_user_" + i);
+            param.setAppId("test_app_id");
+            param.setQwExternalId(1L);
+
+            R result = courseVideoService.sendReward(param);
+            System.out.println("   - 返回码: " + result.get("code"));
+            System.out.println("   - 消息: " + result.get("msg"));
+        }
+
+        // 验证余额仍为0
+        String finalBalance = redisCache.getCacheObject(companyMoneyKey);
+        System.out.println("\n   最终余额: " + finalBalance);
+        assertEquals("并发请求后余额应仍为0", "0", finalBalance);
+
+        System.out.println("   ✅ 并发场景下余额保护正常");
+    }
+
+    @After
+    public void tearDown() {
+        // 恢复原始余额
+        if (originalBalance != null) {
+            System.out.println("\n🔄 测试清理:恢复原始余额 = " + originalBalance);
+            redisCache.setCacheObject(companyMoneyKey, originalBalance);
+        } else {
+            System.out.println("\n🔄 测试清理:删除测试数据");
+            redisCache.deleteObject(companyMoneyKey);
+        }
+
+        System.out.println("✅ 测试清理完成\n");
+    }
+
+    /**
+     * 工具方法:查看当前Redis余额
+     */
+    private void printCurrentBalance() {
+        String balance = redisCache.getCacheObject(companyMoneyKey);
+        System.out.println("💰 当前Redis余额: " + balance);
+    }
+
+    /**
+     * 工具方法:验证余额
+     */
+    private void assertBalance(String expected, String message) {
+        String actual = redisCache.getCacheObject(companyMoneyKey);
+        System.out.println("   预期余额: " + expected);
+        System.out.println("   实际余额: " + actual);
+        assertEquals(message, expected, actual);
+    }
+
+    /**
+     * 工具方法:替代String.repeat()方法 (JDK 8兼容)
+     */
+    private String repeat(String str, int count) {
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < count; i++) {
+            sb.append(str);
+        }
+        return sb.toString();
+    }
+}