|
|
@@ -21,6 +21,7 @@ import com.payment.platform.module.payment.har.entity.HarMerchantEntity;
|
|
|
import com.payment.platform.module.payment.har.entity.HarOrderEntity;
|
|
|
import com.payment.platform.module.payment.har.enums.HarOrderStatus;
|
|
|
import com.payment.platform.module.payment.har.mapper.HarOrderMapper;
|
|
|
+import com.payment.platform.module.payment.stream.OrderEventPublisher;
|
|
|
import org.apache.ibatis.builder.MapperBuilderAssistant;
|
|
|
import org.junit.jupiter.api.BeforeEach;
|
|
|
import org.junit.jupiter.api.Test;
|
|
|
@@ -43,15 +44,28 @@ import static org.mockito.Mockito.*;
|
|
|
@ExtendWith(MockitoExtension.class)
|
|
|
class HarOrderServiceTest {
|
|
|
|
|
|
+ /**
|
|
|
+ * 夹具租户。
|
|
|
+ *
|
|
|
+ * <p>★ 为什么夹具必须自己带一个:生产上 {@code har_order.tenant_id} 是
|
|
|
+ * {@code AutoFillMetaObjectHandler.insertFill} 在 insert 时**写进实体对象**的
|
|
|
+ * (common/config/AutoFillMetaObjectHandler.java),本类的 mapper 是 mock、不跑自动填充
|
|
|
+ * ⇒ 实体上这一格恒为 null。断言里若跟着写 null,「发布的是这一行自己的租户」就退化成
|
|
|
+ * 「恒发 null 也绿」—— 而恒发 null 在生产上意味着**一页都不会刷新**。
|
|
|
+ * 所以给一个非 null 且唯一的取值,需要模拟自动填充的两条用例见 insert 的 doAnswer。
|
|
|
+ */
|
|
|
+ private static final Long TENANT = 7L;
|
|
|
+
|
|
|
@Mock private HarOrderMapper orderMapper;
|
|
|
@Mock private HarMerchantService merchantService;
|
|
|
@Mock private HarClient harClient;
|
|
|
+ @Mock private OrderEventPublisher publisher;
|
|
|
|
|
|
private HarOrderService service;
|
|
|
|
|
|
@BeforeEach
|
|
|
void setUp() {
|
|
|
- service = new HarOrderService(orderMapper, merchantService, harClient);
|
|
|
+ service = new HarOrderService(orderMapper, merchantService, harClient, publisher);
|
|
|
// 初始化 MyBatis-Plus lambda 元数据缓存,使 LambdaQueryWrapper.getSqlSegment() 可在无 Spring 上下文的单测中工作
|
|
|
// (否则 getSqlSegment() 抛 MybatisPlusException: can not find lambda cache for this entity。
|
|
|
// 同款引导见 LsyCallbackServiceTest / AlipayBatchPayServiceTest。)
|
|
|
@@ -84,6 +98,7 @@ class HarOrderServiceTest {
|
|
|
HarOrderEntity o = new HarOrderEntity();
|
|
|
o.setOrderNo(orderNo);
|
|
|
o.setEnterpriseId("E1");
|
|
|
+ o.setTenantId(TENANT);
|
|
|
o.setTradeStatus(tradeStatus);
|
|
|
o.setQueryCount(0);
|
|
|
return o;
|
|
|
@@ -105,12 +120,22 @@ class HarOrderServiceTest {
|
|
|
void create_persistsLocalRecordBeforeCallingUpstream() {
|
|
|
when(merchantService.requireByEnterpriseId("E1")).thenReturn(merchant());
|
|
|
when(harClient.precreate(any())).thenReturn(response("10000", precreateDetail()));
|
|
|
+ // 模拟生产上的自动填充:insertFill 会把 tenantId 写进**这个实体对象**
|
|
|
+ // (common/config/AutoFillMetaObjectHandler:24-31),发布点读的正是它。
|
|
|
+ doAnswer(inv -> {
|
|
|
+ ((HarOrderEntity) inv.getArgument(0)).setTenantId(TENANT);
|
|
|
+ return 1;
|
|
|
+ }).when(orderMapper).insert(any());
|
|
|
|
|
|
service.create(dto(), "E1", "KEY1");
|
|
|
|
|
|
InOrder inOrder = inOrder(orderMapper, harClient);
|
|
|
inOrder.verify(orderMapper).insert(any());
|
|
|
inOrder.verify(harClient).precreate(any());
|
|
|
+
|
|
|
+ // 订单实时信号:本路径两个写点 —— 落库 insert(新行出现)、补 pay_url/上游单号那次
|
|
|
+ // updateById ⇒ 恰好 2 条。同一次请求里的两帧由前端合并(500ms 窗口)。
|
|
|
+ verify(publisher, times(2)).changed(OrderEventPublisher.CHANNEL_HAR_ORDER, TENANT);
|
|
|
}
|
|
|
|
|
|
@Test
|
|
|
@@ -253,6 +278,11 @@ class HarOrderServiceTest {
|
|
|
void create_upstreamNonSuccessCode_throwsAndClosesTheLocalRow() {
|
|
|
when(merchantService.requireByEnterpriseId("E1")).thenReturn(merchant());
|
|
|
when(harClient.precreate(any())).thenReturn(response("40004", null));
|
|
|
+ // 同 create_persistsLocalRecordBeforeCallingUpstream:模拟 insertFill 写回 tenantId
|
|
|
+ doAnswer(inv -> {
|
|
|
+ ((HarOrderEntity) inv.getArgument(0)).setTenantId(TENANT);
|
|
|
+ return 1;
|
|
|
+ }).when(orderMapper).insert(any());
|
|
|
|
|
|
BusinessException e = assertThrows(BusinessException.class,
|
|
|
() -> service.create(dto(), "E1", "KEY1"));
|
|
|
@@ -280,6 +310,10 @@ class HarOrderServiceTest {
|
|
|
// 漏掉 WHERE 就是**更新全表**,所以这一条必须留着。
|
|
|
assertTrue(closing.getSqlSegment().contains("id"),
|
|
|
"少了 WHERE 就是更新全表。实测: " + closing.getSqlSegment());
|
|
|
+
|
|
|
+ // 订单实时信号:本路径也是 2 条 —— insert(新行出现)+ 收尾那次 update(null,w)
|
|
|
+ // (状态落到「8 交易关闭」,页面上那一行要变)。中间没有 updateById:上游拒绝时不补 pay_url。
|
|
|
+ verify(publisher, times(2)).changed(OrderEventPublisher.CHANNEL_HAR_ORDER, TENANT);
|
|
|
}
|
|
|
|
|
|
@Test
|
|
|
@@ -349,6 +383,8 @@ class HarOrderServiceTest {
|
|
|
assertNotNull(vo.getUpstreamCreateTime());
|
|
|
// 终态必须清空,否则轮询任务永远捞起已结束的订单
|
|
|
assertNull(o.getNextQueryTime());
|
|
|
+ // 订单实时信号:查询成功那条尾巴写了一次库 ⇒ 一条。本方法只此一个发布点。
|
|
|
+ verify(publisher).changed(OrderEventPublisher.CHANNEL_HAR_ORDER, TENANT);
|
|
|
}
|
|
|
|
|
|
@Test
|
|
|
@@ -422,6 +458,9 @@ class HarOrderServiceTest {
|
|
|
assertNotNull(o.getNextQueryTime());
|
|
|
assertEquals(1, o.getQueryCount());
|
|
|
verify(orderMapper).updateById(any());
|
|
|
+ // 订单实时信号(负例):这一支只推查询游标(query_count / next_query_time),
|
|
|
+ // 订单列表页不展示这两格 ⇒ 发了就是每轮轮询推一帧,纯噪音。
|
|
|
+ verifyNoInteractions(publisher);
|
|
|
}
|
|
|
|
|
|
@Test
|
|
|
@@ -824,6 +863,10 @@ class HarOrderServiceTest {
|
|
|
InOrder inOrder = inOrder(orderMapper, harClient);
|
|
|
inOrder.verify(orderMapper).update(any(), any());
|
|
|
inOrder.verify(harClient).refund(any());
|
|
|
+
|
|
|
+ // 订单实时信号:抢占那一次条件更新真的写了行(状态已置「退款中」)⇒ 一条。
|
|
|
+ // 页面必须立刻从「已支付」翻成「退款中」,不能等上游来回。
|
|
|
+ verify(publisher).changed(OrderEventPublisher.CHANNEL_HAR_ORDER, TENANT);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -869,6 +912,9 @@ class HarOrderServiceTest {
|
|
|
|
|
|
assertTrue(e.getMessage().contains("不可退款"), "实测: " + e.getMessage());
|
|
|
verify(harClient, never()).refund(any());
|
|
|
+ // 订单实时信号(负例):条件更新命中 0 行 = 一个字节都没改(并发已把状态改掉了)
|
|
|
+ // ⇒ 不发。发了就是让订单页白拉一次。
|
|
|
+ verifyNoInteractions(publisher);
|
|
|
}
|
|
|
|
|
|
/** 非「支付成功」的订单一律不受理 —— 尤其 2 退款成功:再退一次就是重复退款 */
|
|
|
@@ -947,6 +993,11 @@ class HarOrderServiceTest {
|
|
|
// 原因也要一起清掉:这笔退款从没发生过,留着一句原因是页面对着一笔已支付的单说假话
|
|
|
assertTrue(rollback.getSqlSet().contains("refund_reason"),
|
|
|
"回退要把 reason 清成 null(写 null 只能走 SET 子句)。实测 SET: " + rollback.getSqlSet());
|
|
|
+
|
|
|
+ // 订单实时信号:本路径两次写库 —— 抢占(置退款中)、回退(翻回已支付)⇒ 两条。
|
|
|
+ // ★ 回退那一条不能少:页面刚从「已支付」翻成「退款中」,上游拒绝后必须翻回来,
|
|
|
+ // 否则它会一直显示一个**根本没有发生**的退款中。
|
|
|
+ verify(publisher, times(2)).changed(OrderEventPublisher.CHANNEL_HAR_ORDER, TENANT);
|
|
|
}
|
|
|
|
|
|
/** 优先用上游单号退款;拿不到时走 third_order_no(同查询口径) */
|