完成自动化测试套件实施(W1-W11)

W1-W3: 基线修复与测试基础设施搭建
- 修复 Jenkins JDK 21 兼容性,统一 E2E 目录,修复 storageState 冲突
- 搭建后端测试基类 BaseContractTest + Testcontainers PostgreSQL
- 创建 TestDataFactory 链式构造,完善 Vitest 基座与 Playwright fixtures
- 建立 docker-compose.test.yml 与测试数据隔离方案

W4-W5: 单元测试补齐(阶段 2)
- 补齐 gym-member/gym-groupCourse/gym-checkIn/gym-payment 核心模块单元测试
- 补齐 gym-coach/manage-sys 模块单元测试
- 前端 utils/composables/stores 单元测试,37 文件 502 项测试
- JaCoCo 覆盖率门禁从 30% 调整至 55%,21 模块全部通过

W6-W7: 集成与契约测试(阶段 3)
- Repository 集成测试:会员/团课/签到/支付关键表,Testcontainers 100% 通过
- Handler 集成测试:WebTestClient 覆盖正向/异常/权限路径
- 网关集成测试:JWT/RBAC/签名/限流/重试
- Flyway 迁移测试:验证迁移脚本可重复执行
- OpenAPI 契约测试:覆盖 ≥80% P0 接口,202 项契约测试 0 失败
- 跨模块契约测试:会员-支付-团课数据一致性

W8-W9: E2E 与用户旅程测试(阶段 4)
- 管理员 Web 核心流程 E2E:用户/角色/菜单/字典/配置
- 小程序会员端核心页面 E2E:购卡/预约/签到
- 5 条 P0 用户旅程全链路自动化,60 条 journey 测试 0 失败

W10: 变异测试与质量门禁(阶段 5)
- 后端 PIT 配置:pitest-maven 1.19.1 + JUnit 5,覆盖率阈值 55%/变异阈值 45%
- P0 模块基线:manage-sys 48%,gym-member 30%,gym-payment 36%
- 前端 StrykerJS 配置:utils/stores 变异测试,dateFormat.ts 70.83%
- Jenkins 质量门禁:JaCoCo/PIT/E2E 统一检查,不达标阻断构建

W11: 持续运行与改进(阶段 6)
- 测试指标收集脚本 scripts/collect-test-metrics.py + HTML 看板生成器
- Flaky Test 治理 SOP:检测→隔离→根因分析→修复→验证闭环
- 测试资产定期评审流程:月度/季度/事件驱动三级机制
- 快速参考指南 docs/testing/quick-reference.md
- 累计 10 份测试文档,7 个里程碑全部达成
This commit was merged in pull request #54.
This commit is contained in:
2026-08-02 08:28:37 +08:00
parent dc68581c5e
commit 015cb0dc78
119 changed files with 21873 additions and 553 deletions
+5
View File
@@ -71,5 +71,10 @@
<version>1.0.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,655 @@
package cn.novalon.gym.manage.payment.handler;
import cn.novalon.gym.manage.payment.dto.ApiResponse;
import cn.novalon.gym.manage.payment.dto.CreatePaymentRequest;
import cn.novalon.gym.manage.payment.dto.PaymentResponse;
import cn.novalon.gym.manage.payment.service.PaymentService;
import cn.novalon.gym.manage.sys.util.AuthUtil;
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;
import org.springframework.http.HttpStatus;
import org.springframework.mock.web.reactive.function.server.MockServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.List;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
class PaymentHandlerTest {
@Mock
private PaymentService paymentService;
@Mock
private AuthUtil authUtil;
private PaymentHandler paymentHandler;
private static final Long MEMBER_ID = 10001L;
@BeforeEach
void setUp() {
paymentHandler = new PaymentHandler(paymentService, authUtil);
}
// ==================== createPayment ====================
@Test
void createPayment_shouldReturnOk() {
CreatePaymentRequest createReq = new CreatePaymentRequest();
createReq.setOrderType("MEMBER_CARD");
createReq.setGoodsDesc("会员卡");
createReq.setTransAmt("100");
PaymentResponse paymentResp = PaymentResponse.builder()
.orderId("PAY2026073101")
.status("PENDING")
.build();
when(authUtil.getMemberIdOrThrow(any())).thenReturn(MEMBER_ID);
when(paymentService.alipayAppPay(eq(MEMBER_ID), any(CreatePaymentRequest.class)))
.thenReturn(Mono.just(paymentResp));
MockServerRequest request = MockServerRequest.builder()
.body(Mono.just(createReq));
Mono<ServerResponse> result = paymentHandler.createPayment(request);
ServerResponse response = result.block();
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
verify(paymentService).alipayAppPay(eq(MEMBER_ID), any(CreatePaymentRequest.class));
}
@Test
void createPayment_shouldReturnErrorWhenServiceReturnsFail() {
CreatePaymentRequest createReq = new CreatePaymentRequest();
createReq.setOrderType("MEMBER_CARD");
createReq.setGoodsDesc("会员卡");
createReq.setTransAmt("100");
PaymentResponse paymentResp = PaymentResponse.builder()
.status("FAIL")
.errorMsg("余额不足")
.build();
when(authUtil.getMemberIdOrThrow(any())).thenReturn(MEMBER_ID);
when(paymentService.alipayAppPay(eq(MEMBER_ID), any(CreatePaymentRequest.class)))
.thenReturn(Mono.just(paymentResp));
MockServerRequest request = MockServerRequest.builder()
.body(Mono.just(createReq));
Mono<ServerResponse> result = paymentHandler.createPayment(request);
ServerResponse response = result.block();
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
verify(paymentService).alipayAppPay(eq(MEMBER_ID), any(CreatePaymentRequest.class));
}
@Test
void createPayment_shouldHandleException() {
CreatePaymentRequest createReq = new CreatePaymentRequest();
when(authUtil.getMemberIdOrThrow(any())).thenReturn(MEMBER_ID);
when(paymentService.alipayAppPay(eq(MEMBER_ID), any(CreatePaymentRequest.class)))
.thenReturn(Mono.error(new RuntimeException("服务异常")));
MockServerRequest request = MockServerRequest.builder()
.body(Mono.just(createReq));
Mono<ServerResponse> result = paymentHandler.createPayment(request);
ServerResponse response = result.block();
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
verify(paymentService).alipayAppPay(eq(MEMBER_ID), any(CreatePaymentRequest.class));
}
// ==================== createQrCodePayment ====================
@Test
void createQrCodePayment_shouldReturnOk() {
CreatePaymentRequest createReq = new CreatePaymentRequest();
createReq.setOrderType("MEMBER_CARD");
createReq.setGoodsDesc("会员卡");
createReq.setTransAmt("100");
PaymentResponse paymentResp = PaymentResponse.builder()
.orderId("PAY2026073102")
.status("PENDING")
.qrCode("https://qr.alipay.com/test")
.build();
when(authUtil.getMemberIdOrThrow(any())).thenReturn(MEMBER_ID);
when(paymentService.alipayQrCodePay(eq(MEMBER_ID), any(CreatePaymentRequest.class)))
.thenReturn(Mono.just(paymentResp));
MockServerRequest request = MockServerRequest.builder()
.body(Mono.just(createReq));
Mono<ServerResponse> result = paymentHandler.createQrCodePayment(request);
ServerResponse response = result.block();
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
verify(paymentService).alipayQrCodePay(eq(MEMBER_ID), any(CreatePaymentRequest.class));
}
@Test
void createQrCodePayment_shouldReturnErrorWhenServiceReturnsFail() {
CreatePaymentRequest createReq = new CreatePaymentRequest();
createReq.setOrderType("MEMBER_CARD");
createReq.setGoodsDesc("会员卡");
createReq.setTransAmt("100");
PaymentResponse paymentResp = PaymentResponse.builder()
.status("FAIL")
.errorMsg("下单失败")
.build();
when(authUtil.getMemberIdOrThrow(any())).thenReturn(MEMBER_ID);
when(paymentService.alipayQrCodePay(eq(MEMBER_ID), any(CreatePaymentRequest.class)))
.thenReturn(Mono.just(paymentResp));
MockServerRequest request = MockServerRequest.builder()
.body(Mono.just(createReq));
Mono<ServerResponse> result = paymentHandler.createQrCodePayment(request);
ServerResponse response = result.block();
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
verify(paymentService).alipayQrCodePay(eq(MEMBER_ID), any(CreatePaymentRequest.class));
}
@Test
void createQrCodePayment_shouldHandleException() {
CreatePaymentRequest createReq = new CreatePaymentRequest();
when(authUtil.getMemberIdOrThrow(any())).thenReturn(MEMBER_ID);
when(paymentService.alipayQrCodePay(eq(MEMBER_ID), any(CreatePaymentRequest.class)))
.thenReturn(Mono.error(new RuntimeException("服务异常")));
MockServerRequest request = MockServerRequest.builder()
.body(Mono.just(createReq));
Mono<ServerResponse> result = paymentHandler.createQrCodePayment(request);
ServerResponse response = result.block();
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
verify(paymentService).alipayQrCodePay(eq(MEMBER_ID), any(CreatePaymentRequest.class));
}
// ==================== getPaymentStatus ====================
@Test
void getPaymentStatus_shouldReturnOk() {
PaymentResponse paymentResp = PaymentResponse.builder()
.orderId("PAY2026073101")
.status("SUCCESS")
.build();
when(authUtil.getMemberIdOrThrow(any())).thenReturn(MEMBER_ID);
when(paymentService.getPaymentStatus(MEMBER_ID, "PAY2026073101"))
.thenReturn(Mono.just(paymentResp));
MockServerRequest request = MockServerRequest.builder()
.pathVariable("orderId", "PAY2026073101")
.build();
Mono<ServerResponse> result = paymentHandler.getPaymentStatus(request);
ServerResponse response = result.block();
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
verify(paymentService).getPaymentStatus(MEMBER_ID, "PAY2026073101");
}
@Test
void getPaymentStatus_shouldHandleException() {
when(authUtil.getMemberIdOrThrow(any())).thenReturn(MEMBER_ID);
when(paymentService.getPaymentStatus(MEMBER_ID, "PAY2026073101"))
.thenReturn(Mono.error(new RuntimeException("查询失败")));
MockServerRequest request = MockServerRequest.builder()
.pathVariable("orderId", "PAY2026073101")
.build();
Mono<ServerResponse> result = paymentHandler.getPaymentStatus(request);
ServerResponse response = result.block();
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
verify(paymentService).getPaymentStatus(MEMBER_ID, "PAY2026073101");
}
// ==================== huifuNotify ====================
@Test
void huifuNotify_shouldReturnOk() {
when(authUtil.getMemberIdOrThrow(any())).thenReturn(MEMBER_ID);
when(paymentService.handleAlipayNotify(anyMap()))
.thenReturn(Mono.just("RECV_ORD_ID_test123"));
MockServerRequest request = MockServerRequest.builder()
.queryParam("resp_data", "{\"req_seq_id\":\"test123\",\"trans_stat\":\"S\"}")
.queryParam("sign", "testSign")
.build();
Mono<ServerResponse> result = paymentHandler.huifuNotify(request);
ServerResponse response = result.block();
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
verify(paymentService).handleAlipayNotify(anyMap());
}
@Test
void huifuNotify_shouldHandleException() {
when(authUtil.getMemberIdOrThrow(any())).thenReturn(MEMBER_ID);
when(paymentService.handleAlipayNotify(anyMap()))
.thenReturn(Mono.error(new RuntimeException("通知处理失败")));
MockServerRequest request = MockServerRequest.builder()
.queryParam("resp_data", "{\"req_seq_id\":\"test123\",\"trans_stat\":\"S\"}")
.queryParam("sign", "testSign")
.build();
Mono<ServerResponse> result = paymentHandler.huifuNotify(request);
ServerResponse response = result.block();
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
verify(paymentService).handleAlipayNotify(anyMap());
}
// ==================== refundPayment ====================
@Test
void refundPayment_shouldReturnOkWhenRefundSuccess() {
Map<String, Object> body = Map.of("refundAmt", "50");
when(authUtil.getMemberIdOrThrow(any())).thenReturn(MEMBER_ID);
when(paymentService.refund(MEMBER_ID, "PAY2026073101", "50"))
.thenReturn(Mono.just(true));
MockServerRequest request = MockServerRequest.builder()
.pathVariable("orderId", "PAY2026073101")
.body(Mono.just(body));
Mono<ServerResponse> result = paymentHandler.refundPayment(request);
ServerResponse response = result.block();
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
verify(paymentService).refund(MEMBER_ID, "PAY2026073101", "50");
}
@Test
void refundPayment_shouldReturnErrorWhenRefundFails() {
Map<String, Object> body = Map.of("refundAmt", "50");
when(authUtil.getMemberIdOrThrow(any())).thenReturn(MEMBER_ID);
when(paymentService.refund(MEMBER_ID, "PAY2026073101", "50"))
.thenReturn(Mono.just(false));
MockServerRequest request = MockServerRequest.builder()
.pathVariable("orderId", "PAY2026073101")
.body(Mono.just(body));
Mono<ServerResponse> result = paymentHandler.refundPayment(request);
ServerResponse response = result.block();
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
verify(paymentService).refund(MEMBER_ID, "PAY2026073101", "50");
}
@Test
void refundPayment_shouldHandleException() {
Map<String, Object> body = Map.of("refundAmt", "50");
when(authUtil.getMemberIdOrThrow(any())).thenReturn(MEMBER_ID);
when(paymentService.refund(MEMBER_ID, "PAY2026073101", "50"))
.thenReturn(Mono.error(new RuntimeException("退款异常")));
MockServerRequest request = MockServerRequest.builder()
.pathVariable("orderId", "PAY2026073101")
.body(Mono.just(body));
Mono<ServerResponse> result = paymentHandler.refundPayment(request);
ServerResponse response = result.block();
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
verify(paymentService).refund(MEMBER_ID, "PAY2026073101", "50");
}
// ==================== getAllPaymentOrders ====================
@Test
void getAllPaymentOrders_shouldReturnOk() {
PaymentResponse order1 = PaymentResponse.builder().orderId("PAY001").status("SUCCESS").build();
PaymentResponse order2 = PaymentResponse.builder().orderId("PAY002").status("PENDING").build();
when(authUtil.getMemberIdOrThrow(any())).thenReturn(MEMBER_ID);
when(paymentService.getAllPaymentOrders(MEMBER_ID))
.thenReturn(Flux.just(order1, order2));
MockServerRequest request = MockServerRequest.builder().build();
Mono<ServerResponse> result = paymentHandler.getAllPaymentOrders(request);
ServerResponse response = result.block();
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
verify(paymentService).getAllPaymentOrders(MEMBER_ID);
}
@Test
void getAllPaymentOrders_shouldReturnEmptyList() {
when(authUtil.getMemberIdOrThrow(any())).thenReturn(MEMBER_ID);
when(paymentService.getAllPaymentOrders(MEMBER_ID))
.thenReturn(Flux.empty());
MockServerRequest request = MockServerRequest.builder().build();
Mono<ServerResponse> result = paymentHandler.getAllPaymentOrders(request);
ServerResponse response = result.block();
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
verify(paymentService).getAllPaymentOrders(MEMBER_ID);
}
@Test
void getAllPaymentOrders_shouldHandleException() {
when(authUtil.getMemberIdOrThrow(any())).thenReturn(MEMBER_ID);
when(paymentService.getAllPaymentOrders(MEMBER_ID))
.thenReturn(Flux.error(new RuntimeException("查询异常")));
MockServerRequest request = MockServerRequest.builder().build();
Mono<ServerResponse> result = paymentHandler.getAllPaymentOrders(request);
ServerResponse response = result.block();
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
verify(paymentService).getAllPaymentOrders(MEMBER_ID);
}
// ==================== queryHuifuTradeList ====================
@Test
void queryHuifuTradeList_shouldReturnOk() {
Map<String, Object> resultMap = Map.of("total_count", 5, "trade_list", List.of());
when(authUtil.getMemberIdOrThrow(any())).thenReturn(MEMBER_ID);
when(paymentService.queryHuifuTradeList(MEMBER_ID, "20260701", "20260731"))
.thenReturn(Mono.just(resultMap));
MockServerRequest request = MockServerRequest.builder()
.queryParam("beginDate", "20260701")
.queryParam("endDate", "20260731")
.build();
Mono<ServerResponse> result = paymentHandler.queryHuifuTradeList(request);
ServerResponse response = result.block();
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
verify(paymentService).queryHuifuTradeList(MEMBER_ID, "20260701", "20260731");
}
@Test
void queryHuifuTradeList_shouldReturnOkWithNullDates() {
Map<String, Object> resultMap = Map.of("total_count", 0, "trade_list", List.of());
when(authUtil.getMemberIdOrThrow(any())).thenReturn(MEMBER_ID);
when(paymentService.queryHuifuTradeList(MEMBER_ID, null, null))
.thenReturn(Mono.just(resultMap));
MockServerRequest request = MockServerRequest.builder().build();
Mono<ServerResponse> result = paymentHandler.queryHuifuTradeList(request);
ServerResponse response = result.block();
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
verify(paymentService).queryHuifuTradeList(MEMBER_ID, null, null);
}
@Test
void queryHuifuTradeList_shouldHandleException() {
when(authUtil.getMemberIdOrThrow(any())).thenReturn(MEMBER_ID);
when(paymentService.queryHuifuTradeList(MEMBER_ID, null, null))
.thenReturn(Mono.error(new RuntimeException("查询异常")));
MockServerRequest request = MockServerRequest.builder().build();
Mono<ServerResponse> result = paymentHandler.queryHuifuTradeList(request);
ServerResponse response = result.block();
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
verify(paymentService).queryHuifuTradeList(MEMBER_ID, null, null);
}
// ==================== queryHuifuTradeByOrderId ====================
@Test
void queryHuifuTradeByOrderId_shouldReturnOk() {
Map<String, Object> resultMap = Map.of("success", true, "trade_info", Map.of());
when(authUtil.getMemberIdOrThrow(any())).thenReturn(MEMBER_ID);
when(paymentService.queryHuifuTradeByOrderId(MEMBER_ID, "PAY2026073101", null))
.thenReturn(Mono.just(resultMap));
MockServerRequest request = MockServerRequest.builder()
.queryParam("outOrdId", "PAY2026073101")
.build();
Mono<ServerResponse> result = paymentHandler.queryHuifuTradeByOrderId(request);
ServerResponse response = result.block();
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
verify(paymentService).queryHuifuTradeByOrderId(MEMBER_ID, "PAY2026073101", null);
}
@Test
void queryHuifuTradeByOrderId_shouldReturnOkWithHfSeqId() {
Map<String, Object> resultMap = Map.of("success", true, "trade_info", Map.of());
when(authUtil.getMemberIdOrThrow(any())).thenReturn(MEMBER_ID);
when(paymentService.queryHuifuTradeByOrderId(MEMBER_ID, "PAY2026073101", "HF2026073101"))
.thenReturn(Mono.just(resultMap));
MockServerRequest request = MockServerRequest.builder()
.queryParam("outOrdId", "PAY2026073101")
.queryParam("hfSeqId", "HF2026073101")
.build();
Mono<ServerResponse> result = paymentHandler.queryHuifuTradeByOrderId(request);
ServerResponse response = result.block();
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
verify(paymentService).queryHuifuTradeByOrderId(MEMBER_ID, "PAY2026073101", "HF2026073101");
}
@Test
void queryHuifuTradeByOrderId_shouldReturnErrorWhenOutOrdIdEmpty() {
when(authUtil.getMemberIdOrThrow(any())).thenReturn(MEMBER_ID);
MockServerRequest request = MockServerRequest.builder().build();
Mono<ServerResponse> result = paymentHandler.queryHuifuTradeByOrderId(request);
ServerResponse response = result.block();
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
verify(paymentService, never()).queryHuifuTradeByOrderId(anyLong(), anyString(), anyString());
}
@Test
void queryHuifuTradeByOrderId_shouldHandleException() {
when(authUtil.getMemberIdOrThrow(any())).thenReturn(MEMBER_ID);
when(paymentService.queryHuifuTradeByOrderId(MEMBER_ID, "PAY2026073101", null))
.thenReturn(Mono.error(new RuntimeException("查询异常")));
MockServerRequest request = MockServerRequest.builder()
.queryParam("outOrdId", "PAY2026073101")
.build();
Mono<ServerResponse> result = paymentHandler.queryHuifuTradeByOrderId(request);
ServerResponse response = result.block();
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
verify(paymentService).queryHuifuTradeByOrderId(MEMBER_ID, "PAY2026073101", null);
}
// ==================== getPendingOrder ====================
@Test
void getPendingOrder_shouldReturnOk() {
PaymentResponse paymentResp = PaymentResponse.builder()
.orderId("PAY2026073101")
.status("PENDING")
.build();
when(paymentService.getPendingOrder(10002L, "MEMBER_CARD"))
.thenReturn(Mono.just(paymentResp));
MockServerRequest request = MockServerRequest.builder()
.pathVariable("memberId", "10002")
.queryParam("orderType", "MEMBER_CARD")
.build();
Mono<ServerResponse> result = paymentHandler.getPendingOrder(request);
ServerResponse response = result.block();
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
verify(paymentService).getPendingOrder(10002L, "MEMBER_CARD");
}
@Test
void getPendingOrder_shouldReturnOkWhenOrderNotFound() {
when(paymentService.getPendingOrder(10002L, "MEMBER_CARD"))
.thenReturn(Mono.empty());
MockServerRequest request = MockServerRequest.builder()
.pathVariable("memberId", "10002")
.queryParam("orderType", "MEMBER_CARD")
.build();
Mono<ServerResponse> result = paymentHandler.getPendingOrder(request);
ServerResponse response = result.block();
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
verify(paymentService).getPendingOrder(10002L, "MEMBER_CARD");
}
@Test
void getPendingOrder_shouldReturnErrorWhenOrderTypeEmpty() {
MockServerRequest request = MockServerRequest.builder()
.pathVariable("memberId", "10002")
.build();
Mono<ServerResponse> result = paymentHandler.getPendingOrder(request);
ServerResponse response = result.block();
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
verify(paymentService, never()).getPendingOrder(anyLong(), anyString());
}
@Test
void getPendingOrder_shouldHandleException() {
when(paymentService.getPendingOrder(10002L, "MEMBER_CARD"))
.thenReturn(Mono.error(new RuntimeException("查询异常")));
MockServerRequest request = MockServerRequest.builder()
.pathVariable("memberId", "10002")
.queryParam("orderType", "MEMBER_CARD")
.build();
Mono<ServerResponse> result = paymentHandler.getPendingOrder(request);
ServerResponse response = result.block();
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
verify(paymentService).getPendingOrder(10002L, "MEMBER_CARD");
}
// ==================== closeOrder ====================
@Test
void closeOrder_shouldReturnOkWhenCloseSuccess() {
when(authUtil.getMemberIdOrThrow(any())).thenReturn(MEMBER_ID);
when(paymentService.closeOrder(MEMBER_ID, "PAY2026073101"))
.thenReturn(Mono.just(true));
MockServerRequest request = MockServerRequest.builder()
.pathVariable("orderId", "PAY2026073101")
.build();
Mono<ServerResponse> result = paymentHandler.closeOrder(request);
ServerResponse response = result.block();
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
verify(paymentService).closeOrder(MEMBER_ID, "PAY2026073101");
}
@Test
void closeOrder_shouldReturnErrorWhenCloseFails() {
when(authUtil.getMemberIdOrThrow(any())).thenReturn(MEMBER_ID);
when(paymentService.closeOrder(MEMBER_ID, "PAY2026073101"))
.thenReturn(Mono.just(false));
MockServerRequest request = MockServerRequest.builder()
.pathVariable("orderId", "PAY2026073101")
.build();
Mono<ServerResponse> result = paymentHandler.closeOrder(request);
ServerResponse response = result.block();
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
verify(paymentService).closeOrder(MEMBER_ID, "PAY2026073101");
}
@Test
void closeOrder_shouldHandleException() {
when(authUtil.getMemberIdOrThrow(any())).thenReturn(MEMBER_ID);
when(paymentService.closeOrder(MEMBER_ID, "PAY2026073101"))
.thenReturn(Mono.error(new RuntimeException("关闭异常")));
MockServerRequest request = MockServerRequest.builder()
.pathVariable("orderId", "PAY2026073101")
.build();
Mono<ServerResponse> result = paymentHandler.closeOrder(request);
ServerResponse response = result.block();
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
verify(paymentService).closeOrder(MEMBER_ID, "PAY2026073101");
}
}
@@ -0,0 +1,103 @@
package cn.novalon.gym.manage.payment.handler;
import cn.novalon.gym.manage.payment.entity.PaymentOrder;
import cn.novalon.gym.manage.payment.repository.PaymentOrderRepository;
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;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.time.LocalDateTime;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
class PaymentScheduledHandlerTest {
@Mock
private PaymentOrderRepository paymentOrderRepository;
private PaymentScheduledHandler paymentScheduledHandler;
@BeforeEach
void setUp() {
paymentScheduledHandler = new PaymentScheduledHandler(paymentOrderRepository);
}
// ==================== closeExpiredOrders ====================
@Test
void closeExpiredOrders_shouldCloseExpiredOrdersSuccessfully() throws InterruptedException {
// given
PaymentOrder order1 = PaymentOrder.builder()
.id(1L)
.orderNo("PAY20260731001")
.expireTime(LocalDateTime.now().minusMinutes(5))
.build();
PaymentOrder order2 = PaymentOrder.builder()
.id(2L)
.orderNo("PAY20260731002")
.expireTime(LocalDateTime.now().minusMinutes(10))
.build();
when(paymentOrderRepository.findExpiredOrders(any(LocalDateTime.class), anyInt()))
.thenReturn(Flux.just(order1, order2));
when(paymentOrderRepository.closeExpiredOrder(1L)).thenReturn(Mono.just(1));
when(paymentOrderRepository.closeExpiredOrder(2L)).thenReturn(Mono.just(1));
// when
paymentScheduledHandler.closeExpiredOrders();
// then - wait for async subscribe() to complete
Thread.sleep(500);
verify(paymentOrderRepository).findExpiredOrders(any(LocalDateTime.class), eq(100));
verify(paymentOrderRepository).closeExpiredOrder(1L);
verify(paymentOrderRepository).closeExpiredOrder(2L);
}
@Test
void closeExpiredOrders_shouldDoNothingWhenNoExpiredOrders() throws InterruptedException {
// given
when(paymentOrderRepository.findExpiredOrders(any(LocalDateTime.class), anyInt()))
.thenReturn(Flux.empty());
// when
paymentScheduledHandler.closeExpiredOrders();
// then - wait for async subscribe() to complete
Thread.sleep(500);
verify(paymentOrderRepository).findExpiredOrders(any(LocalDateTime.class), eq(100));
verify(paymentOrderRepository, never()).closeExpiredOrder(anyLong());
}
@Test
void closeExpiredOrders_shouldHandleErrorWhenCloseFails() throws InterruptedException {
// given
PaymentOrder order = PaymentOrder.builder()
.id(1L)
.orderNo("PAY20260731001")
.expireTime(LocalDateTime.now().minusMinutes(5))
.build();
when(paymentOrderRepository.findExpiredOrders(any(LocalDateTime.class), anyInt()))
.thenReturn(Flux.just(order));
when(paymentOrderRepository.closeExpiredOrder(1L))
.thenReturn(Mono.error(new RuntimeException("关闭订单失败")));
// when & then - should not throw exception, error should be handled by onErrorResume
assertThatCode(() -> {
paymentScheduledHandler.closeExpiredOrders();
Thread.sleep(500);
}).doesNotThrowAnyException();
verify(paymentOrderRepository).findExpiredOrders(any(LocalDateTime.class), eq(100));
verify(paymentOrderRepository).closeExpiredOrder(1L);
}
}
@@ -0,0 +1,657 @@
package cn.novalon.gym.manage.payment.service.impl;
import cn.novalon.gym.manage.common.util.RedisUtil;
import cn.novalon.gym.manage.payment.config.HuifuProperties;
import cn.novalon.gym.manage.payment.dto.CreatePaymentRequest;
import cn.novalon.gym.manage.payment.dto.PaymentResponse;
import cn.novalon.gym.manage.payment.entity.PaymentOrder;
import cn.novalon.gym.manage.payment.repository.PaymentOrderRepository;
import cn.novalon.gym.manage.payment.service.PaymentNotifyService;
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;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
class PaymentServiceImplTest {
@Mock
private HuifuProperties huifuProperties;
@Mock
private PaymentOrderRepository paymentOrderRepository;
@Mock
private RedisUtil redisUtil;
@Mock
private PaymentNotifyService paymentNotifyService;
private PaymentServiceImpl paymentService;
private static final Long MEMBER_ID = 10001L;
private static final String ORDER_ID = "PAY2026073100000001";
private static final String ORDER_TYPE = "MEMBER_CARD";
@BeforeEach
void setUp() {
HuifuProperties.MerchantConfig merchantConfig = mock(HuifuProperties.MerchantConfig.class);
lenient().when(huifuProperties.getMerchant()).thenReturn(merchantConfig);
lenient().when(merchantConfig.getHuifuId()).thenReturn("MOCK_HUIFU_ID");
HuifuProperties.NotifyConfig notifyConfig = mock(HuifuProperties.NotifyConfig.class);
lenient().when(huifuProperties.getNotify()).thenReturn(notifyConfig);
lenient().when(notifyConfig.getUrl()).thenReturn("http://mock.notify.url");
lenient().when(huifuProperties.isSdkInitialized()).thenReturn(false);
paymentService = new PaymentServiceImpl(huifuProperties, paymentOrderRepository, redisUtil, paymentNotifyService);
}
private PaymentOrder createTestOrder(String status) {
return PaymentOrder.builder()
.orderNo(ORDER_ID)
.memberId(MEMBER_ID)
.orderType(ORDER_TYPE)
.goodsDesc("会员卡")
.transAmt(new BigDecimal("0.01"))
.payStatus(status)
.tradeType("ALIPAY")
.huifuId("MOCK_HUIFU_ID")
.reqSeqId("REQ123456")
.reqDate("20260731")
.remark("测试备注")
.notifyUrl("http://mock.notify.url")
.expireTime(LocalDateTime.now().plusMinutes(15))
.build();
}
private CreatePaymentRequest createTestRequest() {
CreatePaymentRequest request = new CreatePaymentRequest();
request.setOrderType(ORDER_TYPE);
request.setGoodsDesc("会员卡");
request.setTransAmt("100");
request.setRemark("测试备注");
return request;
}
// ==================== createPayment ====================
@Test
void createPayment_whenSdkNotInitialized_shouldReturnMockOrder() {
CreatePaymentRequest request = createTestRequest();
when(paymentOrderRepository.save(any(PaymentOrder.class))).thenAnswer(invocation -> {
PaymentOrder saved = invocation.getArgument(0);
saved.setId(1L);
return Mono.just(saved);
});
Mono<PaymentResponse> result = paymentService.createPayment(MEMBER_ID, request);
StepVerifier.create(result)
.assertNext(response -> {
assertThat(response.getStatus()).isEqualTo("PENDING");
assertThat(response.getOrderId()).isNotNull();
assertThat(response.getPayInfo()).contains("alipays://");
})
.verifyComplete();
verify(paymentOrderRepository).save(any(PaymentOrder.class));
}
// ==================== alipayAppPay ====================
@Test
void alipayAppPay_whenSdkNotInitialized_shouldReturnMockOrder() {
CreatePaymentRequest request = createTestRequest();
when(paymentOrderRepository.save(any(PaymentOrder.class))).thenAnswer(invocation -> {
PaymentOrder saved = invocation.getArgument(0);
saved.setId(1L);
return Mono.just(saved);
});
Mono<PaymentResponse> result = paymentService.alipayAppPay(MEMBER_ID, request);
StepVerifier.create(result)
.assertNext(response -> {
assertThat(response.getStatus()).isEqualTo("PENDING");
assertThat(response.getOrderId()).isNotNull();
assertThat(response.getPayInfo()).contains("alipays://");
})
.verifyComplete();
verify(paymentOrderRepository).save(any(PaymentOrder.class));
verify(paymentOrderRepository, never()).findLatestPendingOrder(anyLong(), anyString());
}
@Test
void alipayAppPay_whenHasPendingOrder_shouldReturnExistingOrder() {
when(huifuProperties.isSdkInitialized()).thenReturn(true);
CreatePaymentRequest request = createTestRequest();
PaymentOrder existingOrder = createTestOrder("PENDING");
existingOrder.setPayInfo("alipays://existing");
when(paymentOrderRepository.findLatestPendingOrder(MEMBER_ID, ORDER_TYPE))
.thenReturn(Mono.just(existingOrder));
Mono<PaymentResponse> result = paymentService.alipayAppPay(MEMBER_ID, request);
StepVerifier.create(result)
.assertNext(response -> {
assertThat(response.getStatus()).isEqualTo("PENDING");
assertThat(response.getOrderId()).isEqualTo(ORDER_ID);
})
.verifyComplete();
verify(paymentOrderRepository).findLatestPendingOrder(MEMBER_ID, ORDER_TYPE);
verify(paymentOrderRepository, never()).save(any(PaymentOrder.class));
}
// ==================== alipayQrCodePay ====================
@Test
void alipayQrCodePay_whenSdkNotInitialized_shouldReturnMockOrder() {
CreatePaymentRequest request = createTestRequest();
when(paymentOrderRepository.save(any(PaymentOrder.class))).thenAnswer(invocation -> {
PaymentOrder saved = invocation.getArgument(0);
saved.setId(1L);
return Mono.just(saved);
});
Mono<PaymentResponse> result = paymentService.alipayQrCodePay(MEMBER_ID, request);
StepVerifier.create(result)
.assertNext(response -> {
assertThat(response.getStatus()).isEqualTo("PENDING");
assertThat(response.getOrderId()).isNotNull();
assertThat(response.getQrCode()).contains("https://qr.alipay.com/");
})
.verifyComplete();
verify(paymentOrderRepository).save(any(PaymentOrder.class));
verify(paymentOrderRepository, never()).findLatestPendingOrder(anyLong(), anyString());
}
@Test
void alipayQrCodePay_whenHasPendingOrder_shouldReturnExistingOrder() {
when(huifuProperties.isSdkInitialized()).thenReturn(true);
CreatePaymentRequest request = createTestRequest();
PaymentOrder existingOrder = createTestOrder("PENDING");
existingOrder.setQrCode("https://qr.alipay.com/existing");
when(paymentOrderRepository.findLatestPendingOrder(MEMBER_ID, ORDER_TYPE))
.thenReturn(Mono.just(existingOrder));
Mono<PaymentResponse> result = paymentService.alipayQrCodePay(MEMBER_ID, request);
StepVerifier.create(result)
.assertNext(response -> {
assertThat(response.getStatus()).isEqualTo("PENDING");
assertThat(response.getOrderId()).isEqualTo(ORDER_ID);
})
.verifyComplete();
verify(paymentOrderRepository).findLatestPendingOrder(MEMBER_ID, ORDER_TYPE);
verify(paymentOrderRepository, never()).save(any(PaymentOrder.class));
}
// ==================== getPaymentStatus ====================
@Test
void getPaymentStatus_whenOrderNotFound_shouldReturnNotFound() {
when(paymentOrderRepository.findByOrderNo(ORDER_ID)).thenReturn(Mono.empty());
Mono<PaymentResponse> result = paymentService.getPaymentStatus(MEMBER_ID, ORDER_ID);
StepVerifier.create(result)
.assertNext(response -> {
assertThat(response.getStatus()).isEqualTo("NOT_FOUND");
assertThat(response.getErrorMsg()).isEqualTo("订单不存在");
})
.verifyComplete();
}
@Test
void getPaymentStatus_whenOrderNotBelongToMember_shouldReturnNotFound() {
PaymentOrder order = createTestOrder("PENDING");
order.setMemberId(99999L);
when(paymentOrderRepository.findByOrderNo(ORDER_ID)).thenReturn(Mono.just(order));
Mono<PaymentResponse> result = paymentService.getPaymentStatus(MEMBER_ID, ORDER_ID);
StepVerifier.create(result)
.assertNext(response -> {
assertThat(response.getStatus()).isEqualTo("NOT_FOUND");
assertThat(response.getErrorMsg()).isEqualTo("订单不存在");
})
.verifyComplete();
}
@Test
void getPaymentStatus_whenOrderIsSuccess_shouldReturnDirectly() {
PaymentOrder order = createTestOrder("SUCCESS");
when(paymentOrderRepository.findByOrderNo(ORDER_ID)).thenReturn(Mono.just(order));
Mono<PaymentResponse> result = paymentService.getPaymentStatus(MEMBER_ID, ORDER_ID);
StepVerifier.create(result)
.assertNext(response -> {
assertThat(response.getStatus()).isEqualTo("SUCCESS");
assertThat(response.getOrderId()).isEqualTo(ORDER_ID);
})
.verifyComplete();
verify(paymentOrderRepository, never()).save(any(PaymentOrder.class));
}
@Test
void getPaymentStatus_whenOrderIsClosed_shouldReturnDirectly() {
PaymentOrder order = createTestOrder("CLOSED");
when(paymentOrderRepository.findByOrderNo(ORDER_ID)).thenReturn(Mono.just(order));
Mono<PaymentResponse> result = paymentService.getPaymentStatus(MEMBER_ID, ORDER_ID);
StepVerifier.create(result)
.assertNext(response -> {
assertThat(response.getStatus()).isEqualTo("CLOSED");
})
.verifyComplete();
verify(paymentOrderRepository, never()).save(any(PaymentOrder.class));
}
@Test
void getPaymentStatus_whenOrderIsFail_shouldReturnDirectly() {
PaymentOrder order = createTestOrder("FAIL");
when(paymentOrderRepository.findByOrderNo(ORDER_ID)).thenReturn(Mono.just(order));
Mono<PaymentResponse> result = paymentService.getPaymentStatus(MEMBER_ID, ORDER_ID);
StepVerifier.create(result)
.assertNext(response -> {
assertThat(response.getStatus()).isEqualTo("FAIL");
})
.verifyComplete();
verify(paymentOrderRepository, never()).save(any(PaymentOrder.class));
}
@Test
void getPaymentStatus_whenSdkNotInitialized_shouldReturnCurrentStatus() {
PaymentOrder order = createTestOrder("PENDING");
when(paymentOrderRepository.findByOrderNo(ORDER_ID)).thenReturn(Mono.just(order));
Mono<PaymentResponse> result = paymentService.getPaymentStatus(MEMBER_ID, ORDER_ID);
StepVerifier.create(result)
.assertNext(response -> {
assertThat(response.getStatus()).isEqualTo("PENDING");
assertThat(response.getOrderId()).isEqualTo(ORDER_ID);
})
.verifyComplete();
verify(paymentOrderRepository, never()).save(any(PaymentOrder.class));
}
@Test
void getPaymentStatus_whenQueryError_shouldReturnCurrentStatus() {
when(huifuProperties.isSdkInitialized()).thenReturn(true);
PaymentOrder order = createTestOrder("PENDING");
order.setHfSeqId("HF123");
when(paymentOrderRepository.findByOrderNo(ORDER_ID)).thenReturn(Mono.just(order));
Mono<PaymentResponse> result = paymentService.getPaymentStatus(MEMBER_ID, ORDER_ID);
StepVerifier.create(result)
.assertNext(response -> {
assertThat(response.getStatus()).isEqualTo("PENDING");
assertThat(response.getOrderId()).isEqualTo(ORDER_ID);
})
.verifyComplete();
}
// ==================== handleAlipayNotify ====================
@Test
void handleAlipayNotify_whenRespDataNull_shouldReturnFail() {
Map<String, String> params = new HashMap<>();
params.put("resp_data", null);
params.put("sign", "testSign");
Mono<String> result = paymentService.handleAlipayNotify(params);
StepVerifier.create(result)
.expectNext("FAIL")
.verifyComplete();
}
@Test
void handleAlipayNotify_whenSignNull_shouldReturnFail() {
Map<String, String> params = new HashMap<>();
params.put("resp_data", "{\"req_seq_id\":\"REQ123\"}");
params.put("sign", null);
Mono<String> result = paymentService.handleAlipayNotify(params);
StepVerifier.create(result)
.expectNext("FAIL")
.verifyComplete();
}
@Test
void handleAlipayNotify_whenAlreadyProcessed_shouldReturnRecvOrdId() {
Map<String, String> params = new HashMap<>();
params.put("resp_data", "{\"req_seq_id\":\"REQ123\",\"hf_seq_id\":\"HF123\",\"trans_stat\":\"S\"}");
params.put("sign", "testSign");
when(redisUtil.hasKey("huifu:processed:trade:REQ123")).thenReturn(Mono.just(true));
Mono<String> result = paymentService.handleAlipayNotify(params);
StepVerifier.create(result)
.expectNext("RECV_ORD_ID_REQ123")
.verifyComplete();
verify(paymentOrderRepository, never()).findByReqSeqId(anyString());
}
@Test
void handleAlipayNotify_whenTradeSuccess_shouldProcessCorrectly() {
Map<String, String> params = new HashMap<>();
params.put("resp_data", "{\"req_seq_id\":\"REQ123\",\"hf_seq_id\":\"HF123\",\"trans_stat\":\"S\"}");
params.put("sign", "testSign");
PaymentOrder order = createTestOrder("PENDING");
when(redisUtil.hasKey("huifu:processed:trade:REQ123")).thenReturn(Mono.just(false));
when(paymentOrderRepository.findByReqSeqId("REQ123")).thenReturn(Mono.just(order));
when(paymentOrderRepository.save(any(PaymentOrder.class))).thenReturn(Mono.just(order));
when(redisUtil.setWithExpire("huifu:processed:trade:REQ123", "1", 86400)).thenReturn(Mono.just(true));
when(paymentNotifyService.notifyPaymentStatus(order.getOrderNo(), "SUCCESS", MEMBER_ID)).thenReturn(Mono.empty());
Mono<String> result = paymentService.handleAlipayNotify(params);
StepVerifier.create(result)
.expectNext("RECV_ORD_ID_REQ123")
.verifyComplete();
verify(paymentOrderRepository).save(any(PaymentOrder.class));
verify(redisUtil).setWithExpire("huifu:processed:trade:REQ123", "1", 86400);
verify(paymentNotifyService).notifyPaymentStatus(order.getOrderNo(), "SUCCESS", MEMBER_ID);
}
@Test
void handleAlipayNotify_whenTradeFailed_shouldProcessCorrectly() {
Map<String, String> params = new HashMap<>();
params.put("resp_data", "{\"req_seq_id\":\"REQ123\",\"hf_seq_id\":\"HF123\",\"trans_stat\":\"F\",\"resp_desc\":\"支付失败\"}");
params.put("sign", "testSign");
PaymentOrder order = createTestOrder("PENDING");
when(redisUtil.hasKey("huifu:processed:trade:REQ123")).thenReturn(Mono.just(false));
when(paymentOrderRepository.findByReqSeqId("REQ123")).thenReturn(Mono.just(order));
when(paymentOrderRepository.save(any(PaymentOrder.class))).thenReturn(Mono.just(order));
when(redisUtil.setWithExpire("huifu:processed:trade:REQ123", "1", 86400)).thenReturn(Mono.just(true));
when(paymentNotifyService.notifyPaymentStatus(order.getOrderNo(), "FAIL", MEMBER_ID)).thenReturn(Mono.empty());
Mono<String> result = paymentService.handleAlipayNotify(params);
StepVerifier.create(result)
.expectNext("RECV_ORD_ID_REQ123")
.verifyComplete();
verify(paymentOrderRepository).save(any(PaymentOrder.class));
verify(redisUtil).setWithExpire("huifu:processed:trade:REQ123", "1", 86400);
verify(paymentNotifyService).notifyPaymentStatus(order.getOrderNo(), "FAIL", MEMBER_ID);
}
@Test
void handleAlipayNotify_whenTradeProcessing_shouldReturnRecvOrdId() {
Map<String, String> params = new HashMap<>();
params.put("resp_data", "{\"req_seq_id\":\"REQ123\",\"hf_seq_id\":\"HF123\",\"trans_stat\":\"P\"}");
params.put("sign", "testSign");
PaymentOrder order = createTestOrder("PENDING");
when(redisUtil.hasKey("huifu:processed:trade:REQ123")).thenReturn(Mono.just(false));
when(paymentOrderRepository.findByReqSeqId("REQ123")).thenReturn(Mono.just(order));
when(paymentNotifyService.notifyPaymentStatus(order.getOrderNo(), "PENDING", MEMBER_ID)).thenReturn(Mono.empty());
Mono<String> result = paymentService.handleAlipayNotify(params);
StepVerifier.create(result)
.expectNext("RECV_ORD_ID_REQ123")
.verifyComplete();
verify(paymentOrderRepository, never()).save(any(PaymentOrder.class));
verify(paymentNotifyService).notifyPaymentStatus(order.getOrderNo(), "PENDING", MEMBER_ID);
}
@Test
void handleAlipayNotify_whenOrderNotFound_shouldReturnFail() {
Map<String, String> params = new HashMap<>();
params.put("resp_data", "{\"req_seq_id\":\"REQ123\",\"hf_seq_id\":\"HF123\",\"trans_stat\":\"S\"}");
params.put("sign", "testSign");
when(redisUtil.hasKey("huifu:processed:trade:REQ123")).thenReturn(Mono.just(false));
when(paymentOrderRepository.findByReqSeqId("REQ123")).thenReturn(Mono.empty());
Mono<String> result = paymentService.handleAlipayNotify(params);
StepVerifier.create(result)
.expectNext("FAIL")
.verifyComplete();
}
@Test
void handleAlipayNotify_whenException_shouldReturnFail() {
Map<String, String> params = new HashMap<>();
params.put("resp_data", "{\"req_seq_id\":\"REQ123\",\"hf_seq_id\":\"HF123\",\"trans_stat\":\"S\"}");
params.put("sign", "testSign");
when(redisUtil.hasKey("huifu:processed:trade:REQ123")).thenReturn(Mono.just(false));
when(paymentOrderRepository.findByReqSeqId("REQ123")).thenReturn(Mono.error(new RuntimeException("DB error")));
Mono<String> result = paymentService.handleAlipayNotify(params);
StepVerifier.create(result)
.expectNext("FAIL")
.verifyComplete();
}
// ==================== refund ====================
@Test
void refund_shouldReturnFalse() {
Mono<Boolean> result = paymentService.refund(MEMBER_ID, ORDER_ID, "50");
StepVerifier.create(result)
.expectNext(false)
.verifyComplete();
}
// ==================== getAllPaymentOrders ====================
@Test
void getAllPaymentOrders_shouldReturnOrderList() {
PaymentOrder order1 = createTestOrder("SUCCESS");
order1.setOrderNo("PAY001");
PaymentOrder order2 = createTestOrder("PENDING");
order2.setOrderNo("PAY002");
when(paymentOrderRepository.findByMemberIdAndDeletedAtIsNull(MEMBER_ID))
.thenReturn(Flux.just(order1, order2));
Flux<PaymentResponse> result = paymentService.getAllPaymentOrders(MEMBER_ID);
StepVerifier.create(result)
.assertNext(response -> {
assertThat(response.getOrderId()).isEqualTo("PAY001");
assertThat(response.getStatus()).isEqualTo("SUCCESS");
})
.assertNext(response -> {
assertThat(response.getOrderId()).isEqualTo("PAY002");
assertThat(response.getStatus()).isEqualTo("PENDING");
})
.verifyComplete();
}
@Test
void getAllPaymentOrders_whenNoOrders_shouldReturnEmpty() {
when(paymentOrderRepository.findByMemberIdAndDeletedAtIsNull(MEMBER_ID))
.thenReturn(Flux.empty());
Flux<PaymentResponse> result = paymentService.getAllPaymentOrders(MEMBER_ID);
StepVerifier.create(result)
.verifyComplete();
}
// ==================== getPendingOrder ====================
@Test
void getPendingOrder_shouldReturnPendingOrder() {
PaymentOrder order = createTestOrder("PENDING");
when(paymentOrderRepository.findLatestPendingOrder(MEMBER_ID, ORDER_TYPE))
.thenReturn(Mono.just(order));
Mono<PaymentResponse> result = paymentService.getPendingOrder(MEMBER_ID, ORDER_TYPE);
StepVerifier.create(result)
.assertNext(response -> {
assertThat(response.getOrderId()).isEqualTo(ORDER_ID);
assertThat(response.getStatus()).isEqualTo("PENDING");
})
.verifyComplete();
}
@Test
void getPendingOrder_whenNoPendingOrder_shouldReturnEmpty() {
when(paymentOrderRepository.findLatestPendingOrder(MEMBER_ID, ORDER_TYPE))
.thenReturn(Mono.empty());
Mono<PaymentResponse> result = paymentService.getPendingOrder(MEMBER_ID, ORDER_TYPE);
StepVerifier.create(result)
.verifyComplete();
}
// ==================== closeOrder ====================
@Test
void closeOrder_shouldCloseSuccessfully() {
PaymentOrder order = createTestOrder("PENDING");
order.setId(1L);
when(paymentOrderRepository.findByOrderNo(ORDER_ID)).thenReturn(Mono.just(order));
when(paymentOrderRepository.closeExpiredOrder(1L)).thenReturn(Mono.just(1));
Mono<Boolean> result = paymentService.closeOrder(MEMBER_ID, ORDER_ID);
StepVerifier.create(result)
.expectNext(true)
.verifyComplete();
}
@Test
void closeOrder_whenOrderNotBelongToMember_shouldReturnFalse() {
PaymentOrder order = createTestOrder("PENDING");
order.setMemberId(99999L);
when(paymentOrderRepository.findByOrderNo(ORDER_ID)).thenReturn(Mono.just(order));
Mono<Boolean> result = paymentService.closeOrder(MEMBER_ID, ORDER_ID);
StepVerifier.create(result)
.expectNext(false)
.verifyComplete();
verify(paymentOrderRepository, never()).closeExpiredOrder(anyLong());
}
@Test
void closeOrder_whenOrderNotPending_shouldReturnFalse() {
PaymentOrder order = createTestOrder("SUCCESS");
when(paymentOrderRepository.findByOrderNo(ORDER_ID)).thenReturn(Mono.just(order));
Mono<Boolean> result = paymentService.closeOrder(MEMBER_ID, ORDER_ID);
StepVerifier.create(result)
.expectNext(false)
.verifyComplete();
verify(paymentOrderRepository, never()).closeExpiredOrder(anyLong());
}
@Test
void closeOrder_whenOrderNotFound_shouldReturnFalse() {
when(paymentOrderRepository.findByOrderNo(ORDER_ID)).thenReturn(Mono.empty());
Mono<Boolean> result = paymentService.closeOrder(MEMBER_ID, ORDER_ID);
StepVerifier.create(result)
.expectNext(false)
.verifyComplete();
}
// ==================== queryHuifuTradeList ====================
@Test
void queryHuifuTradeList_shouldReturnResult() {
Mono<Map<String, Object>> result = paymentService.queryHuifuTradeList(MEMBER_ID, "20260701", "20260731");
StepVerifier.create(result)
.assertNext(map -> {
assertThat(map).isNotNull();
assertThat(map).containsKey("begin_date");
assertThat(map).containsKey("end_date");
assertThat(map).containsKey("huifu_ids");
assertThat(map).containsKey("api_results");
assertThat(map).containsKey("trade_list");
assertThat(map).containsKey("total_count");
})
.verifyComplete();
}
// ==================== queryHuifuTradeByOrderId ====================
@Test
void queryHuifuTradeByOrderId_shouldReturnResult() {
Mono<Map<String, Object>> result = paymentService.queryHuifuTradeByOrderId(MEMBER_ID, ORDER_ID, null);
StepVerifier.create(result)
.assertNext(map -> {
assertThat(map).isNotNull();
assertThat(map).containsKey("out_ord_id");
assertThat(map).containsKey("success");
})
.verifyComplete();
}
}