- 添加数据库迁移脚本,为 play_clerk_level_info 表新增 order_number 字段 - 更新测试数据种子,设置默认等级的排序号 - 新增店员用户API测试,验证按等级排序号和在线状态的排序逻辑
This commit is contained in:
@@ -259,6 +259,7 @@ public class ApiTestDataSeeder implements CommandLineRunner {
|
|||||||
entity.setNotFirstRandomRadio(45);
|
entity.setNotFirstRandomRadio(45);
|
||||||
entity.setFirstRewardRatio(40);
|
entity.setFirstRewardRatio(40);
|
||||||
entity.setNotFirstRewardRatio(35);
|
entity.setNotFirstRewardRatio(35);
|
||||||
|
entity.setOrderNumber(1L);
|
||||||
clerkLevelInfoService.save(entity);
|
clerkLevelInfoService.save(entity);
|
||||||
log.info("Inserted API test clerk level {}", DEFAULT_CLERK_LEVEL_ID);
|
log.info("Inserted API test clerk level {}", DEFAULT_CLERK_LEVEL_ID);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
SET @column_exists := (
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM information_schema.COLUMNS
|
||||||
|
WHERE TABLE_SCHEMA = DATABASE()
|
||||||
|
AND TABLE_NAME = 'play_clerk_level_info'
|
||||||
|
AND COLUMN_NAME = 'order_number'
|
||||||
|
);
|
||||||
|
|
||||||
|
SET @ddl := IF(
|
||||||
|
@column_exists = 0,
|
||||||
|
'ALTER TABLE `play_clerk_level_info` ADD COLUMN `order_number` bigint NULL COMMENT ''等级排序号''',
|
||||||
|
'SELECT 1'
|
||||||
|
);
|
||||||
|
|
||||||
|
PREPARE stmt FROM @ddl;
|
||||||
|
EXECUTE stmt;
|
||||||
|
DEALLOCATE PREPARE stmt;
|
||||||
|
|
||||||
|
UPDATE `play_clerk_level_info`
|
||||||
|
SET `order_number` = COALESCE(`order_number`, `level`);
|
||||||
@@ -0,0 +1,322 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user