| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295 |
- package com.xuekairui.gateway;
- import org.junit.jupiter.api.*;
- import org.springframework.http.MediaType;
- import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
- import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
- /**
- * 支付模块接口测试
- */
- @DisplayName("支付模块接口测试")
- @TestMethodOrder(MethodOrderer.OrderAnnotation.class)
- class PaymentControllerTest extends GatewayBaseTest {
- // ==================== 支付方案 ====================
- @Test
- @Order(1)
- @DisplayName("GET /api/payment/plans - 无需认证应返回方案列表")
- void getPlans_withoutAuth_shouldReturnPlans() throws Exception {
- mockMvc.perform(get("/api/payment/plans"))
- .andExpect(status().isOk())
- .andExpect(jsonPath("$.code").value(200))
- .andExpect(jsonPath("$.data").isArray())
- .andExpect(jsonPath("$.data.length()").value(org.hamcrest.Matchers.greaterThanOrEqualTo(2)))
- .andExpect(jsonPath("$.data[0].planCode").exists())
- .andExpect(jsonPath("$.data[0].planName").exists());
- }
- @Test
- @Order(2)
- @DisplayName("GET /api/payment/plans - 方案应包含价格、会员等级等字段")
- void getPlans_shouldContainRequiredFields() throws Exception {
- mockMvc.perform(get("/api/payment/plans"))
- .andExpect(status().isOk())
- .andExpect(jsonPath("$.data[0].price").isNumber())
- .andExpect(jsonPath("$.data[0].membershipLevel").exists())
- .andExpect(jsonPath("$.data[0].durationDays").isNumber())
- .andExpect(jsonPath("$.data[0].planCode").exists());
- }
- // ==================== 创建订单 ====================
- @Test
- @Order(3)
- @DisplayName("POST /api/payment/order/create - 微信支付应返回订单和二维码")
- void createOrder_wechat_shouldReturnQrCode() throws Exception {
- String token = generateAccessToken(1L, "13800138000");
- mockMvc.perform(post("/api/payment/order/create")
- .header("Authorization", "Bearer " + token)
- .contentType(MediaType.APPLICATION_JSON)
- .content("{\"planId\":1,\"channel\":\"WECHAT\"}"))
- .andExpect(status().isOk())
- .andExpect(jsonPath("$.code").value(200))
- .andExpect(jsonPath("$.data.orderNo").exists())
- .andExpect(jsonPath("$.data.planName").exists())
- .andExpect(jsonPath("$.data.amount").isNumber())
- .andExpect(jsonPath("$.data.channel").value("WECHAT"))
- .andExpect(jsonPath("$.data.status").value("PENDING"))
- .andExpect(jsonPath("$.data.qrCodeBase64").isString())
- .andExpect(jsonPath("$.data.expireTime").exists());
- }
- @Test
- @Order(4)
- @DisplayName("POST /api/payment/order/create - 支付宝应返回订单和二维码")
- void createOrder_alipay_shouldReturnQrCode() throws Exception {
- String token = generateAccessToken(1L, "13800138000");
- mockMvc.perform(post("/api/payment/order/create")
- .header("Authorization", "Bearer " + token)
- .contentType(MediaType.APPLICATION_JSON)
- .content("{\"planId\":1,\"channel\":\"ALIPAY\"}"))
- .andExpect(status().isOk())
- .andExpect(jsonPath("$.code").value(200))
- .andExpect(jsonPath("$.data.planName").exists())
- .andExpect(jsonPath("$.data.amount").isNumber())
- .andExpect(jsonPath("$.data.channel").value("ALIPAY"));
- }
- @Test
- @Order(5)
- @DisplayName("POST /api/payment/order/create - 无效渠道应返回错误")
- void createOrder_invalidChannel_shouldReturnError() throws Exception {
- String token = generateAccessToken(1L, "13800138000");
- mockMvc.perform(post("/api/payment/order/create")
- .header("Authorization", "Bearer " + token)
- .contentType(MediaType.APPLICATION_JSON)
- .content("{\"planId\":1,\"channel\":\"INVALID\"}"))
- .andExpect(status().isOk())
- .andExpect(jsonPath("$.code").value(2004));
- }
- @Test
- @Order(6)
- @DisplayName("POST /api/payment/order/create - 无Token应返回401")
- void createOrder_withoutToken_shouldReturn401() throws Exception {
- mockMvc.perform(post("/api/payment/order/create")
- .contentType(MediaType.APPLICATION_JSON)
- .content("{\"planId\":1,\"channel\":\"WECHAT\"}"))
- .andExpect(status().isUnauthorized());
- }
- // ==================== 查询订单 ====================
- @Test
- @Order(7)
- @DisplayName("GET /api/payment/order/{orderNo} - 查询存在的订单应返回详情")
- void queryOrder_existing_shouldReturnDetail() throws Exception {
- String token = generateAccessToken(1L, "13800138000");
- // 先创建订单
- var result = mockMvc.perform(post("/api/payment/order/create")
- .header("Authorization", "Bearer " + token)
- .contentType(MediaType.APPLICATION_JSON)
- .content("{\"planId\":1,\"channel\":\"WECHAT\"}"))
- .andExpect(status().isOk())
- .andReturn();
- String orderNo = new com.fasterxml.jackson.databind.ObjectMapper()
- .readTree(result.getResponse().getContentAsString())
- .get("data").get("orderNo").asText();
- // 再查询
- mockMvc.perform(get("/api/payment/order/" + orderNo)
- .header("Authorization", "Bearer " + token))
- .andExpect(status().isOk())
- .andExpect(jsonPath("$.code").value(200))
- .andExpect(jsonPath("$.data.orderNo").value(orderNo));
- }
- @Test
- @Order(8)
- @DisplayName("GET /api/payment/order/{orderNo} - 不存在的订单应返回错误")
- void queryOrder_nonexistent_shouldReturnError() throws Exception {
- String token = generateAccessToken(1L, "13800138000");
- mockMvc.perform(get("/api/payment/order/OP_NOTEXIST")
- .header("Authorization", "Bearer " + token))
- .andExpect(status().isOk())
- .andExpect(jsonPath("$.code").value(2001));
- }
- // ==================== 模拟支付 ====================
- @Test
- @Order(9)
- @DisplayName("POST /api/payment/order/{orderNo}/pay - 模拟支付应成功并激活会员")
- void mockPay_shouldSucceedAndActivateMembership() throws Exception {
- String token = generateAccessToken(1L, "13800138000");
- // 创建订单
- var result = mockMvc.perform(post("/api/payment/order/create")
- .header("Authorization", "Bearer " + token)
- .contentType(MediaType.APPLICATION_JSON)
- .content("{\"planId\":1,\"channel\":\"WECHAT\"}"))
- .andReturn();
- String orderNo = new com.fasterxml.jackson.databind.ObjectMapper()
- .readTree(result.getResponse().getContentAsString())
- .get("data").get("orderNo").asText();
- // 模拟支付
- mockMvc.perform(post("/api/payment/order/" + orderNo + "/pay")
- .header("Authorization", "Bearer " + token))
- .andExpect(status().isOk())
- .andExpect(jsonPath("$.code").value(200))
- .andExpect(jsonPath("$.data.status").value("PAID"))
- .andExpect(jsonPath("$.data.planName").exists())
- .andExpect(jsonPath("$.data.membershipLevel").exists())
- .andExpect(jsonPath("$.data.paidTime").exists());
- }
- @Test
- @Order(10)
- @DisplayName("POST /api/payment/order/{orderNo}/pay - 已支付订单不能再次支付")
- void mockPay_alreadyPaid_shouldReturnError() throws Exception {
- String token = generateAccessToken(1L, "13800138000");
- var result = mockMvc.perform(post("/api/payment/order/create")
- .header("Authorization", "Bearer " + token)
- .contentType(MediaType.APPLICATION_JSON)
- .content("{\"planId\":1,\"channel\":\"WECHAT\"}"))
- .andReturn();
- String orderNo = new com.fasterxml.jackson.databind.ObjectMapper()
- .readTree(result.getResponse().getContentAsString())
- .get("data").get("orderNo").asText();
- // 第一次支付
- mockMvc.perform(post("/api/payment/order/" + orderNo + "/pay")
- .header("Authorization", "Bearer " + token));
- // 第二次支付应返回已支付错误
- mockMvc.perform(post("/api/payment/order/" + orderNo + "/pay")
- .header("Authorization", "Bearer " + token))
- .andExpect(status().isOk())
- .andExpect(jsonPath("$.code").value(2005));
- }
- // ==================== 方案管理(需管理员权限,当前SecurityConfig中/admin/payment/**已纳入/admin/**保护) ====================
- @Test
- @Order(12)
- @DisplayName("GET /api/payment/orders - 查询用户订单列表")
- void listMyOrders_shouldReturnList() throws Exception {
- String token = generateAccessToken(1L, "13800138000");
- mockMvc.perform(get("/api/payment/orders")
- .header("Authorization", "Bearer " + token))
- .andExpect(status().isOk())
- .andExpect(jsonPath("$.code").value(200))
- .andExpect(jsonPath("$.data").isArray());
- }
- // ==================== 管理端支付方案管理 ====================
- @Test
- @Order(20)
- @DisplayName("POST /api/admin/payment/plans - 管理员创建支付方案")
- void createPlan_asAdmin_shouldSucceed() throws Exception {
- String token = generateAccessToken(1L, "13800138000");
- mockMvc.perform(post("/api/admin/payment/plans")
- .header("Authorization", "Bearer " + token)
- .contentType(MediaType.APPLICATION_JSON)
- .content("{\"planCode\":\"BIENNIAL\",\"planName\":\"两年卡\",\"price\":1599,\"originalPrice\":1798,\"durationDays\":730,\"membershipLevel\":\"PRO\",\"tag\":\"省11%\"}"))
- .andExpect(status().isOk())
- .andExpect(jsonPath("$.code").value(200))
- .andExpect(jsonPath("$.data.planCode").value("BIENNIAL"))
- .andExpect(jsonPath("$.data.planName").value("两年卡"));
- }
- @Test
- @Order(21)
- @DisplayName("PUT /api/admin/payment/plans/{id} - 管理员更新支付方案")
- void updatePlan_asAdmin_shouldSucceed() throws Exception {
- String token = generateAccessToken(1L, "13800138000");
- mockMvc.perform(put("/api/admin/payment/plans/1")
- .header("Authorization", "Bearer " + token)
- .contentType(MediaType.APPLICATION_JSON)
- .content("{\"planCode\":\"MONTHLY\",\"planName\":\"月卡(更新)\",\"price\":109,\"originalPrice\":109,\"durationDays\":30,\"membershipLevel\":\"PRO\"}"))
- .andExpect(status().isOk())
- .andExpect(jsonPath("$.code").value(200))
- .andExpect(jsonPath("$.data.planName").value("月卡(更新)"));
- }
- @Test
- @Order(22)
- @DisplayName("PUT /api/admin/payment/plans/{id}/status - 切换方案上架/下架")
- void togglePlanStatus_asAdmin_shouldSucceed() throws Exception {
- String token = generateAccessToken(1L, "13800138000");
- mockMvc.perform(put("/api/admin/payment/plans/1/status")
- .header("Authorization", "Bearer " + token)
- .contentType(MediaType.APPLICATION_JSON)
- .content("{\"status\":0}"))
- .andExpect(status().isOk())
- .andExpect(jsonPath("$.code").value(200));
- }
- @Test
- @Order(23)
- @DisplayName("POST /api/admin/payment/plans - 无Token应返回401")
- void createPlan_withoutToken_shouldReturn401() throws Exception {
- mockMvc.perform(post("/api/admin/payment/plans")
- .contentType(MediaType.APPLICATION_JSON)
- .content("{\"planCode\":\"TEST\",\"planName\":\"测试\",\"price\":1,\"durationDays\":1,\"membershipLevel\":\"PRO\"}"))
- .andExpect(status().isUnauthorized());
- }
- @Test
- @Order(24)
- @DisplayName("PUT /api/admin/payment/plans/{id} - 普通用户应返回403")
- void updatePlan_asNormalUser_shouldReturn403() throws Exception {
- String token = generateAccessToken(1L, "13800138000", "USER");
- mockMvc.perform(put("/api/admin/payment/plans/1")
- .header("Authorization", "Bearer " + token)
- .contentType(MediaType.APPLICATION_JSON)
- .content("{\"planCode\":\"MONTHLY\",\"planName\":\"月卡\",\"price\":99,\"durationDays\":30,\"membershipLevel\":\"PRO\"}"))
- .andExpect(status().isForbidden());
- }
- // ==================== 支付回调 ====================
- @Test
- @Order(30)
- @DisplayName("POST /api/payment/callback/wechat - 缺少订单号应返回FAIL")
- void wechatCallback_noOrderNo_shouldReturnFail() throws Exception {
- mockMvc.perform(post("/api/payment/callback/wechat")
- .contentType(MediaType.APPLICATION_FORM_URLENCODED))
- .andExpect(status().isOk())
- .andExpect(content().string("FAIL"));
- }
- @Test
- @Order(31)
- @DisplayName("POST /api/payment/callback/alipay - 缺少订单号应返回fail")
- void alipayCallback_noOrderNo_shouldReturnFail() throws Exception {
- mockMvc.perform(post("/api/payment/callback/alipay")
- .contentType(MediaType.APPLICATION_FORM_URLENCODED))
- .andExpect(status().isOk())
- .andExpect(content().string("fail"));
- }
- }
|