Files
peipei-backend/play-admin/src/test/java/com/starry/admin/api/PlayClerkUserInfoApiTest.java
2025-11-14 10:31:16 -05:00

382 lines
18 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.starry.admin.modules.clerk.module.entity.PlayClerkLevelInfoEntity;
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;
import com.starry.admin.modules.clerk.service.IPlayClerkLevelInfoService;
import com.starry.admin.modules.clerk.service.IPlayClerkUserInfoService;
import com.starry.admin.utils.SecurityUtils;
import com.starry.common.utils.IdUtils;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
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 PlayClerkUserInfoApiTest extends AbstractApiTest {
private final ObjectMapper objectMapper = new ObjectMapper();
@Autowired
private IPlayClerkLevelInfoService clerkLevelInfoService;
@Autowired
private IPlayClerkUserInfoService clerkUserInfoService;
private final List<String> levelIdsToCleanup = new ArrayList<>();
private final List<String> clerkIdsToCleanup = new ArrayList<>();
@AfterEach
void tearDown() {
ensureTenantContext();
if (!clerkIdsToCleanup.isEmpty()) {
clerkUserInfoService.removeByIds(clerkIdsToCleanup);
clerkIdsToCleanup.clear();
}
if (!levelIdsToCleanup.isEmpty()) {
clerkLevelInfoService.removeByIds(levelIdsToCleanup);
levelIdsToCleanup.clear();
}
}
@Test
// 场景:不同等级的排序号不同,接口应按照排序号升序返回,验证等级排序字段生效。
void listOrdersByLevelOrderNumberAscending() throws Exception {
ensureTenantContext();
PlayClerkLevelInfoEntity lowOrderLevel = createClerkLevel("low", 1L, 50);
PlayClerkLevelInfoEntity highOrderLevel = createClerkLevel("high", 5L, 60);
String filterToken = "order-sort-" + IdUtils.getUuid().substring(0, 8);
String lowOrderClerkId = createClerk(filterToken + "-low", lowOrderLevel.getId(), "0");
String highOrderClerkId = createClerk(filterToken + "-high", highOrderLevel.getId(), "1");
MvcResult result = mockMvc.perform(get("/clerk/user/list")
.param("pageNum", "1")
.param("pageSize", "20")
.param("nickname", filterToken)
.header(TENANT_HEADER, DEFAULT_TENANT)
.header(USER_HEADER, DEFAULT_USER))
.andExpect(status().isOk())
.andReturn();
String responseBody = result.getResponse().getContentAsString();
JsonNode root = objectMapper.readTree(responseBody);
assertThat(root.get("code").asInt()).isEqualTo(200);
JsonNode records = root.path("data");
assertThat(records.isArray()).as("Response payload: %s", responseBody).isTrue();
List<String> orderedIds = new ArrayList<>();
for (JsonNode record : records) {
orderedIds.add(record.path("id").asText());
}
assertThat(orderedIds).contains(lowOrderClerkId, highOrderClerkId);
assertThat(orderedIds.indexOf(lowOrderClerkId))
.withFailMessage("Unexpected order for token %s: %s", filterToken, orderedIds)
.isLessThan(orderedIds.indexOf(highOrderClerkId));
}
@Test
// 场景:相同等级排序号相同,接口应按在线状态优先展示在线店员,验证排序二级规则。
void listOrdersByOnlineStateWhenOrderNumberMatches() throws Exception {
ensureTenantContext();
PlayClerkLevelInfoEntity level = createClerkLevel("tie", 3L, 70);
String filterToken = "online-priority-" + IdUtils.getUuid().substring(0, 8);
String onlineClerkId = createClerk(filterToken + "-online", level.getId(), "1");
String offlineClerkId = createClerk(filterToken + "-offline", level.getId(), "0");
MvcResult result = mockMvc.perform(get("/clerk/user/list")
.param("pageNum", "1")
.param("pageSize", "20")
.param("nickname", filterToken)
.header(TENANT_HEADER, DEFAULT_TENANT)
.header(USER_HEADER, DEFAULT_USER))
.andExpect(status().isOk())
.andReturn();
String responseBody = result.getResponse().getContentAsString();
JsonNode root = objectMapper.readTree(responseBody);
assertThat(root.get("code").asInt()).isEqualTo(200);
JsonNode records = root.path("data");
assertThat(records.isArray()).as("Response payload: %s", responseBody).isTrue();
List<String> orderedIds = new ArrayList<>();
for (JsonNode record : records) {
orderedIds.add(record.path("id").asText());
}
assertThat(orderedIds).contains(onlineClerkId, offlineClerkId);
assertThat(orderedIds.indexOf(onlineClerkId))
.withFailMessage("Unexpected order for token %s: %s | response: %s",
filterToken, orderedIds, responseBody)
.isLessThan(orderedIds.indexOf(offlineClerkId));
}
@Test
// 场景:两个等级都未配置排序号时,接口仍可返回且在线客服优先排序。
void listOrdersByOnlineStateWhenOrderNumberMissing() throws Exception {
ensureTenantContext();
PlayClerkLevelInfoEntity onlineLevel = createClerkLevel("null-online", null, 20);
PlayClerkLevelInfoEntity offlineLevel = createClerkLevel("null-offline", null, 30);
String filterToken = "null-priority-" + IdUtils.getUuid().substring(0, 8);
String onlineClerkId = createClerk(filterToken + "-online", onlineLevel.getId(), "1");
String offlineClerkId = createClerk(filterToken + "-offline", offlineLevel.getId(), "0");
MvcResult result = mockMvc.perform(get("/clerk/user/list")
.param("pageNum", "1")
.param("pageSize", "20")
.param("nickname", filterToken)
.header(TENANT_HEADER, DEFAULT_TENANT)
.header(USER_HEADER, DEFAULT_USER))
.andExpect(status().isOk())
.andReturn();
String responseBody = result.getResponse().getContentAsString();
JsonNode root = objectMapper.readTree(responseBody);
assertThat(root.get("code").asInt()).isEqualTo(200);
JsonNode records = root.path("data");
assertThat(records.isArray()).as("Response payload: %s", responseBody).isTrue();
List<String> orderedIds = new ArrayList<>();
for (JsonNode record : records) {
orderedIds.add(record.path("id").asText());
}
assertThat(orderedIds).contains(onlineClerkId, offlineClerkId);
assertThat(orderedIds.indexOf(onlineClerkId))
.withFailMessage("Online clerk should remain prioritized even without orderNumber. token=%s order=%s response=%s",
filterToken, orderedIds, responseBody)
.isLessThan(orderedIds.indexOf(offlineClerkId));
}
@Test
// 场景:存在未填写排序号的等级时,接口仍能返回,并默认将该等级排在有序的等级之后。
void listHandlesNullOrderNumberGracefully() throws Exception {
ensureTenantContext();
PlayClerkLevelInfoEntity orderedLevel = createClerkLevel("ordered", 2L, 40);
PlayClerkLevelInfoEntity nullLevel = createClerkLevel("null", null, 50);
String filterToken = "null-order-" + IdUtils.getUuid().substring(0, 8);
String orderedClerkId = createClerk(filterToken + "-ordered", orderedLevel.getId(), "1");
String nullOrderClerkId = createClerk(filterToken + "-null", nullLevel.getId(), "1");
MvcResult result = mockMvc.perform(get("/clerk/user/list")
.param("pageNum", "1")
.param("pageSize", "20")
.param("nickname", filterToken)
.header(TENANT_HEADER, DEFAULT_TENANT)
.header(USER_HEADER, DEFAULT_USER))
.andExpect(status().isOk())
.andReturn();
String responseBody = result.getResponse().getContentAsString();
JsonNode root = objectMapper.readTree(responseBody);
assertThat(root.get("code").asInt()).isEqualTo(200);
JsonNode records = root.path("data");
assertThat(records.isArray()).as("Response payload: %s", responseBody).isTrue();
List<String> orderedIds = new ArrayList<>();
for (JsonNode record : records) {
orderedIds.add(record.path("id").asText());
}
assertThat(orderedIds).contains(orderedClerkId, nullOrderClerkId);
assertThat(orderedIds.indexOf(orderedClerkId))
.withFailMessage("Null orderNumber should fall back after populated ones. token=%s, order=%s, response=%s",
filterToken, orderedIds, responseBody)
.isLessThan(orderedIds.indexOf(nullOrderClerkId));
}
@Test
// 场景:更新店员等级时传入排序号,接口应成功持久化该值,验证新增字段的写入能力。
void updateLevelPersistsOrderNumber() throws Exception {
ensureTenantContext();
PlayClerkLevelInfoEntity level = createClerkLevel("update", 8L, 80);
long updatedOrderNumber = 42L;
ObjectNode payload = objectMapper.createObjectNode();
payload.put("id", level.getId());
payload.put("name", level.getName());
payload.put("firstRegularRatio", level.getFirstRegularRatio());
payload.put("notFirstRegularRatio", level.getNotFirstRegularRatio());
payload.put("firstRewardRatio", level.getFirstRewardRatio());
payload.put("notFirstRewardRatio", level.getNotFirstRewardRatio());
payload.put("firstRandomRadio", level.getFirstRandomRadio());
payload.put("notFirstRandomRadio", level.getNotFirstRandomRadio());
payload.put("styleType", level.getStyleType());
payload.put("orderNumber", updatedOrderNumber);
mockMvc.perform(post("/clerk/level/update")
.header(TENANT_HEADER, DEFAULT_TENANT)
.header(USER_HEADER, DEFAULT_USER)
.contentType(MediaType.APPLICATION_JSON)
.content(payload.toString()))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(200));
ensureTenantContext();
PlayClerkLevelInfoEntity reloaded = clerkLevelInfoService.getById(level.getId());
assertThat(reloaded.getOrderNumber()).isEqualTo(updatedOrderNumber);
}
@Test
// 场景更新时清空排序号接口应允许写入null并持久化。
void updateLevelAllowsClearingOrderNumber() throws Exception {
ensureTenantContext();
PlayClerkLevelInfoEntity level = createClerkLevel("clear", 5L, 85);
ObjectNode payload = objectMapper.createObjectNode();
payload.put("id", level.getId());
payload.put("name", level.getName());
payload.put("firstRegularRatio", level.getFirstRegularRatio());
payload.put("notFirstRegularRatio", level.getNotFirstRegularRatio());
payload.put("firstRewardRatio", level.getFirstRewardRatio());
payload.put("notFirstRewardRatio", level.getNotFirstRewardRatio());
payload.put("firstRandomRadio", level.getFirstRandomRadio());
payload.put("notFirstRandomRadio", level.getNotFirstRandomRadio());
payload.put("styleType", level.getStyleType());
payload.putNull("orderNumber");
mockMvc.perform(post("/clerk/level/update")
.header(TENANT_HEADER, DEFAULT_TENANT)
.header(USER_HEADER, DEFAULT_USER)
.contentType(MediaType.APPLICATION_JSON)
.content(payload.toString()))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(200));
ensureTenantContext();
PlayClerkLevelInfoEntity reloaded = clerkLevelInfoService.getById(level.getId());
assertThat(reloaded.getOrderNumber()).isNull();
}
private void ensureTenantContext() {
SecurityUtils.setTenantId(DEFAULT_TENANT);
}
private PlayClerkLevelInfoEntity createClerkLevel(String suffix, Long orderNumber, int levelValue) {
ensureTenantContext();
String levelId = IdUtils.getUuid();
PlayClerkLevelInfoEntity level = new PlayClerkLevelInfoEntity();
level.setId(levelId);
level.setTenantId(DEFAULT_TENANT);
level.setName("API测试等级-" + suffix);
level.setLevel(levelValue);
level.setFirstRegularRatio(60);
level.setNotFirstRegularRatio(50);
level.setFirstRewardRatio(45);
level.setNotFirstRewardRatio(35);
level.setFirstRandomRadio(55);
level.setNotFirstRandomRadio(40);
level.setStyleType(levelValue);
level.setOrderNumber(orderNumber);
clerkLevelInfoService.save(level);
levelIdsToCleanup.add(levelId);
return level;
}
private String createClerk(String suffix, String levelId, String onlineState) {
ensureTenantContext();
String clerkId = IdUtils.getUuid();
PlayClerkUserInfoEntity clerk = new PlayClerkUserInfoEntity();
clerk.setId(clerkId);
clerk.setTenantId(DEFAULT_TENANT);
clerk.setNickname("API测试店员-" + suffix);
clerk.setLevelId(levelId);
clerk.setClerkState("1");
clerk.setOnboardingState("1");
clerk.setListingState("1");
clerk.setDisplayState("1");
clerk.setRecommendationState("0");
clerk.setPinToTopState("0");
clerk.setRandomOrderState("1");
clerk.setFixingLevel("0");
clerk.setOnlineState(onlineState);
clerk.setPhone("138" + String.format("%08d", ThreadLocalRandom.current().nextInt(0, 100_000_000)));
clerk.setOpenid("openid-" + suffix + "-" + IdUtils.getUuid());
clerk.setWeiChatCode("wx-code-" + suffix);
clerk.setWeiChatAvatar("https://example.com/avatar/" + suffix);
clerk.setTypeId("api-type");
clerk.setProvince("API省");
clerk.setCity("API市");
clerk.setEntryTime(LocalDateTime.now());
clerk.setAddTime(LocalDateTime.now());
clerkUserInfoService.save(clerk);
clerkIdsToCleanup.add(clerkId);
return clerkId;
}
@Test
void listOrderingStableWithMultipleCriteria() throws Exception {
ensureTenantContext();
PlayClerkLevelInfoEntity level = createClerkLevel("stable", 10L, 80);
String filterToken = "stable-" + IdUtils.getUuid().substring(0, 6);
String pinnedOnline = createClerk(filterToken + "-pinned-online", level.getId(), "1");
togglePin(pinnedOnline, "1");
String pinnedOffline = createClerk(filterToken + "-pinned-offline", level.getId(), "0");
togglePin(pinnedOffline, "1");
String online1 = createClerk(filterToken + "-online-one", level.getId(), "1");
pause(50);
String online2 = createClerk(filterToken + "-online-two", level.getId(), "1");
String offline = createClerk(filterToken + "-offline", level.getId(), "0");
MvcResult result = mockMvc.perform(get("/clerk/user/list")
.param("pageNum", "1")
.param("pageSize", "20")
.param("nickname", filterToken)
.header(TENANT_HEADER, DEFAULT_TENANT)
.header(USER_HEADER, DEFAULT_USER))
.andExpect(status().isOk())
.andReturn();
JsonNode root = objectMapper.readTree(result.getResponse().getContentAsString());
JsonNode records = root.path("data");
assertThat(records.isArray()).isTrue();
List<String> orderedIds = new ArrayList<>();
for (JsonNode record : records) {
orderedIds.add(record.path("id").asText());
}
assertThat(orderedIds.indexOf(pinnedOnline))
.isLessThan(orderedIds.indexOf(online1));
assertThat(orderedIds.indexOf(online1))
.withFailMessage("Online entries should stay ahead, list=%s", orderedIds)
.isLessThan(orderedIds.indexOf(online2));
assertThat(orderedIds.indexOf(online2))
.isLessThan(orderedIds.indexOf(pinnedOffline));
assertThat(orderedIds.indexOf(pinnedOffline))
.isLessThan(orderedIds.indexOf(offline));
}
private void togglePin(String clerkId, String pinState) {
ensureTenantContext();
PlayClerkUserInfoEntity update = new PlayClerkUserInfoEntity();
update.setId(clerkId);
update.setPinToTopState(pinState);
clerkUserInfoService.update(update);
}
private void pause(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
}