完善后台订单筛选及接口测试

This commit is contained in:
irving
2025-11-07 23:42:15 -05:00
parent 5c3fa1e33f
commit 51ec9dd85b
3 changed files with 376 additions and 0 deletions

View File

@@ -0,0 +1,323 @@
package com.starry.admin.api;
import static org.assertj.core.api.Assertions.assertThat;
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.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.starry.admin.common.apitest.ApiTestDataSeeder;
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
import com.starry.admin.modules.order.service.IPlayOrderInfoService;
import com.starry.admin.utils.SecurityUtils;
import com.starry.common.context.CustomSecurityContextHolder;
import com.starry.common.utils.IdUtils;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.function.Consumer;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MvcResult;
class PlayOrderInfoControllerApiTest extends AbstractApiTest {
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
private static final BigDecimal DEFAULT_AMOUNT = new BigDecimal("188.00");
@Autowired
private IPlayOrderInfoService orderInfoService;
private final ObjectMapper objectMapper = new ObjectMapper();
private final List<String> orderIdsToCleanup = new ArrayList<>();
@AfterEach
void tearDown() {
ensureTenantContext();
if (!orderIdsToCleanup.isEmpty()) {
orderInfoService.removeByIds(orderIdsToCleanup);
orderIdsToCleanup.clear();
}
CustomSecurityContextHolder.remove();
}
@Test
void listByPage_honorsAllSupportedFilters() throws Exception {
ensureTenantContext();
String marker = ("FT" + IdUtils.getUuid().replace("-", "").substring(0, 6)).toUpperCase();
LocalDateTime reference = LocalDateTime.now().minusHours(6).withNano(0);
PlayOrderInfoEntity matching = persistOrder(marker, "match", reference, order -> {
order.setOrderStatus("3");
order.setPlaceType("2");
order.setPayMethod("2");
order.setUseCoupon("1");
order.setBackendEntry("1");
order.setFirstOrder("0");
order.setGroupId(ApiTestDataSeeder.DEFAULT_GROUP_ID);
order.setSex("2");
order.setAcceptBy(ApiTestDataSeeder.DEFAULT_CLERK_ID);
order.setPurchaserBy(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID);
});
persistOrder(marker, "noise", reference.minusDays(3), order -> {
order.setOrderStatus("0");
order.setPlaceType("0");
order.setPayMethod("0");
order.setUseCoupon("0");
order.setBackendEntry("0");
order.setFirstOrder("1");
order.setGroupId(marker + "-grp");
order.setSex("1");
order.setAcceptBy(null);
order.setPurchaserBy("customer-" + marker);
});
// orderNo filter (exact)
ObjectNode orderNoPayload = baseQuery();
orderNoPayload.put("orderNo", matching.getOrderNo());
assertFilterMatches(orderNoPayload, matching.getId());
// acceptBy filter
ObjectNode acceptPayload = queryWithMarker(marker);
acceptPayload.put("acceptBy", matching.getAcceptBy());
assertFilterMatches(acceptPayload, matching.getId());
// purchaserBy filter
ObjectNode purchaserPayload = queryWithMarker(marker);
purchaserPayload.put("purchaserBy", matching.getPurchaserBy());
assertFilterMatches(purchaserPayload, matching.getId());
// orderStatus filter
ObjectNode statusPayload = queryWithMarker(marker);
statusPayload.put("orderStatus", matching.getOrderStatus());
assertFilterMatches(statusPayload, matching.getId());
// placeType filter
ObjectNode placePayload = queryWithMarker(marker);
placePayload.put("placeType", matching.getPlaceType());
assertFilterMatches(placePayload, matching.getId());
// payMethod filter
ObjectNode payPayload = queryWithMarker(marker);
payPayload.put("payMethod", matching.getPayMethod());
assertFilterMatches(payPayload, matching.getId());
// useCoupon filter
ObjectNode couponPayload = queryWithMarker(marker);
couponPayload.put("useCoupon", matching.getUseCoupon());
assertFilterMatches(couponPayload, matching.getId());
// backendEntry filter
ObjectNode backendPayload = queryWithMarker(marker);
backendPayload.put("backendEntry", matching.getBackendEntry());
assertFilterMatches(backendPayload, matching.getId());
// firstOrder filter
ObjectNode firstOrderPayload = queryWithMarker(marker);
firstOrderPayload.put("firstOrder", matching.getFirstOrder());
assertFilterMatches(firstOrderPayload, matching.getId());
// groupId filter
ObjectNode groupPayload = queryWithMarker(marker);
groupPayload.put("groupId", matching.getGroupId());
assertFilterMatches(groupPayload, matching.getId());
// sex filter
ObjectNode sexPayload = queryWithMarker(marker);
sexPayload.put("sex", matching.getSex());
assertFilterMatches(sexPayload, matching.getId());
// purchaserTime range filter
ObjectNode purchaserTimePayload = queryWithMarker(marker);
purchaserTimePayload.set("purchaserTime", range(
reference.minusMinutes(30),
reference.plusMinutes(30)));
assertFilterMatches(purchaserTimePayload, matching.getId());
// acceptTime range filter
ObjectNode acceptTimePayload = queryWithMarker(marker);
acceptTimePayload.set("acceptTime", range(
matching.getAcceptTime().minusMinutes(15),
matching.getAcceptTime().plusMinutes(15)));
assertFilterMatches(acceptTimePayload, matching.getId());
// endOrderTime range filter
ObjectNode endTimePayload = queryWithMarker(marker);
endTimePayload.set("endOrderTime", range(
matching.getOrderEndTime().minusMinutes(15),
matching.getOrderEndTime().plusMinutes(15)));
assertFilterMatches(endTimePayload, matching.getId());
// Combined filters to verify logical AND behaviour
ObjectNode combinedPayload = queryWithMarker(marker);
combinedPayload.put("acceptBy", matching.getAcceptBy());
combinedPayload.put("purchaserBy", matching.getPurchaserBy());
combinedPayload.put("orderStatus", matching.getOrderStatus());
combinedPayload.put("placeType", matching.getPlaceType());
combinedPayload.put("payMethod", matching.getPayMethod());
combinedPayload.put("useCoupon", matching.getUseCoupon());
combinedPayload.put("backendEntry", matching.getBackendEntry());
combinedPayload.put("firstOrder", matching.getFirstOrder());
combinedPayload.put("groupId", matching.getGroupId());
combinedPayload.put("sex", matching.getSex());
combinedPayload.set("purchaserTime", range(
reference.minusMinutes(5),
reference.plusMinutes(5)));
combinedPayload.set("acceptTime", range(
matching.getAcceptTime().minusMinutes(5),
matching.getAcceptTime().plusMinutes(5)));
combinedPayload.set("endOrderTime", range(
matching.getOrderEndTime().minusMinutes(5),
matching.getOrderEndTime().plusMinutes(5)));
assertFilterMatches(combinedPayload, matching.getId());
}
private PlayOrderInfoEntity persistOrder(
String marker,
String token,
LocalDateTime purchaserTime,
Consumer<PlayOrderInfoEntity> customizer) {
PlayOrderInfoEntity order = buildBaselineOrder(marker, token, purchaserTime);
customizer.accept(order);
assertThat(orderInfoService.save(order))
.withFailMessage("Failed to persist order %s", order.getOrderNo())
.isTrue();
orderIdsToCleanup.add(order.getId());
return order;
}
private PlayOrderInfoEntity buildBaselineOrder(String marker, String token, LocalDateTime purchaserTime) {
PlayOrderInfoEntity order = new PlayOrderInfoEntity();
order.setId("order-" + token + "-" + IdUtils.getUuid().substring(0, 8));
String tokenFragment = token.length() >= 2 ? token.substring(0, 2) : token;
order.setOrderNo(marker + tokenFragment.toUpperCase() + IdUtils.getUuid().substring(0, 4));
order.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
order.setOrderType("2");
order.setPlaceType("1");
order.setRewardType("0");
order.setFirstOrder("0");
order.setRefundType("0");
order.setRefundAmount(BigDecimal.ZERO);
order.setRefundReason(null);
order.setOrderMoney(DEFAULT_AMOUNT);
order.setDiscountAmount(BigDecimal.ZERO);
order.setFinalAmount(DEFAULT_AMOUNT);
order.setEstimatedRevenue(new BigDecimal("88.00"));
order.setEstimatedRevenueRatio(50);
order.setLabels(Collections.singletonList("label-" + marker));
order.setUseCoupon("1");
order.setCouponIds(Collections.singletonList("coupon-" + marker));
order.setBackendEntry("1");
order.setPaymentSource("balance");
order.setPayMethod("2");
order.setPayState("2");
order.setWeiChatCode("wx-" + marker);
order.setPurchaserBy(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID);
order.setPurchaserTime(purchaserTime);
order.setAcceptBy(ApiTestDataSeeder.DEFAULT_CLERK_ID);
order.setAcceptTime(purchaserTime.plusMinutes(20));
order.setGroupId(ApiTestDataSeeder.DEFAULT_GROUP_ID);
order.setOrderStartTime(purchaserTime.plusMinutes(30));
order.setOrderEndTime(purchaserTime.plusHours(2));
order.setOrderSettlementState("0");
order.setOrdersExpiredState("0");
order.setSex("2");
order.setCommodityId(ApiTestDataSeeder.DEFAULT_COMMODITY_ID);
order.setCommodityType("1");
order.setCommodityPrice(DEFAULT_AMOUNT);
order.setCommodityName("API Filter Commodity");
order.setServiceDuration("60min");
order.setCommodityNumber("1");
order.setLevelId(ApiTestDataSeeder.DEFAULT_CLERK_LEVEL_ID);
order.setExcludeHistory("0");
order.setRemark("marker-" + marker);
order.setBackendRemark("backend-" + marker);
order.setProfitSharingAmount(BigDecimal.ZERO);
order.setDeleted(Boolean.FALSE);
order.setCreatedBy("apitest");
order.setUpdatedBy("apitest");
order.setCreatedTime(toDate(purchaserTime));
order.setUpdatedTime(toDate(purchaserTime.plusMinutes(45)));
return order;
}
private ObjectNode baseQuery() {
ObjectNode node = objectMapper.createObjectNode();
node.put("pageNum", 1);
node.put("pageSize", 20);
return node;
}
private ObjectNode queryWithMarker(String marker) {
ObjectNode node = baseQuery();
node.put("orderNo", marker);
return node;
}
private ArrayNode range(LocalDateTime start, LocalDateTime end) {
ArrayNode array = objectMapper.createArrayNode();
array.add(DATE_TIME_FORMATTER.format(start));
array.add(DATE_TIME_FORMATTER.format(end));
return array;
}
private void assertFilterMatches(ObjectNode payload, String expectedOrderId) throws Exception {
RecordsResponse response = executeList(payload);
JsonNode records = response.records;
assertThat(records.isArray())
.withFailMessage("Records payload is not an array for body=%s | response=%s", payload, response.rawResponse)
.isTrue();
assertThat(records.size())
.withFailMessage("Unexpected record count for body=%s | response=%s", payload, response.rawResponse)
.isEqualTo(1);
assertThat(records.get(0).path("id").asText())
.withFailMessage("Unexpected order id for body=%s | response=%s", payload, response.rawResponse)
.isEqualTo(expectedOrderId);
}
private RecordsResponse executeList(ObjectNode payload) throws Exception {
MvcResult result = mockMvc.perform(post("/order/order/listByPage")
.contentType(MediaType.APPLICATION_JSON)
.header(TENANT_HEADER, DEFAULT_TENANT)
.header(USER_HEADER, DEFAULT_USER)
.content(payload.toString()))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(200))
.andReturn();
String responseBody = result.getResponse().getContentAsString();
JsonNode root = objectMapper.readTree(responseBody);
JsonNode data = root.path("data");
JsonNode records = data.isArray() ? data : data.path("records");
return new RecordsResponse(records, responseBody);
}
private Date toDate(LocalDateTime time) {
return Date.from(time.atZone(ZoneId.systemDefault()).toInstant());
}
private void ensureTenantContext() {
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
}
private static class RecordsResponse {
private final JsonNode records;
private final String rawResponse;
private RecordsResponse(JsonNode records, String rawResponse) {
this.records = records;
this.rawResponse = rawResponse;
}
}
}