|
@@ -389,4 +389,96 @@ class LeshouyingOrderControllerTest {
|
|
|
"取不到主体时**不能默认放行**(写成 Boolean.FALSE.equals 就会在这里放行)");
|
|
"取不到主体时**不能默认放行**(写成 Boolean.FALSE.equals 就会在这里放行)");
|
|
|
verify(orderMapper, never()).selectPage(any(), any());
|
|
verify(orderMapper, never()).selectPage(any(), any());
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ // ─────────────── 退款在途标记:列表/详情出口处补的那个派生量 ───────────────
|
|
|
|
|
+ //
|
|
|
|
|
+ // 用户 2026-09-30 报的:「我点击退款后,lsy 回调送达前,页面上还有退款按钮,难道还能重复点吗?」
|
|
|
|
|
+ // 后端有三道闸(在途闸门 + 预插入 + 部分唯一索引 uk_lsy_refund_one_pending),重复点**不会**
|
|
|
|
|
+ // 重复出金 —— 但界面上那枚按钮在整段在途期一直亮着,因为它判的是**订单状态**,
|
|
|
|
|
+ // 而退款在途时订单状态一动不动(还是 SUCCESS)。下面两条用例钉的是「谁在途」这个新读数的来源。
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 两条订单,退款表**按判据过滤之后**只剩一行在途的(orderId=2)。
|
|
|
|
|
+ *
|
|
|
|
|
+ * <p>★ 这里必须把「过滤」写进桩里,不能把两条退款行都给出去 —— mock 的
|
|
|
|
|
+ * {@code selectList} **不过滤**,谓词是数据库执行的。第一版就是这么写的
|
|
|
|
|
+ * (桩返回 [order1 的 SUCCESS, order2 的 REFUNDING]),于是 order1 也被标成在途,
|
|
|
|
|
+ * 断言红在 {@code expected: <false> but was: <true>}。
|
|
|
|
|
+ * 那次的教训是:**桩返回什么,就等于宣称数据库会返回什么** —— 桩里的两行
|
|
|
|
|
+ * 等于宣称「status IN (CREATED,REFUNDING) 会匹配到一行 SUCCESS」。
|
|
|
|
|
+ * 谓词本身对不对,由下一条用例直接读 wrapper 来钉。
|
|
|
|
|
+ */
|
|
|
|
|
+ private Page<LsyOrderEntity> pageOfTwoOrders() {
|
|
|
|
|
+ LsyOrderEntity first = order(); // id=1:只有一笔**已成功**的退款
|
|
|
|
|
+ LsyOrderEntity second = order(); // id=2:有一笔退款在途
|
|
|
|
|
+ second.setId(2L);
|
|
|
|
|
+ second.setOutTradeNo("OUT002");
|
|
|
|
|
+ Page<LsyOrderEntity> p = new Page<>(1, 20);
|
|
|
|
|
+ p.setRecords(new ArrayList<>(List.of(first, second)));
|
|
|
|
|
+ when(orderMapper.selectPage(any(), any())).thenReturn(p);
|
|
|
|
|
+
|
|
|
|
|
+ LsyRefundEntity inFlight = refund(9L, "RT009");
|
|
|
|
|
+ inFlight.setOrderId(2L);
|
|
|
|
|
+ inFlight.setStatus("REFUNDING");
|
|
|
|
|
+ when(refundMapper.selectList(any())).thenReturn(List.of(inFlight));
|
|
|
|
|
+ return p;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Test
|
|
|
|
|
+ void page_只有退款在途的那一行才标记为退款中() {
|
|
|
|
|
+ Page<LsyOrderEntity> p = pageOfTwoOrders();
|
|
|
|
|
+
|
|
|
|
|
+ controller.page(1, 20, null, null, null, null);
|
|
|
|
|
+
|
|
|
|
|
+ assertEquals(Boolean.FALSE, p.getRecords().get(0).getRefunding(),
|
|
|
|
|
+ "这张单的退款已结束、没有在途的 ⇒ 不在途,按钮该留着(否则退款过一次就再也退不了第二次)");
|
|
|
|
|
+ assertEquals(Boolean.TRUE, p.getRecords().get(1).getRefunding(),
|
|
|
|
|
+ "这张单有一笔 REFUNDING ⇒ 在途,界面必须据此把退款入口藏起来");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Test
|
|
|
|
|
+ void 退款在途的判据_与后端闸门同一集合() {
|
|
|
|
|
+ pageOfTwoOrders();
|
|
|
|
|
+
|
|
|
|
|
+ controller.page(1, 20, null, null, null, null);
|
|
|
|
|
+
|
|
|
|
|
+ // ★ 这条不是为了覆盖率,是为了钉「口径只有一份」:界面藏按钮的判据必须与
|
|
|
|
|
+ // LsyRefundService 的在途闸门(LsyRefundStatus.PENDING)是同一个集合。
|
|
|
|
|
+ // 两处各写各的,后果是闸门锁着而界面显示「可以退」—— 点下去才 400。
|
|
|
|
|
+ AbstractWrapper<?, ?, ?> w = capturedRefundQuery();
|
|
|
|
|
+ String sql = w.getSqlSegment();
|
|
|
|
|
+ assertTrue(sql.contains("status IN ("),
|
|
|
|
|
+ "必须按退款单状态过滤。**这条断言就是上一条用例里那个桩的前提** —— "
|
|
|
|
|
+ + "少了它,「已结束的退款不算在途」全靠桩自己说了算。实测: " + sql);
|
|
|
|
|
+ List<Object> statuses = boundValues(w);
|
|
|
|
|
+ assertTrue(statuses.containsAll(List.of("CREATED", "REFUNDING")),
|
|
|
|
|
+ "在途 = CREATED/REFUNDING(与 LsyRefundStatus.PENDING 同集合);实测绑定值=" + statuses);
|
|
|
|
|
+ assertFalse(statuses.contains("SUCCESS"),
|
|
|
|
|
+ "已成功的退款**不是**在途;实测绑定值=" + statuses);
|
|
|
|
|
+ assertTrue(sql.contains("order_id IN ("),
|
|
|
|
|
+ "必须按本页那些订单号批量查(逐行查一页 20 行就是 20 次往返);实测: " + sql);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Test
|
|
|
|
|
+ void detail_同样带上退款在途标记() {
|
|
|
|
|
+ when(orderMapper.selectOne(any())).thenReturn(order());
|
|
|
|
|
+ LsyRefundEntity inFlight = refund(9L, "RT009");
|
|
|
|
|
+ inFlight.setStatus("REFUNDING");
|
|
|
|
|
+ when(refundMapper.selectList(any())).thenReturn(List.of(inFlight));
|
|
|
|
|
+
|
|
|
|
|
+ LsyOrderEntity o = controller.detail("OUT001").getData();
|
|
|
|
|
+
|
|
|
|
|
+ assertEquals(Boolean.TRUE, o.getRefunding(),
|
|
|
|
|
+ "抽屉里的退款按钮用同一个标记判可见性 —— 不补这一处,列表刚藏掉的按钮会在抽屉里又亮出来");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Test
|
|
|
|
|
+ void page_空页_一次退款表都不查() {
|
|
|
|
|
+ when(orderMapper.selectPage(any(), any())).thenReturn(new Page<>(1, 20));
|
|
|
|
|
+
|
|
|
|
|
+ controller.page(1, 20, null, null, null, null);
|
|
|
|
|
+
|
|
|
|
|
+ // 空页时 `IN ()` 不是合法 SQL —— 这条防的是「为了补标记而无条件发一次恒假查询」。
|
|
|
|
|
+ verify(refundMapper, never()).selectList(any());
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|