|
@@ -0,0 +1,126 @@
|
|
|
|
|
+package com.etotem.cfc.unit;
|
|
|
|
|
+
|
|
|
|
|
+import com.baomidou.mybatisplus.core.conditions.Wrapper;
|
|
|
|
|
+import com.etotem.cfc.entity.ProductBundleItem;
|
|
|
|
|
+import com.etotem.cfc.entity.ProductSku;
|
|
|
|
|
+import com.etotem.cfc.mapper.ProductBundleItemMapper;
|
|
|
|
|
+import com.etotem.cfc.pricing.BundleDiscountResult;
|
|
|
|
|
+import com.etotem.cfc.pricing.LineItem;
|
|
|
|
|
+import com.etotem.cfc.service.BundlePricingService;
|
|
|
|
|
+import com.etotem.cfc.service.ProductSkuService;
|
|
|
|
|
+import org.junit.jupiter.api.Test;
|
|
|
|
|
+import org.junit.jupiter.api.extension.ExtendWith;
|
|
|
|
|
+import org.mockito.InjectMocks;
|
|
|
|
|
+import org.mockito.Mock;
|
|
|
|
|
+import org.mockito.junit.jupiter.MockitoExtension;
|
|
|
|
|
+
|
|
|
|
|
+import java.util.Arrays;
|
|
|
|
|
+import java.util.Collections;
|
|
|
|
|
+import java.util.List;
|
|
|
|
|
+
|
|
|
|
|
+import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
|
|
|
+import static org.junit.jupiter.api.Assertions.assertFalse;
|
|
|
|
|
+import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
|
|
|
+import static org.mockito.ArgumentMatchers.any;
|
|
|
|
|
+import static org.mockito.Mockito.when;
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * BundlePricingService 映射单测:装载 bundle_sku_id 非空行、组装 BundleDef、委托计算。
|
|
|
|
|
+ */
|
|
|
|
|
+@ExtendWith(MockitoExtension.class)
|
|
|
|
|
+class BundlePricingServiceTest {
|
|
|
|
|
+
|
|
|
|
|
+ @Mock
|
|
|
|
|
+ private ProductBundleItemMapper productBundleItemMapper;
|
|
|
|
|
+ @Mock
|
|
|
|
|
+ private ProductSkuService productSkuService;
|
|
|
|
|
+ @InjectMocks
|
|
|
|
|
+ private BundlePricingService service;
|
|
|
|
|
+
|
|
|
|
|
+ private ProductBundleItem row(Long bundleSkuId, Long childProductId, Long childSkuId, int qty) {
|
|
|
|
|
+ ProductBundleItem r = new ProductBundleItem();
|
|
|
|
|
+ r.setBundleSkuId(bundleSkuId);
|
|
|
|
|
+ r.setChildProductId(childProductId);
|
|
|
|
|
+ r.setChildSkuId(childSkuId);
|
|
|
|
|
+ r.setQuantity(qty);
|
|
|
|
|
+ return r;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private ProductSku sku(Long id, String label, int price) {
|
|
|
|
|
+ ProductSku s = new ProductSku();
|
|
|
|
|
+ s.setId(id);
|
|
|
|
|
+ s.setLabel(label);
|
|
|
|
|
+ s.setPrice(price);
|
|
|
|
|
+ return s;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Test
|
|
|
|
|
+ void noPricingRows_inactive() {
|
|
|
|
|
+ when(productBundleItemMapper.selectList(any())).thenReturn(Collections.emptyList());
|
|
|
|
|
+ BundleDiscountResult r = service.compute(Collections.singletonList(
|
|
|
|
|
+ new LineItem(10L, null, 2, 69900)));
|
|
|
|
|
+ assertFalse(r.isActive());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Test
|
|
|
|
|
+ void assembleDefsAndDelegate_hit() {
|
|
|
|
|
+ when(productBundleItemMapper.selectList(any(Wrapper.class))).thenReturn(
|
|
|
|
|
+ Collections.singletonList(row(3L, 10L, null, 2)));
|
|
|
|
|
+ when(productSkuService.getById(3L)).thenReturn(sku(3L, "套餐一", 128000));
|
|
|
|
|
+
|
|
|
|
|
+ BundleDiscountResult r = service.compute(Collections.singletonList(
|
|
|
|
|
+ new LineItem(10L, null, 2, 69900)));
|
|
|
|
|
+
|
|
|
|
|
+ assertTrue(r.isActive());
|
|
|
|
|
+ assertEquals(11800, r.getDiscount());
|
|
|
|
|
+ assertEquals("套餐一", r.getBundleList().get(0).getBundleName());
|
|
|
|
|
+ assertEquals(1, r.getBundleList().get(0).getTimes());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Test
|
|
|
|
|
+ void anchorSkuMissing_skipDef() {
|
|
|
|
|
+ when(productBundleItemMapper.selectList(any(Wrapper.class))).thenReturn(
|
|
|
|
|
+ Collections.singletonList(row(3L, 10L, null, 2)));
|
|
|
|
|
+ when(productSkuService.getById(3L)).thenReturn(null);
|
|
|
|
|
+
|
|
|
|
|
+ BundleDiscountResult r = service.compute(Collections.singletonList(
|
|
|
|
|
+ new LineItem(10L, null, 2, 69900)));
|
|
|
|
|
+
|
|
|
|
|
+ assertFalse(r.isActive());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Test
|
|
|
|
|
+ void multipleChildrenSameDef_merged() {
|
|
|
|
|
+ // 同一套餐锚点两行子商品:childA(10,qty2) + childB(20,qty1)
|
|
|
|
|
+ when(productBundleItemMapper.selectList(any(Wrapper.class))).thenReturn(Arrays.asList(
|
|
|
|
|
+ row(3L, 10L, null, 2),
|
|
|
|
|
+ row(3L, 20L, null, 1)));
|
|
|
|
|
+ when(productSkuService.getById(3L)).thenReturn(sku(3L, "套餐一", 128000));
|
|
|
|
|
+
|
|
|
|
|
+ BundleDiscountResult r = service.compute(Arrays.asList(
|
|
|
|
|
+ new LineItem(10L, null, 2, 69900),
|
|
|
|
|
+ new LineItem(20L, null, 1, 50000)));
|
|
|
|
|
+
|
|
|
|
|
+ assertTrue(r.isActive());
|
|
|
|
|
+ assertEquals(1, r.getBundleList().size());
|
|
|
|
|
+ // 原价 = 2×69900 + 1×50000 = 189800;省 = 189800 - 128000 = 61800
|
|
|
|
|
+ assertEquals(61800, r.getDiscount());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Test
|
|
|
|
|
+ void labelFallbackToSpecs() {
|
|
|
|
|
+ when(productBundleItemMapper.selectList(any(Wrapper.class))).thenReturn(
|
|
|
|
|
+ Collections.singletonList(row(3L, 10L, null, 2)));
|
|
|
|
|
+ ProductSku s = new ProductSku();
|
|
|
|
|
+ s.setId(3L);
|
|
|
|
|
+ s.setLabel(null);
|
|
|
|
|
+ s.setSpecs("成长烦恼、学业提升");
|
|
|
|
|
+ s.setPrice(128000);
|
|
|
|
|
+ when(productSkuService.getById(3L)).thenReturn(s);
|
|
|
|
|
+
|
|
|
|
|
+ BundleDiscountResult r = service.compute(Collections.singletonList(
|
|
|
|
|
+ new LineItem(10L, null, 2, 69900)));
|
|
|
|
|
+
|
|
|
|
|
+ assertEquals("成长烦恼、学业提升", r.getBundleList().get(0).getBundleName());
|
|
|
|
|
+ }
|
|
|
|
|
+}
|