PromotionDiscountLimitCheckerTest.java 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. package com.ruoyi.system.service.impl;
  2. import com.ruoyi.common.exception.ServiceException;
  3. import com.ruoyi.system.domain.PosFood;
  4. import com.ruoyi.system.domain.PromotionActivity;
  5. import com.ruoyi.system.domain.PromotionActivityRule;
  6. import com.ruoyi.system.domain.PromotionCouponBatch;
  7. import com.ruoyi.system.domain.PromotionCouponRule;
  8. import com.ruoyi.system.mapper.PosFoodMapper;
  9. import com.ruoyi.system.service.ISysConfigService;
  10. import org.junit.jupiter.api.BeforeEach;
  11. import org.junit.jupiter.api.Test;
  12. import org.junit.jupiter.api.extension.ExtendWith;
  13. import org.mockito.InjectMocks;
  14. import org.mockito.Mock;
  15. import org.mockito.junit.jupiter.MockitoExtension;
  16. import java.math.BigDecimal;
  17. import java.util.List;
  18. import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
  19. import static org.junit.jupiter.api.Assertions.assertEquals;
  20. import static org.junit.jupiter.api.Assertions.assertThrows;
  21. import static org.mockito.Mockito.lenient;
  22. import static org.mockito.Mockito.when;
  23. import static org.mockito.Mockito.reset;
  24. @ExtendWith(MockitoExtension.class)
  25. class PromotionDiscountLimitCheckerTest {
  26. @Mock
  27. private ISysConfigService configService;
  28. @Mock
  29. private PosFoodMapper posFoodMapper;
  30. @InjectMocks
  31. private PromotionDiscountLimitChecker checker;
  32. @BeforeEach
  33. void enableTwentyPercentLimit() {
  34. when(configService.selectConfigByKey("promotion.discount.limit.enabled"))
  35. .thenReturn("true");
  36. // type=5 满额免运创建侧不读比例(不预估免运额),lenient 避免严格stub误报
  37. lenient().when(configService.selectConfigByKey("promotion.discount.limit.ratio"))
  38. .thenReturn("20");
  39. }
  40. @Test
  41. void fullReductionCouponAllowsExactLimitAndRejectsAmountAboveLimit() {
  42. PromotionCouponBatch batch = batch(1);
  43. assertDoesNotThrow(() -> checker.checkCouponBatch(batch,
  44. rule(null, "100", "20", null)));
  45. assertThrows(ServiceException.class, () -> checker.checkCouponBatch(batch,
  46. rule(null, "100", "20.01", null)));
  47. }
  48. @Test
  49. void productDiscountCouponAllowsEightTenthsAndRejectsLowerRate() {
  50. PromotionCouponBatch batch = batch(2);
  51. assertDoesNotThrow(() -> checker.checkCouponBatch(batch,
  52. rule(1L, null, null, "0.80")));
  53. assertThrows(ServiceException.class, () -> checker.checkCouponBatch(batch,
  54. rule(1L, null, null, "0.79")));
  55. }
  56. @Test
  57. void productVoucherUsesCurrentProductPriceAsLimitAnchor() {
  58. when(posFoodMapper.selectById(1L)).thenReturn(food(1L, "100"));
  59. PromotionCouponBatch batch = batch(2);
  60. assertDoesNotThrow(() -> checker.checkCouponBatch(batch,
  61. rule(1L, null, "20", null)));
  62. assertThrows(ServiceException.class, () -> checker.checkCouponBatch(batch,
  63. rule(1L, null, "20.01", null)));
  64. }
  65. @Test
  66. void productVoucherDoesNotRoundNinetyNineDollarLimitUpToTwenty() {
  67. when(posFoodMapper.selectById(1L)).thenReturn(food(1L, "99"));
  68. assertThrows(ServiceException.class, () -> checker.checkCouponBatch(batch(2),
  69. rule(1L, null, "20", null)));
  70. }
  71. @Test
  72. void customThirtyPercentRatioChangesAllowedVoucherAmount() {
  73. when(configService.selectConfigByKey("promotion.discount.limit.ratio"))
  74. .thenReturn("30");
  75. when(posFoodMapper.selectById(1L)).thenReturn(food(1L, "100"));
  76. PromotionCouponBatch batch = batch(2);
  77. assertDoesNotThrow(() -> checker.checkCouponBatch(batch,
  78. rule(1L, null, "30", null)));
  79. assertThrows(ServiceException.class, () -> checker.checkCouponBatch(batch,
  80. rule(1L, null, "30.01", null)));
  81. }
  82. @Test
  83. void invalidRatioFallsBackToTwentyPercent() {
  84. when(configService.selectConfigByKey("promotion.discount.limit.ratio"))
  85. .thenReturn("not-a-number");
  86. assertEquals(new BigDecimal("20"), checker.getRatioPercent());
  87. assertThrows(ServiceException.class, () -> checker.checkCouponBatch(batch(1),
  88. rule(null, "100", "21", null)));
  89. }
  90. @Test
  91. void disabledLimitPreservesPreviousCouponBehavior() {
  92. reset(configService);
  93. when(configService.selectConfigByKey("promotion.discount.limit.enabled"))
  94. .thenReturn("false");
  95. assertDoesNotThrow(() -> checker.checkCouponBatch(batch(1),
  96. rule(null, "100", "90", null)));
  97. }
  98. @Test
  99. void freeDeliveryCouponRequiresPositiveThreshold() {
  100. assertThrows(ServiceException.class, () -> checker.checkCouponBatch(batch(3),
  101. rule(null, null, "10", null)));
  102. }
  103. @Test
  104. void multiProductCouponRejectsBatchWhenAnyProductExceedsLimit() {
  105. when(posFoodMapper.selectById(1L)).thenReturn(food(1L, "100"));
  106. when(posFoodMapper.selectById(2L)).thenReturn(food(2L, "50"));
  107. List<PromotionCouponRule> rules = List.of(
  108. rule(1L, null, "20", null),
  109. rule(2L, null, "11", null));
  110. assertThrows(ServiceException.class,
  111. () -> checker.checkCouponBatch(batch(2), rules));
  112. }
  113. @Test
  114. void freeDeliveryActivityCreationSkipsReductionRatioCheck() {
  115. // T074:type=5 满额免运只有门槛没有减免额,创建侧不预估/不限制实际免运额
  116. PromotionActivity activity = new PromotionActivity();
  117. activity.setType(5);
  118. PromotionActivityRule rule = new PromotionActivityRule();
  119. rule.setThreshold(new BigDecimal("100"));
  120. assertDoesNotThrow(() -> checker.checkActivity(activity, List.of(rule)));
  121. }
  122. private PromotionCouponBatch batch(int type) {
  123. PromotionCouponBatch batch = new PromotionCouponBatch();
  124. batch.setCouponType(type);
  125. return batch;
  126. }
  127. private PromotionCouponRule rule(Long productId, String threshold, String amount,
  128. String discountRate) {
  129. PromotionCouponRule rule = new PromotionCouponRule();
  130. rule.setProductId(productId);
  131. rule.setThreshold(decimal(threshold));
  132. rule.setAmount(decimal(amount));
  133. rule.setDiscountRate(decimal(discountRate));
  134. return rule;
  135. }
  136. private PosFood food(Long id, String price) {
  137. PosFood food = new PosFood();
  138. food.setId(id);
  139. food.setName("food-" + id);
  140. food.setPrice(new BigDecimal(price));
  141. return food;
  142. }
  143. private BigDecimal decimal(String value) {
  144. return value == null ? null : new BigDecimal(value);
  145. }
  146. }