package com.starry.admin.api; import static org.assertj.core.api.Assertions.assertThat; 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.fasterxml.jackson.databind.JsonNode; 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.constant.OrderConstant; import com.starry.admin.modules.order.module.entity.PlayOrderComplaintInfoEntity; import com.starry.admin.modules.order.module.entity.PlayOrderEvaluateInfoEntity; import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity; import com.starry.admin.modules.order.service.IPlayOrderComplaintInfoService; import com.starry.admin.modules.order.service.IPlayOrderEvaluateInfoService; 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.time.LocalDateTime; import java.util.ArrayList; import java.util.List; 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 WxCustomOrderQueryApiTest extends WxCustomOrderApiTestSupport { @Autowired private IPlayOrderEvaluateInfoService playOrderEvaluateInfoService; @Autowired private IPlayOrderComplaintInfoService playOrderComplaintInfoService; private final List orderIdsToCleanup = new ArrayList<>(); private final List evalIdsToCleanup = new ArrayList<>(); private final List complaintIdsToCleanup = new ArrayList<>(); @AfterEach void cleanUpOrders() { if (!orderIdsToCleanup.isEmpty()) { playOrderInfoService.removeByIds(orderIdsToCleanup); orderIdsToCleanup.clear(); } if (!evalIdsToCleanup.isEmpty()) { playOrderEvaluateInfoService.removeByIds(evalIdsToCleanup); evalIdsToCleanup.clear(); } if (!complaintIdsToCleanup.isEmpty()) { playOrderComplaintInfoService.removeByIds(complaintIdsToCleanup); complaintIdsToCleanup.clear(); } } @Test void queryByPageFiltersRevokedOrdersAndDetailShowsReason() 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 = "revoked-flow-" + IdUtils.getUuid(); placeRandomOrder(remark, customerToken); ensureTenantContext(); PlayOrderInfoEntity createdOrder = playOrderInfoService.lambdaQuery() .eq(PlayOrderInfoEntity::getPurchaserBy, ApiTestDataSeeder.DEFAULT_CUSTOMER_ID) .eq(PlayOrderInfoEntity::getRemark, remark) .orderByDesc(PlayOrderInfoEntity::getCreatedTime) .last("limit 1") .one(); assertThat(createdOrder).as("Order with remark %s should exist", remark).isNotNull(); String orderId = createdOrder.getId(); ensureTenantContext(); playOrderInfoService.lambdaUpdate() .set(PlayOrderInfoEntity::getOrderType, OrderConstant.OrderType.GIFT.getCode()) .eq(PlayOrderInfoEntity::getId, orderId) .update(); ensureTenantContext(); playOrderInfoService.updateStateTo1( OrderConstant.OperatorType.CLERK.getCode(), ApiTestDataSeeder.DEFAULT_CLERK_ID, ApiTestDataSeeder.DEFAULT_CLERK_ID, orderId); ensureTenantContext(); playOrderInfoService.updateStateTo23( OrderConstant.OperatorType.CLERK.getCode(), ApiTestDataSeeder.DEFAULT_CLERK_ID, OrderConstant.OrderStatus.IN_PROGRESS.getCode(), orderId); ensureTenantContext(); playOrderInfoService.updateStateTo23( OrderConstant.OperatorType.ADMIN.getCode(), ApiTestDataSeeder.DEFAULT_ADMIN_USER_ID, OrderConstant.OrderStatus.COMPLETED.getCode(), orderId); String revokeReason = "auto-revoke-" + IdUtils.getUuid(); String revokePayload = "{" + "\"orderId\":\"" + orderId + "\"," + "\"refundToCustomer\":false," + "\"refundReason\":\"" + revokeReason + "\"," + "\"earningsStrategy\":\"NONE\"" + "}"; mockMvc.perform(post("/order/order/revokeCompleted") .header(USER_HEADER, DEFAULT_USER) .header(TENANT_HEADER, DEFAULT_TENANT) .contentType(MediaType.APPLICATION_JSON) .content(revokePayload)) .andExpect(status().isOk()) .andExpect(jsonPath("$.code").value(200)); ObjectNode filterPayload = basePayload(1, 10); filterPayload.put("orderStatus", OrderConstant.OrderStatus.REVOKED.getCode()); JsonNode listRoot = executeOrderQuery(customerToken, filterPayload); JsonNode dataNode = listRoot.path("data"); JsonNode records = dataNode.isArray() ? dataNode : dataNode.path("records"); assertThat(records.isArray()).as("List response should contain records array").isTrue(); assertThat(records.size()).as("Should return at least one revoked order").isGreaterThan(0); boolean found = false; for (JsonNode node : records) { assertThat(node.path("orderStatus").asText()).isEqualTo("5"); if (orderId.equals(node.path("id").asText())) { found = true; } } assertThat(found).as("Revoked order should be present in filter result").isTrue(); MvcResult detailResult = mockMvc.perform(get("/wx/custom/order/queryById") .header(USER_HEADER, DEFAULT_USER) .header(TENANT_HEADER, DEFAULT_TENANT) .header(Constants.CUSTOM_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + customerToken) .param("id", orderId)) .andExpect(status().isOk()) .andExpect(jsonPath("$.code").value(200)) .andReturn(); JsonNode detailRoot = objectMapper.readTree(detailResult.getResponse().getContentAsString()); JsonNode detail = detailRoot.path("data"); assertThat(detail.path("orderStatus").asText()).isEqualTo("5"); assertThat(detail.path("refundReason").asText()).isEqualTo(revokeReason); } finally { CustomSecurityContextHolder.remove(); } } @Test void queryByPageSkipsRechargeOrdersByDefaultButAllowsExplicitFilter() 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 rechargeRemark = "recharge-like-" + IdUtils.getUuid(); placeRandomOrder(rechargeRemark, customerToken); PlayOrderInfoEntity rechargeOrder = playOrderInfoService.lambdaQuery() .eq(PlayOrderInfoEntity::getPurchaserBy, ApiTestDataSeeder.DEFAULT_CUSTOMER_ID) .eq(PlayOrderInfoEntity::getRemark, rechargeRemark) .orderByDesc(PlayOrderInfoEntity::getCreatedTime) .last("limit 1") .one(); assertThat(rechargeOrder).isNotNull(); ensureTenantContext(); playOrderInfoService.lambdaUpdate() .set(PlayOrderInfoEntity::getOrderType, OrderConstant.OrderType.RECHARGE.getCode()) .eq(PlayOrderInfoEntity::getId, rechargeOrder.getId()) .update(); String giftRemark = "gift-like-" + IdUtils.getUuid(); placeRandomOrder(giftRemark, customerToken); PlayOrderInfoEntity giftOrder = playOrderInfoService.lambdaQuery() .eq(PlayOrderInfoEntity::getPurchaserBy, ApiTestDataSeeder.DEFAULT_CUSTOMER_ID) .eq(PlayOrderInfoEntity::getRemark, giftRemark) .orderByDesc(PlayOrderInfoEntity::getCreatedTime) .last("limit 1") .one(); assertThat(giftOrder).isNotNull(); ensureTenantContext(); playOrderInfoService.lambdaUpdate() .set(PlayOrderInfoEntity::getOrderType, OrderConstant.OrderType.GIFT.getCode()) .eq(PlayOrderInfoEntity::getId, giftOrder.getId()) .update(); ObjectNode defaultPayload = basePayload(1, 20); JsonNode defaultRecords = queryOrders(customerToken, defaultPayload); assertThat(defaultRecords.size()).isGreaterThan(0); assertThat(defaultRecords).noneMatch(node -> rechargeOrder.getId().equals(node.path("id").asText())); ObjectNode explicitPayload = basePayload(1, 20); explicitPayload.put("orderType", OrderConstant.OrderType.GIFT.getCode()); JsonNode filteredRecords = queryOrders(customerToken, explicitPayload); assertThat(filteredRecords) .anyMatch(node -> giftOrder.getId().equals(node.path("id").asText())); } finally { CustomSecurityContextHolder.remove(); } } @Test void queryByPageReturnsOnlyOrdersBelongingToCurrentCustomer() throws Exception { SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID); try { String token = ensureCustomerToken(); LocalDateTime base = LocalDateTime.now().minusMinutes(30); PlayOrderInfoEntity own = persistOrder(base, order -> order.setOrderNo("OWN-" + IdUtils.getUuid().substring(0, 6))); PlayOrderInfoEntity foreign = persistOrder(base.plusMinutes(5), order -> { order.setPurchaserBy("other-customer"); order.setOrderNo("FOREIGN-" + IdUtils.getUuid().substring(0, 6)); }); ObjectNode payload = basePayload(1, 20); JsonNode records = queryOrders(token, payload); List ids = new ArrayList<>(); records.forEach(node -> ids.add(node.path("id").asText())); assertThat(ids).contains(own.getId()); assertThat(ids).doesNotContain(foreign.getId()); } finally { CustomSecurityContextHolder.remove(); } } @Test void queryByPageSupportsPagingMeta() throws Exception { SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID); try { String token = ensureCustomerToken(); LocalDateTime now = LocalDateTime.now().minusMinutes(60); PlayOrderInfoEntity first = persistOrder(now, order -> order.setOrderNo("PAGE-A")); PlayOrderInfoEntity second = persistOrder(now.plusMinutes(2), order -> order.setOrderNo("PAGE-B")); PlayOrderInfoEntity third = persistOrder(now.plusMinutes(4), order -> order.setOrderNo("PAGE-C")); ObjectNode pageOne = basePayload(1, 2); JsonNode rootPageOne = executeOrderQuery(token, pageOne); JsonNode dataOne = rootPageOne.path("data"); JsonNode recordsOne = dataOne.path("records"); assertThat(recordsOne.size()).isEqualTo(2); assertThat(dataOne.path("total").asInt()).isEqualTo(3); ObjectNode pageTwo = basePayload(2, 2); JsonNode rootPageTwo = executeOrderQuery(token, pageTwo); JsonNode recordsTwo = rootPageTwo.path("data").path("records"); assertThat(recordsTwo.size()).isEqualTo(1); assertThat(recordsTwo.get(0).path("id").asText()).isIn(first.getId(), second.getId(), third.getId()); } finally { CustomSecurityContextHolder.remove(); } } @Test void queryByPageFiltersByOrderStatus() throws Exception { SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID); try { String token = ensureCustomerToken(); PlayOrderInfoEntity pending = persistOrder(LocalDateTime.now().minusMinutes(50), order -> order.setOrderStatus(OrderConstant.OrderStatus.PENDING.getCode())); PlayOrderInfoEntity completed = persistOrder(LocalDateTime.now().minusMinutes(40), order -> order.setOrderStatus(OrderConstant.OrderStatus.COMPLETED.getCode())); PlayOrderInfoEntity revoked = persistOrder(LocalDateTime.now().minusMinutes(30), order -> order.setOrderStatus(OrderConstant.OrderStatus.REVOKED.getCode())); ObjectNode payload = basePayload(1, 10); payload.put("orderStatus", OrderConstant.OrderStatus.REVOKED.getCode()); JsonNode records = queryOrders(token, payload); assertThat(records.size()).isEqualTo(1); assertThat(records.get(0).path("id").asText()).isEqualTo(revoked.getId()); } finally { CustomSecurityContextHolder.remove(); } } @Test void queryByPageFiltersByPlaceTypeAndCompositeCriteria() throws Exception { SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID); try { String token = ensureCustomerToken(); LocalDateTime base = LocalDateTime.now().minusMinutes(90); PlayOrderInfoEntity target = persistOrder(base, order -> { order.setOrderStatus(OrderConstant.OrderStatus.IN_PROGRESS.getCode()); order.setPlaceType(OrderConstant.PlaceType.SPECIFIED.getCode()); order.setOrderNo("FOCUS-" + IdUtils.getUuid().substring(0, 4)); order.setUseCoupon("1"); order.setBackendEntry("1"); order.setFirstOrder("1"); order.setGroupId("group-focus"); order.setSex("1"); order.setPurchaserTime(base.plusMinutes(5)); order.setAcceptTime(base.plusMinutes(10)); order.setOrderEndTime(base.plusMinutes(50)); }); persistOrder(base.plusMinutes(5), order -> { order.setOrderStatus(OrderConstant.OrderStatus.IN_PROGRESS.getCode()); order.setPlaceType(OrderConstant.PlaceType.RANDOM.getCode()); order.setUseCoupon("0"); order.setBackendEntry("0"); order.setFirstOrder("0"); order.setGroupId("group-noise"); order.setSex("2"); }); ObjectNode payload = basePayload(1, 10); payload.put("placeType", OrderConstant.PlaceType.SPECIFIED.getCode()); payload.put("orderNo", target.getOrderNo().substring(0, 6)); payload.put("useCoupon", "1"); payload.put("backendEntry", "1"); payload.put("firstOrder", "1"); payload.put("groupId", "group-focus"); payload.put("sex", "1"); payload.set("purchaserTime", range(target.getPurchaserTime().minusMinutes(1), target.getPurchaserTime().plusMinutes(1))); payload.set("acceptTime", range(target.getAcceptTime().minusMinutes(1), target.getAcceptTime().plusMinutes(1))); payload.set("endOrderTime", range(target.getOrderEndTime().minusMinutes(1), target.getOrderEndTime().plusMinutes(1))); JsonNode records = queryOrders(token, payload); assertThat(records.size()).isEqualTo(1); assertThat(records.get(0).path("id").asText()).isEqualTo(target.getId()); } finally { CustomSecurityContextHolder.remove(); } } @Test void queryByPageMarksEvaluateAndComplaintFlags() throws Exception { SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID); try { String token = ensureCustomerToken(); PlayOrderInfoEntity evaluated = persistOrder(LocalDateTime.now().minusMinutes(10), order -> order.setOrderNo("EVAL-" + IdUtils.getUuid().substring(0, 4))); PlayOrderInfoEntity complained = persistOrder(LocalDateTime.now().minusMinutes(8), order -> order.setOrderNo("COMP-" + IdUtils.getUuid().substring(0, 4))); markEvaluated(evaluated.getId()); markComplained(complained.getId()); ObjectNode payload = basePayload(1, 20); JsonNode records = queryOrders(token, payload); String evalFlag = findById(records, evaluated.getId()).path("evaluate").asText(); String complaintFlag = findById(records, complained.getId()).path("complaint").asText(); assertThat(evalFlag).isEqualTo("1"); assertThat(complaintFlag).isEqualTo("1"); } finally { CustomSecurityContextHolder.remove(); } } private void placeRandomOrder(String remark, String customerToken) throws Exception { String payload = "{" + "\"sex\":\"2\"," + "\"levelId\":\"" + ApiTestDataSeeder.DEFAULT_CLERK_LEVEL_ID + "\"," + "\"commodityId\":\"" + ApiTestDataSeeder.DEFAULT_COMMODITY_ID + "\"," + "\"commodityQuantity\":1," + "\"weiChatCode\":\"apitest-customer-wx\"," + "\"excludeHistory\":\"0\"," + "\"couponIds\":[]," + "\"remark\":\"" + remark + "\"" + "}"; mockMvc.perform(post("/wx/custom/order/random") .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("δΈ‹ε•ζˆεŠŸ")); } private PlayOrderInfoEntity persistOrder(LocalDateTime baseTime, java.util.function.Consumer customizer) { PlayOrderInfoEntity order = new PlayOrderInfoEntity(); order.setId("order-" + IdUtils.getUuid()); order.setOrderNo("WXQ-" + IdUtils.getUuid().substring(0, 8)); order.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID); order.setOrderStatus(OrderConstant.OrderStatus.PENDING.getCode()); order.setOrderType(OrderConstant.OrderType.NORMAL.getCode()); order.setPlaceType(OrderConstant.PlaceType.SPECIFIED.getCode()); order.setRewardType("0"); order.setFirstOrder("0"); order.setRefundType("0"); order.setRefundAmount(BigDecimal.ZERO); order.setOrderMoney(new BigDecimal("99.00")); order.setFinalAmount(new BigDecimal("99.00")); order.setDiscountAmount(BigDecimal.ZERO); order.setEstimatedRevenue(new BigDecimal("40.00")); order.setEstimatedRevenueRatio(40); order.setUseCoupon("0"); order.setBackendEntry("0"); order.setCouponIds(java.util.Collections.emptyList()); order.setPaymentSource("balance"); order.setPayMethod("0"); order.setPayState("1"); order.setPurchaserBy(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID); order.setPurchaserTime(baseTime); order.setAcceptBy(ApiTestDataSeeder.DEFAULT_CLERK_ID); order.setAcceptTime(baseTime.plusMinutes(5)); order.setGroupId(ApiTestDataSeeder.DEFAULT_GROUP_ID); order.setOrderStartTime(baseTime.plusMinutes(10)); order.setOrderEndTime(baseTime.plusMinutes(40)); order.setOrdersExpiredState("0"); order.setOrderSettlementState("0"); order.setSex("2"); order.setCommodityId(ApiTestDataSeeder.DEFAULT_COMMODITY_ID); order.setCommodityType("1"); order.setCommodityPrice(new BigDecimal("99.00")); order.setCommodityName("Weixin Order"); order.setServiceDuration("60min"); order.setCommodityNumber("1"); order.setRemark("auto"); order.setBackendRemark("auto"); order.setCreatedTime(baseTime); order.setUpdatedTime(baseTime); order.setCreatedBy("wx-test"); order.setUpdatedBy("wx-test"); customizer.accept(order); ensureTenantContext(); playOrderInfoService.save(order); orderIdsToCleanup.add(order.getId()); return order; } private ObjectNode basePayload(int pageNum, int pageSize) { ObjectNode node = objectMapper.createObjectNode(); node.put("pageNum", pageNum); node.put("pageSize", pageSize); return node; } private ArrayNode range(LocalDateTime start, LocalDateTime end) { ArrayNode node = objectMapper.createArrayNode(); node.add(DATE_TIME_FORMATTER.format(start)); node.add(DATE_TIME_FORMATTER.format(end)); return node; } private String ensureCustomerToken() { resetCustomerBalance(); String customerToken = wxTokenService.createWxUserToken(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID); customUserInfoService.updateTokenById(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID, customerToken); return customerToken; } private void markEvaluated(String orderId) { PlayOrderEvaluateInfoEntity entity = new PlayOrderEvaluateInfoEntity(); entity.setId("eval-" + IdUtils.getUuid()); entity.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID); entity.setOrderId(orderId); entity.setCustomId(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID); entity.setClerkId(ApiTestDataSeeder.DEFAULT_CLERK_ID); entity.setCommodityId(ApiTestDataSeeder.DEFAULT_COMMODITY_ID); entity.setAnonymous("0"); entity.setEvaluateType("0"); entity.setEvaluateLevel(5); entity.setEvaluateCon("Great job"); entity.setEvaluateTime(java.sql.Timestamp.valueOf(LocalDateTime.now())); entity.setHidden("0"); ensureTenantContext(); playOrderEvaluateInfoService.save(entity); evalIdsToCleanup.add(entity.getId()); } private void markComplained(String orderId) { PlayOrderComplaintInfoEntity entity = new PlayOrderComplaintInfoEntity(); entity.setId("complaint-" + IdUtils.getUuid()); entity.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID); entity.setOrderId(orderId); entity.setCustomId(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID); entity.setClerkId(ApiTestDataSeeder.DEFAULT_CLERK_ID); entity.setCommodityId(ApiTestDataSeeder.DEFAULT_COMMODITY_ID); entity.setComplaintCon("Need assistance"); entity.setComplaintTime(java.sql.Timestamp.valueOf(LocalDateTime.now())); entity.setHidden("0"); ensureTenantContext(); playOrderComplaintInfoService.save(entity); complaintIdsToCleanup.add(entity.getId()); } private JsonNode findById(JsonNode records, String id) { for (JsonNode node : records) { if (id.equals(node.path("id").asText())) { return node; } } throw new AssertionError("Record with id " + id + " not found in response"); } private JsonNode queryOrders(String customerToken, ObjectNode payload) throws Exception { JsonNode root = executeOrderQuery(customerToken, payload); JsonNode dataNode = root.path("data"); return dataNode.isArray() ? dataNode : dataNode.path("records"); } private JsonNode executeOrderQuery(String customerToken, ObjectNode payload) throws Exception { MvcResult result = mockMvc.perform(post("/wx/custom/order/queryByPage") .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.toString())) .andExpect(status().isOk()) .andExpect(jsonPath("$.code").value(200)) .andReturn(); return objectMapper.readTree(result.getResponse().getContentAsString()); } }