API-test-in-progress

This commit is contained in:
irving
2025-11-01 15:16:45 -04:00
parent f3480b6ba0
commit 04b9960e35
12 changed files with 2253 additions and 19 deletions

View File

@@ -0,0 +1,354 @@
package com.starry.admin.api;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import com.starry.admin.common.apitest.ApiTestDataSeeder;
import com.starry.admin.modules.order.module.constant.OrderConstant;
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
import com.starry.admin.modules.shop.module.constant.CouponUseState;
import com.starry.admin.modules.shop.module.entity.PlayCouponDetailsEntity;
import com.starry.admin.modules.shop.module.vo.PlayCouponDetailsReturnVo;
import com.starry.admin.modules.weichat.service.WxCustomMpService;
import com.starry.admin.utils.SecurityUtils;
import com.starry.common.constant.Constants;
import com.starry.common.context.CustomSecurityContextHolder;
import com.starry.common.utils.IdUtils;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Collections;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
class WxCustomSpecifiedOrderApiTest extends WxCustomOrderApiTestSupport {
@MockBean
private WxCustomMpService wxCustomMpService;
@org.springframework.beans.factory.annotation.Autowired
private com.starry.admin.modules.order.service.IPlayOrderInfoService orderInfoService;
@org.springframework.beans.factory.annotation.Autowired
private com.starry.admin.modules.weichat.service.WxTokenService clerkWxTokenService;
@Test
// 测试用例:指定单取消后优惠券应恢复为未使用
void specifiedOrderCancellationReleasesCoupon() throws Exception {
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
String remark = "API specified cancel " + IdUtils.getUuid();
BigDecimal discount = new BigDecimal("10.00");
try {
resetCustomerBalance();
String customerToken = wxTokenService.createWxUserToken(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID);
customUserInfoService.updateTokenById(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID, customerToken);
String couponInfoId = createCouponId("cpn-sc-");
ensureFixedReductionCoupon(
couponInfoId,
OrderConstant.PlaceType.SPECIFIED,
discount,
new BigDecimal("60.00"),
"0");
String couponDetailId = claimCouponForOrder(couponInfoId, OrderConstant.PlaceType.SPECIFIED, customerToken);
String payload = "{" +
"\"clerkId\":\"" + ApiTestDataSeeder.DEFAULT_CLERK_ID + "\"," +
"\"commodityId\":\"" + ApiTestDataSeeder.DEFAULT_COMMODITY_ID + "\"," +
"\"commodityQuantity\":1," +
"\"weiChatCode\":\"apitest-customer-wx\"," +
"\"couponIds\":[\"" + couponDetailId + "\"]," +
"\"remark\":\"" + remark + "\"" +
"}";
mockMvc.perform(post("/wx/custom/order/commodity")
.header(USER_HEADER, DEFAULT_USER)
.header(TENANT_HEADER, DEFAULT_TENANT)
.header(Constants.CUSTOM_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + customerToken)
.contentType(MediaType.APPLICATION_JSON)
.content(payload))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(200));
ensureTenantContext();
PlayOrderInfoEntity order = playOrderInfoService.lambdaQuery()
.eq(PlayOrderInfoEntity::getPurchaserBy, ApiTestDataSeeder.DEFAULT_CUSTOMER_ID)
.eq(PlayOrderInfoEntity::getRemark, remark)
.orderByDesc(PlayOrderInfoEntity::getCreatedTime)
.last("limit 1")
.one();
Assertions.assertThat(order).isNotNull();
String cancelPayload = "{" +
"\"orderId\":\"" + order.getId() + "\"," +
"\"refundReason\":\"测试取消\"," +
"\"images\":[]" +
"}";
mockMvc.perform(post("/wx/custom/order/cancellation")
.header(USER_HEADER, DEFAULT_USER)
.header(TENANT_HEADER, DEFAULT_TENANT)
.header(Constants.CUSTOM_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + customerToken)
.contentType(MediaType.APPLICATION_JSON)
.content(cancelPayload))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(200))
.andExpect(jsonPath("$.data").value("取消成功"));
ensureTenantContext();
PlayOrderInfoEntity cancelled = playOrderInfoService.selectOrderInfoById(order.getId());
Assertions.assertThat(cancelled.getOrderStatus()).isEqualTo(OrderConstant.OrderStatus.CANCELLED.getCode());
PlayCouponDetailsEntity detail = couponDetailsService.getById(couponDetailId);
Assertions.assertThat(detail.getUseState())
.as("取消指定单后优惠券应恢复为未使用")
.isEqualTo(CouponUseState.UNUSED.getCode());
} finally {
CustomSecurityContextHolder.remove();
}
}
@Test
// 测试用例:指定单使用满减券下单后,应按折后金额推送创建通知,
// 并将预计收益扣除优惠金额,同时把优惠券详情标记为已使用。
void specifiedOrderWithCouponAdjustsAmountAndRevenue() throws Exception {
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
String remark = "API specified coupon " + IdUtils.getUuid();
BigDecimal discount = new BigDecimal("15.00");
try {
reset(wxCustomMpService);
resetCustomerBalance();
String customerToken = wxTokenService.createWxUserToken(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID);
customUserInfoService.updateTokenById(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID, customerToken);
BigDecimal grossAmount = ApiTestDataSeeder.DEFAULT_COMMODITY_PRICE;
String couponInfoId = createCouponId("cpn-s-");
ensureFixedReductionCoupon(
couponInfoId,
OrderConstant.PlaceType.SPECIFIED,
discount,
new BigDecimal("60.00"),
"0");
String couponDetailId = claimCouponForOrder(couponInfoId, OrderConstant.PlaceType.SPECIFIED, customerToken);
String payload = "{" +
"\"clerkId\":\"" + ApiTestDataSeeder.DEFAULT_CLERK_ID + "\"," +
"\"commodityId\":\"" + ApiTestDataSeeder.DEFAULT_COMMODITY_ID + "\"," +
"\"commodityQuantity\":1," +
"\"weiChatCode\":\"apitest-customer-wx\"," +
"\"couponIds\":[\"" + couponDetailId + "\"]," +
"\"remark\":\"" + remark + "\"" +
"}";
mockMvc.perform(post("/wx/custom/order/commodity")
.header(USER_HEADER, DEFAULT_USER)
.header(TENANT_HEADER, DEFAULT_TENANT)
.header(Constants.CUSTOM_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + customerToken)
.contentType(MediaType.APPLICATION_JSON)
.content(payload))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(200))
.andExpect(jsonPath("$.data").value("成功"));
ensureTenantContext();
PlayOrderInfoEntity order = playOrderInfoService.lambdaQuery()
.eq(PlayOrderInfoEntity::getPurchaserBy, ApiTestDataSeeder.DEFAULT_CUSTOMER_ID)
.eq(PlayOrderInfoEntity::getRemark, remark)
.orderByDesc(PlayOrderInfoEntity::getCreatedTime)
.last("limit 1")
.one();
Assertions.assertThat(order).isNotNull();
Assertions.assertThat(order.getCouponIds()).contains(couponDetailId);
BigDecimal expectedNet = grossAmount.subtract(discount).setScale(2, RoundingMode.HALF_UP);
Assertions.assertThat(order.getFinalAmount()).isEqualByComparingTo(expectedNet);
Assertions.assertThat(order.getDiscountAmount()).isEqualByComparingTo(discount);
verify(wxCustomMpService).sendCreateOrderMessage(
eq(ApiTestDataSeeder.DEFAULT_TENANT_ID),
eq(ApiTestDataSeeder.DEFAULT_CLERK_OPEN_ID),
anyString(),
eq(expectedNet.toString()),
eq(order.getCommodityName()),
eq(order.getId()));
int ratio = order.getEstimatedRevenueRatio();
BigDecimal baseRevenue = grossAmount
.multiply(BigDecimal.valueOf(ratio))
.divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP);
PlayCouponDetailsReturnVo detail = couponDetailsService.selectPlayCouponDetailsById(couponDetailId);
BigDecimal clerkDiscount = BigDecimal.ZERO;
if (detail != null && "0".equals(detail.getAttributionDiscounts())) {
BigDecimal discountAmount = detail.getDiscountAmount() == null ? BigDecimal.ZERO : detail.getDiscountAmount();
clerkDiscount = discountAmount;
}
BigDecimal expectedRevenue = baseRevenue.subtract(clerkDiscount).max(BigDecimal.ZERO).setScale(2, RoundingMode.HALF_UP);
Assertions.assertThat(order.getEstimatedRevenue()).isEqualByComparingTo(expectedRevenue);
assertCouponUsed(couponDetailId);
} finally {
CustomSecurityContextHolder.remove();
}
}
@Test
// 测试用例:指定单在接单后由管理员强制取消时,优惠券应恢复为未使用
void specifiedOrderForceCancelReleasesCoupon() throws Exception {
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
String remark = "API specified force cancel " + IdUtils.getUuid();
BigDecimal discount = new BigDecimal("8.00");
try {
reset(wxCustomMpService);
resetCustomerBalance();
String customerToken = wxTokenService.createWxUserToken(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID);
customUserInfoService.updateTokenById(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID, customerToken);
String clerkToken = wxTokenService.createWxUserToken(ApiTestDataSeeder.DEFAULT_CLERK_ID);
clerkUserInfoService.updateTokenById(ApiTestDataSeeder.DEFAULT_CLERK_ID, clerkToken);
String couponInfoId = createCouponId("cpn-sf-");
ensureFixedReductionCoupon(
couponInfoId,
OrderConstant.PlaceType.SPECIFIED,
discount,
new BigDecimal("60.00"),
"0");
String couponDetailId = claimCouponForOrder(couponInfoId, OrderConstant.PlaceType.SPECIFIED, customerToken);
String payload = "{" +
"\"clerkId\":\"" + ApiTestDataSeeder.DEFAULT_CLERK_ID + "\"," +
"\"commodityId\":\"" + ApiTestDataSeeder.DEFAULT_COMMODITY_ID + "\"," +
"\"commodityQuantity\":1," +
"\"weiChatCode\":\"apitest-customer-wx\"," +
"\"couponIds\":[\"" + couponDetailId + "\"]," +
"\"remark\":\"" + remark + "\"" +
"}";
mockMvc.perform(post("/wx/custom/order/commodity")
.header(USER_HEADER, DEFAULT_USER)
.header(TENANT_HEADER, DEFAULT_TENANT)
.header(Constants.CUSTOM_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + customerToken)
.contentType(MediaType.APPLICATION_JSON)
.content(payload))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(200));
ensureTenantContext();
PlayOrderInfoEntity order = playOrderInfoService.lambdaQuery()
.eq(PlayOrderInfoEntity::getPurchaserBy, ApiTestDataSeeder.DEFAULT_CUSTOMER_ID)
.eq(PlayOrderInfoEntity::getRemark, remark)
.orderByDesc(PlayOrderInfoEntity::getCreatedTime)
.last("limit 1")
.one();
Assertions.assertThat(order).isNotNull();
mockMvc.perform(get("/wx/clerk/order/accept")
.param("id", order.getId())
.header(USER_HEADER, DEFAULT_USER)
.header(TENANT_HEADER, DEFAULT_TENANT)
.header(Constants.CLERK_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + clerkToken))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(200))
.andExpect(jsonPath("$.data").value("成功"));
ensureTenantContext();
orderInfoService.forceCancelOngoingOrder(
"2",
ApiTestDataSeeder.DEFAULT_ADMIN_USER_ID,
order.getId(),
order.getFinalAmount(),
"管理员强制取消",
java.util.Collections.emptyList());
ensureTenantContext();
PlayOrderInfoEntity cancelled = playOrderInfoService.selectOrderInfoById(order.getId());
Assertions.assertThat(cancelled.getOrderStatus()).isEqualTo(OrderConstant.OrderStatus.CANCELLED.getCode());
PlayCouponDetailsEntity detail = couponDetailsService.getById(couponDetailId);
Assertions.assertThat(detail.getUseState())
.as("强制取消指定单后优惠券应恢复为未使用")
.isEqualTo(CouponUseState.UNUSED.getCode());
} finally {
CustomSecurityContextHolder.remove();
}
}
@Test
// 测试用例:客户携带指定陪玩师和服务下单时,接口需返回成功,并生成待支付状态的指定订单,
// 验证订单金额与种子服务价格一致、陪玩师被正确指派,同时触发微信创建订单通知。
void specifiedOrderCreatesPendingOrder() throws Exception {
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
try {
resetCustomerBalance();
String customerToken = wxTokenService.createWxUserToken(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID);
customUserInfoService.updateTokenById(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID, customerToken);
String remark = "API specified order " + IdUtils.getUuid();
ensureTenantContext();
long beforeCount = playOrderInfoService.lambdaQuery()
.eq(PlayOrderInfoEntity::getPurchaserBy, ApiTestDataSeeder.DEFAULT_CUSTOMER_ID)
.eq(PlayOrderInfoEntity::getPlaceType, OrderConstant.PlaceType.SPECIFIED.getCode())
.count();
String payload = "{" +
"\"clerkId\":\"" + ApiTestDataSeeder.DEFAULT_CLERK_ID + "\"," +
"\"commodityId\":\"" + ApiTestDataSeeder.DEFAULT_COMMODITY_ID + "\"," +
"\"commodityQuantity\":1," +
"\"weiChatCode\":\"apitest-customer-wx\"," +
"\"couponIds\":[]," +
"\"remark\":\"" + remark + "\"" +
"}";
reset(wxCustomMpService);
mockMvc.perform(post("/wx/custom/order/commodity")
.header(USER_HEADER, DEFAULT_USER)
.header(TENANT_HEADER, DEFAULT_TENANT)
.header(Constants.CUSTOM_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + customerToken)
.contentType(MediaType.APPLICATION_JSON)
.content(payload))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(200))
.andExpect(jsonPath("$.data").value("成功"));
ensureTenantContext();
PlayOrderInfoEntity order = playOrderInfoService.lambdaQuery()
.eq(PlayOrderInfoEntity::getPurchaserBy, ApiTestDataSeeder.DEFAULT_CUSTOMER_ID)
.eq(PlayOrderInfoEntity::getRemark, remark)
.orderByDesc(PlayOrderInfoEntity::getCreatedTime)
.last("limit 1")
.one();
Assertions.assertThat(order).isNotNull();
Assertions.assertThat(order.getPlaceType()).isEqualTo(OrderConstant.PlaceType.SPECIFIED.getCode());
Assertions.assertThat(order.getOrderStatus()).isEqualTo(OrderConstant.OrderStatus.PENDING.getCode());
Assertions.assertThat(order.getAcceptBy()).isEqualTo(ApiTestDataSeeder.DEFAULT_CLERK_ID);
Assertions.assertThat(order.getFinalAmount()).isEqualByComparingTo(ApiTestDataSeeder.DEFAULT_COMMODITY_PRICE);
verify(wxCustomMpService).sendCreateOrderMessage(
eq(ApiTestDataSeeder.DEFAULT_TENANT_ID),
eq(ApiTestDataSeeder.DEFAULT_CLERK_OPEN_ID),
anyString(),
eq(order.getFinalAmount().toString()),
eq(order.getCommodityName()),
eq(order.getId()));
ensureTenantContext();
long afterCount = playOrderInfoService.lambdaQuery()
.eq(PlayOrderInfoEntity::getPurchaserBy, ApiTestDataSeeder.DEFAULT_CUSTOMER_ID)
.eq(PlayOrderInfoEntity::getPlaceType, OrderConstant.PlaceType.SPECIFIED.getCode())
.count();
Assertions.assertThat(afterCount).isEqualTo(beforeCount + 1);
} finally {
CustomSecurityContextHolder.remove();
}
}
}