WIP
This commit is contained in:
@@ -33,6 +33,7 @@ import org.assertj.core.api.SoftAssertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.web.servlet.MvcResult;
|
||||
|
||||
class WxBlindBoxOrderApiTest extends WxCustomOrderApiTestSupport {
|
||||
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
package com.starry.admin.modules.order.listener;
|
||||
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
|
||||
import com.starry.admin.modules.order.module.dto.OrderRevocationContext;
|
||||
import com.starry.admin.modules.order.module.dto.OrderRevocationContext.EarningsAdjustStrategy;
|
||||
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
|
||||
import com.starry.admin.modules.order.module.event.OrderRevocationEvent;
|
||||
import com.starry.admin.modules.withdraw.service.IEarningsService;
|
||||
import java.math.BigDecimal;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class OrderRevocationEarningsListenerTest {
|
||||
|
||||
@Mock
|
||||
private IEarningsService earningsService;
|
||||
|
||||
private OrderRevocationEarningsListener listener;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
listener = new OrderRevocationEarningsListener(earningsService);
|
||||
}
|
||||
|
||||
@Test
|
||||
void handle_counterStrategyFallsBackToEstimatedRevenueWhenRefundAmountZero() {
|
||||
OrderRevocationContext context = new OrderRevocationContext();
|
||||
context.setOrderId("order-counter-1");
|
||||
context.setOperatorId("admin-op");
|
||||
context.setEarningsStrategy(EarningsAdjustStrategy.COUNTER_TO_PEIPEI);
|
||||
context.setRefundAmount(BigDecimal.ZERO);
|
||||
context.setCounterClerkId("ops-clerk");
|
||||
|
||||
PlayOrderInfoEntity order = new PlayOrderInfoEntity();
|
||||
order.setId("order-counter-1");
|
||||
order.setTenantId("tenant-77");
|
||||
order.setEstimatedRevenue(BigDecimal.valueOf(68));
|
||||
|
||||
listener.handle(new OrderRevocationEvent(context, order));
|
||||
|
||||
verify(earningsService)
|
||||
.createCounterLine(order.getId(), order.getTenantId(), "ops-clerk", BigDecimal.valueOf(68), "admin-op");
|
||||
}
|
||||
|
||||
@Test
|
||||
void handle_reverseStrategyRevertsAvailableLines() {
|
||||
OrderRevocationContext context = new OrderRevocationContext();
|
||||
context.setOrderId("order-reverse-2");
|
||||
context.setOperatorId("admin-reviewer");
|
||||
context.setEarningsStrategy(EarningsAdjustStrategy.REVERSE_CLERK);
|
||||
|
||||
PlayOrderInfoEntity order = new PlayOrderInfoEntity();
|
||||
order.setId("order-reverse-2");
|
||||
|
||||
listener.handle(new OrderRevocationEvent(context, order));
|
||||
|
||||
verify(earningsService).reverseByOrder("order-reverse-2", "admin-reviewer");
|
||||
}
|
||||
|
||||
@Test
|
||||
void handle_noneStrategyIsNoOp() {
|
||||
OrderRevocationContext context = new OrderRevocationContext();
|
||||
context.setOrderId("order-none-3");
|
||||
context.setOperatorId("admin-noop");
|
||||
context.setEarningsStrategy(EarningsAdjustStrategy.NONE);
|
||||
|
||||
PlayOrderInfoEntity order = new PlayOrderInfoEntity();
|
||||
order.setId("order-none-3");
|
||||
|
||||
listener.handle(new OrderRevocationEvent(context, order));
|
||||
|
||||
verifyNoInteractions(earningsService);
|
||||
}
|
||||
}
|
||||
@@ -40,9 +40,12 @@ import com.starry.admin.modules.order.module.dto.OrderCreationContext;
|
||||
import com.starry.admin.modules.order.module.dto.OrderPlacementCommand;
|
||||
import com.starry.admin.modules.order.module.dto.OrderPlacementResult;
|
||||
import com.starry.admin.modules.order.module.dto.OrderRefundContext;
|
||||
import com.starry.admin.modules.order.module.dto.OrderRevocationContext;
|
||||
import com.starry.admin.modules.order.module.dto.OrderRevocationContext.EarningsAdjustStrategy;
|
||||
import com.starry.admin.modules.order.module.dto.PaymentInfo;
|
||||
import com.starry.admin.modules.order.module.dto.RandomOrderRequirements;
|
||||
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
|
||||
import com.starry.admin.modules.order.module.event.OrderRevocationEvent;
|
||||
import com.starry.admin.modules.order.module.vo.ClerkEstimatedRevenueVo;
|
||||
import com.starry.admin.modules.order.service.IPlayOrderRefundInfoService;
|
||||
import com.starry.admin.modules.order.service.support.ClerkRevenueCalculator;
|
||||
@@ -65,10 +68,12 @@ import java.util.UUID;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
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.Mockito;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class OrderLifecycleServiceImplTest {
|
||||
@@ -106,11 +111,104 @@ class OrderLifecycleServiceImplTest {
|
||||
@Mock
|
||||
private IPlayBalanceDetailsInfoService playBalanceDetailsInfoService;
|
||||
|
||||
@Mock
|
||||
private ApplicationEventPublisher applicationEventPublisher;
|
||||
|
||||
@BeforeEach
|
||||
void initStrategies() {
|
||||
lifecycleService.initPlacementStrategies();
|
||||
}
|
||||
|
||||
@Test
|
||||
void revokeCompletedOrder_updatesStatusAndPublishesEvent() {
|
||||
String orderId = UUID.randomUUID().toString();
|
||||
PlayOrderInfoEntity completed = buildOrder(orderId, OrderStatus.COMPLETED.getCode());
|
||||
completed.setFinalAmount(BigDecimal.valueOf(188));
|
||||
completed.setRefundType(OrderRefundFlag.NOT_REFUNDED.getCode());
|
||||
PlayOrderInfoEntity revoked = buildOrder(orderId, OrderStatus.REVOKED.getCode());
|
||||
revoked.setFinalAmount(BigDecimal.valueOf(188));
|
||||
|
||||
when(orderInfoMapper.selectById(orderId)).thenReturn(completed, revoked);
|
||||
when(orderInfoMapper.update(isNull(), any())).thenReturn(1);
|
||||
when(earningsService.hasLockedLines(orderId)).thenReturn(false);
|
||||
PlayCustomUserInfoEntity customer = buildCustomer(completed.getPurchaserBy(), BigDecimal.ZERO);
|
||||
when(customUserInfoService.getById(completed.getPurchaserBy())).thenReturn(customer);
|
||||
doNothing().when(customUserInfoService)
|
||||
.updateAccountBalanceById(anyString(), any(), any(), anyString(), anyString(), any(), any(), anyString());
|
||||
doNothing().when(orderRefundInfoService)
|
||||
.add(anyString(), anyString(), anyString(), anyString(), anyString(), any(), anyString(), anyString(), anyString(), anyString(), anyString());
|
||||
|
||||
OrderRevocationContext context = new OrderRevocationContext();
|
||||
context.setOrderId(orderId);
|
||||
context.setOperatorId("admin-8");
|
||||
context.setOperatorType(OperatorType.ADMIN.getCode());
|
||||
context.setRefundAmount(BigDecimal.valueOf(88));
|
||||
context.setRefundReason("客户投诉");
|
||||
context.setRefundToCustomer(true);
|
||||
context.setEarningsStrategy(EarningsAdjustStrategy.REVERSE_CLERK);
|
||||
|
||||
lifecycleService.revokeCompletedOrder(context);
|
||||
|
||||
verify(orderInfoMapper).update(isNull(), any());
|
||||
verify(orderLogInfoMapper).insert(any());
|
||||
ArgumentCaptor<OrderRevocationEvent> captor = ArgumentCaptor.forClass(OrderRevocationEvent.class);
|
||||
verify(applicationEventPublisher).publishEvent(captor.capture());
|
||||
OrderRevocationEvent event = captor.getValue();
|
||||
assertEquals(orderId, event.getContext().getOrderId());
|
||||
assertEquals(EarningsAdjustStrategy.REVERSE_CLERK, event.getContext().getEarningsStrategy());
|
||||
assertEquals(BigDecimal.valueOf(88), event.getContext().getRefundAmount());
|
||||
}
|
||||
|
||||
@Test
|
||||
void revokeCompletedOrder_defersBalanceCreditToListener() {
|
||||
String orderId = UUID.randomUUID().toString();
|
||||
PlayOrderInfoEntity completed = buildOrder(orderId, OrderStatus.COMPLETED.getCode());
|
||||
completed.setFinalAmount(BigDecimal.valueOf(208));
|
||||
completed.setRefundType(OrderRefundFlag.NOT_REFUNDED.getCode());
|
||||
PlayOrderInfoEntity revoked = buildOrder(orderId, OrderStatus.REVOKED.getCode());
|
||||
revoked.setFinalAmount(BigDecimal.valueOf(208));
|
||||
|
||||
when(orderInfoMapper.selectById(orderId)).thenReturn(completed, revoked);
|
||||
when(orderInfoMapper.update(isNull(), any())).thenReturn(1);
|
||||
when(earningsService.hasLockedLines(orderId)).thenReturn(false);
|
||||
when(customUserInfoService.getById(anyString())).thenThrow(new AssertionError("Balance update should be handled by listener"));
|
||||
|
||||
OrderRevocationContext context = new OrderRevocationContext();
|
||||
context.setOrderId(orderId);
|
||||
context.setOperatorId("admin-9");
|
||||
context.setOperatorType(OperatorType.ADMIN.getCode());
|
||||
context.setRefundAmount(BigDecimal.valueOf(108));
|
||||
context.setRefundReason("质量问题");
|
||||
context.setRefundToCustomer(true);
|
||||
context.setEarningsStrategy(EarningsAdjustStrategy.NONE);
|
||||
|
||||
lifecycleService.revokeCompletedOrder(context);
|
||||
|
||||
verify(customUserInfoService, never()).getById(anyString());
|
||||
verify(applicationEventPublisher).publishEvent(any(OrderRevocationEvent.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void revokeCompletedOrder_blocksWhenEarningsLocked() {
|
||||
String orderId = UUID.randomUUID().toString();
|
||||
PlayOrderInfoEntity completed = buildOrder(orderId, OrderStatus.COMPLETED.getCode());
|
||||
completed.setRefundType(OrderRefundFlag.NOT_REFUNDED.getCode());
|
||||
|
||||
when(orderInfoMapper.selectById(orderId)).thenReturn(completed);
|
||||
when(earningsService.hasLockedLines(orderId)).thenReturn(true);
|
||||
|
||||
OrderRevocationContext context = new OrderRevocationContext();
|
||||
context.setOrderId(orderId);
|
||||
context.setOperatorId("admin-locked");
|
||||
context.setOperatorType(OperatorType.ADMIN.getCode());
|
||||
context.setRefundToCustomer(false);
|
||||
context.setEarningsStrategy(EarningsAdjustStrategy.REVERSE_CLERK);
|
||||
|
||||
assertThrows(CustomException.class, () -> lifecycleService.revokeCompletedOrder(context));
|
||||
verify(orderInfoMapper, never()).update(isNull(), any());
|
||||
verify(applicationEventPublisher, never()).publishEvent(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void placeOrder_throwsWhenCommandNull() {
|
||||
assertThrows(CustomException.class, () -> lifecycleService.placeOrder(null));
|
||||
@@ -1223,6 +1321,13 @@ class OrderLifecycleServiceImplTest {
|
||||
return entity;
|
||||
}
|
||||
|
||||
private PlayCustomUserInfoEntity buildCustomer(String id, BigDecimal balance) {
|
||||
PlayCustomUserInfoEntity entity = new PlayCustomUserInfoEntity();
|
||||
entity.setId(id);
|
||||
entity.setAccountBalance(balance);
|
||||
return entity;
|
||||
}
|
||||
|
||||
private void stubDefaultPersistence() {
|
||||
lenient().when(orderInfoMapper.selectCount(any())).thenReturn(0L);
|
||||
lenient().when(orderInfoMapper.insert(any())).thenReturn(1);
|
||||
|
||||
@@ -12,6 +12,8 @@ import com.starry.admin.modules.withdraw.mapper.EarningsLineMapper;
|
||||
import com.starry.admin.modules.withdraw.service.IFreezePolicyService;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
@@ -68,4 +70,54 @@ class EarningsServiceImplTest {
|
||||
|
||||
verify(baseMapper, never()).insert(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void createCounterLine_persistsNegativeAvailableLine() {
|
||||
when(baseMapper.insert(any())).thenReturn(1);
|
||||
|
||||
earningsService.createCounterLine("order-neg", "tenant-t", "clerk-c", BigDecimal.valueOf(88), "admin");
|
||||
|
||||
ArgumentCaptor<EarningsLineEntity> captor = ArgumentCaptor.forClass(EarningsLineEntity.class);
|
||||
verify(baseMapper).insert(captor.capture());
|
||||
EarningsLineEntity saved = captor.getValue();
|
||||
assertEquals(new BigDecimal("-88"), saved.getAmount());
|
||||
assertEquals("clerk-c", saved.getClerkId());
|
||||
assertEquals("available", saved.getStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
void findWithdrawable_returnsEmptyWhenCounterLinesCreateDebt() {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
List<EarningsLineEntity> lines = Arrays.asList(
|
||||
line("neg", new BigDecimal("-60")),
|
||||
line("pos", new BigDecimal("40")));
|
||||
when(baseMapper.selectWithdrawableLines("clerk-001", now)).thenReturn(lines);
|
||||
|
||||
List<EarningsLineEntity> picked = earningsService.findWithdrawable("clerk-001", BigDecimal.valueOf(30), now);
|
||||
|
||||
assertEquals(0, picked.size(), "净额不足时不应允许提现");
|
||||
}
|
||||
|
||||
@Test
|
||||
void findWithdrawable_allowsWithdrawalAfterPositiveLinesCoverDebt() {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
List<EarningsLineEntity> lines = Arrays.asList(
|
||||
line("neg", new BigDecimal("-60")),
|
||||
line("first", new BigDecimal("40")),
|
||||
line("second", new BigDecimal("150")));
|
||||
when(baseMapper.selectWithdrawableLines("clerk-001", now)).thenReturn(lines);
|
||||
|
||||
List<EarningsLineEntity> picked = earningsService.findWithdrawable("clerk-001", BigDecimal.valueOf(70), now);
|
||||
|
||||
assertEquals(3, picked.size());
|
||||
assertEquals("second", picked.get(2).getId());
|
||||
}
|
||||
|
||||
private EarningsLineEntity line(String id, BigDecimal amount) {
|
||||
EarningsLineEntity entity = new EarningsLineEntity();
|
||||
entity.setId(id);
|
||||
entity.setAmount(amount);
|
||||
entity.setStatus("available");
|
||||
return entity;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user