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,519 @@
package com.starry.admin.api;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.hasItem;
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.ObjectNode;
import com.starry.admin.common.apitest.ApiTestDataSeeder;
import com.starry.admin.modules.clerk.module.entity.PlayClerkCommodityEntity;
import com.starry.admin.modules.clerk.module.entity.PlayClerkLevelInfoEntity;
import com.starry.admin.modules.clerk.service.IPlayClerkCommodityService;
import com.starry.admin.modules.clerk.service.IPlayClerkLevelInfoService;
import com.starry.admin.modules.order.module.constant.OrderConstant;
import com.starry.admin.modules.shop.module.entity.PlayCommodityAndLevelInfoEntity;
import com.starry.admin.modules.shop.module.entity.PlayCommodityInfoEntity;
import com.starry.admin.modules.shop.service.IPlayCommodityAndLevelInfoService;
import com.starry.admin.modules.shop.service.IPlayCommodityInfoService;
import com.starry.admin.utils.SecurityUtils;
import com.starry.common.constant.Constants;
import com.starry.common.utils.IdUtils;
import java.math.BigDecimal;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
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 PlayCommodityInfoApiTest extends WxCustomOrderApiTestSupport {
private static final String ROOT_PARENT_ID = "00";
private static final long DEFAULT_PRICE_SORT = 1L;
private static final int SINGLE_COMMODITY_QUANTITY = 1;
private enum SwitchState {
ENABLED("1"),
DISABLED("0");
private final String code;
SwitchState(String code) {
this.code = code;
}
String getCode() {
return code;
}
}
private enum HistoryFilter {
INCLUDE("0"),
EXCLUDE("1");
private final String code;
HistoryFilter(String code) {
this.code = code;
}
String getCode() {
return code;
}
}
private enum SortPriority {
PRIMARY(1),
SECONDARY(2);
private final int value;
SortPriority(int value) {
this.value = value;
}
int getValue() {
return value;
}
}
private enum AutomaticSettlementPolicy {
DISABLED(-1),
TEN_MINUTES(600);
private final int seconds;
AutomaticSettlementPolicy(int seconds) {
this.seconds = seconds;
}
int getSeconds() {
return seconds;
}
}
@Autowired
private IPlayCommodityInfoService commodityInfoService;
@Autowired
private IPlayCommodityAndLevelInfoService commodityAndLevelInfoService;
@Autowired
private IPlayClerkCommodityService clerkCommodityService;
@Autowired
private IPlayClerkLevelInfoService clerkLevelInfoService;
private final Deque<String> commodityIdsToCleanup = new ArrayDeque<>();
private final List<String> priceIdsToCleanup = new ArrayList<>();
private final List<String> clerkCommodityIdsToCleanup = new ArrayList<>();
@AfterEach
void tearDown() {
ensureTenantContext();
if (!priceIdsToCleanup.isEmpty()) {
commodityAndLevelInfoService.removeByIds(priceIdsToCleanup);
priceIdsToCleanup.clear();
}
if (!clerkCommodityIdsToCleanup.isEmpty()) {
clerkCommodityService.removeByIds(clerkCommodityIdsToCleanup);
clerkCommodityIdsToCleanup.clear();
}
while (!commodityIdsToCleanup.isEmpty()) {
String commodityId = commodityIdsToCleanup.removeFirst();
commodityInfoService.removeById(commodityId);
}
}
@Test
// 测试用例:调用商品表头接口,验证默认店员等级会出现在表头列表中,确保管理端可正确展示等级价格列。
void tableNameEndpointReturnsClerkLevels() throws Exception {
ensureTenantContext();
PlayClerkLevelInfoEntity level =
clerkLevelInfoService.getById(ApiTestDataSeeder.DEFAULT_CLERK_LEVEL_ID);
mockMvc.perform(get("/shop/commodity/getTableName")
.header(USER_HEADER, DEFAULT_USER)
.header(TENANT_HEADER, DEFAULT_TENANT))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(200))
.andExpect(jsonPath("$.data[*].prop", hasItem(ApiTestDataSeeder.DEFAULT_CLERK_LEVEL_ID)))
.andExpect(jsonPath("$.data[*].name", hasItem(level.getName())));
}
@Test
// 测试用例:查询所有商品树,验证种子数据的父子节点与对应等级价格一起返回,确保层级结构与定价展示正确。
void listAllIncludesSeedCommodityWithLevelPrice() throws Exception {
ensureTenantContext();
MvcResult result = mockMvc.perform(get("/shop/commodity/listAll")
.header(USER_HEADER, DEFAULT_USER)
.header(TENANT_HEADER, DEFAULT_TENANT))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(200))
.andReturn();
JsonNode root = objectMapper.readTree(result.getResponse().getContentAsString());
JsonNode data = root.get("data");
assertThat(data.isArray()).isTrue();
JsonNode parentNode = findNodeById(data, ApiTestDataSeeder.DEFAULT_COMMODITY_PARENT_ID);
assertThat(parentNode).isNotNull();
JsonNode children = parentNode.get("children");
JsonNode childNode = findNodeById(children, ApiTestDataSeeder.DEFAULT_COMMODITY_ID);
assertThat(childNode).isNotNull();
BigDecimal price = new BigDecimal(childNode.get(ApiTestDataSeeder.DEFAULT_CLERK_LEVEL_ID).asText());
assertThat(price).isEqualByComparingTo(ApiTestDataSeeder.DEFAULT_COMMODITY_PRICE);
}
@Test
// 测试用例:调用价格更新接口,为新创建的子商品配置等级价格,验证价格落库并可复查,确保后台配置生效。
void updateInfoAssignsLevelPricing() throws Exception {
ensureTenantContext();
String parentId = generateId("svc-parent-");
String childId = generateId("svc-child-");
PlayCommodityInfoEntity parent = createCommodity(
parentId,
ROOT_PARENT_ID,
"API测试父类目",
"N/A",
SwitchState.ENABLED,
SortPriority.PRIMARY,
AutomaticSettlementPolicy.DISABLED);
PlayCommodityInfoEntity child = createCommodity(
childId,
parent.getId(),
"API测试子项目",
"45min",
SwitchState.ENABLED,
SortPriority.PRIMARY,
AutomaticSettlementPolicy.DISABLED);
ObjectNode payload = objectMapper.createObjectNode();
payload.put("id", child.getId());
payload.put(ApiTestDataSeeder.DEFAULT_CLERK_LEVEL_ID, "188.50");
mockMvc.perform(post("/shop/commodity/updateInfo")
.header(USER_HEADER, DEFAULT_USER)
.header(TENANT_HEADER, DEFAULT_TENANT)
.contentType(MediaType.APPLICATION_JSON)
.content(payload.toString()))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(200));
PlayCommodityAndLevelInfoEntity pricing = commodityAndLevelInfoService.lambdaQuery()
.eq(PlayCommodityAndLevelInfoEntity::getCommodityId, child.getId())
.eq(PlayCommodityAndLevelInfoEntity::getLevelId, ApiTestDataSeeder.DEFAULT_CLERK_LEVEL_ID)
.one();
assertThat(pricing).isNotNull();
priceIdsToCleanup.add(pricing.getId());
assertThat(pricing.getPrice()).isEqualByComparingTo(new BigDecimal("188.50"));
}
@Test
// 测试用例:使用商品修改接口把自动结算等待时长从不限时(-1调整为10分钟验证更新后查询返回新的配置。
void updateEndpointSwitchesAutomaticSettlement() throws Exception {
ensureTenantContext();
String parentId = generateId("svc-parent-");
String childId = generateId("svc-child-");
PlayCommodityInfoEntity parent = createCommodity(
parentId,
ROOT_PARENT_ID,
"自动结算父类目",
"N/A",
SwitchState.ENABLED,
SortPriority.PRIMARY,
AutomaticSettlementPolicy.DISABLED);
PlayCommodityInfoEntity child = createCommodity(
childId,
parent.getId(),
"自动结算子项",
"30min",
SwitchState.ENABLED,
SortPriority.PRIMARY,
AutomaticSettlementPolicy.DISABLED);
PlayCommodityInfoEntity beforeUpdate = commodityInfoService.getById(child.getId());
assertThat(beforeUpdate.getAutomaticSettlementDuration())
.isEqualTo(AutomaticSettlementPolicy.DISABLED.getSeconds());
PlayCommodityInfoEntity updateBody = new PlayCommodityInfoEntity();
updateBody.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
updateBody.setPId(parent.getId());
updateBody.setItemType(child.getItemType());
updateBody.setItemName(child.getItemName());
updateBody.setServiceDuration("30min");
updateBody.setAutomaticSettlementDuration(AutomaticSettlementPolicy.TEN_MINUTES.getSeconds());
updateBody.setEnableStace(SwitchState.ENABLED.getCode());
updateBody.setSort(SortPriority.PRIMARY.getValue());
mockMvc.perform(post("/shop/commodity/update/" + child.getId())
.header(USER_HEADER, DEFAULT_USER)
.header(TENANT_HEADER, DEFAULT_TENANT)
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(updateBody)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(200));
PlayCommodityInfoEntity updated = commodityInfoService.getById(child.getId());
assertThat(updated.getAutomaticSettlementDuration())
.isEqualTo(AutomaticSettlementPolicy.TEN_MINUTES.getSeconds());
}
@Test
// 测试用例:为店员构造包含定价与未定价子项的类目,调用按等级查询接口,验证未设置价格的子项被过滤,只返回可售商品。
void queryClerkAllCommodityByLevelSkipsUnpricedChildren() throws Exception {
ensureTenantContext();
String parentId = generateId("svc-parent-");
String pricedChildId = generateId("svc-child-priced-");
String unpricedChildId = generateId("svc-child-unpriced-");
PlayCommodityInfoEntity parent = createCommodity(
parentId,
ROOT_PARENT_ID,
"等级过滤父类目",
"N/A",
SwitchState.ENABLED,
SortPriority.SECONDARY,
AutomaticSettlementPolicy.DISABLED);
PlayCommodityInfoEntity pricedChild = createCommodity(
pricedChildId,
parent.getId(),
"有定价子项",
"60min",
SwitchState.ENABLED,
SortPriority.PRIMARY,
AutomaticSettlementPolicy.DISABLED);
createCommodity(
unpricedChildId,
parent.getId(),
"无定价子项",
"60min",
SwitchState.ENABLED,
SortPriority.SECONDARY,
AutomaticSettlementPolicy.DISABLED);
PlayCommodityAndLevelInfoEntity price = createPrice(pricedChild.getId(), new BigDecimal("99.90"));
linkCommodityToClerk(parent, SwitchState.ENABLED, SortPriority.PRIMARY);
MvcResult result = mockMvc.perform(get("/wx/commodity/custom/queryClerkAllCommodityByLevel")
.param("id", ApiTestDataSeeder.DEFAULT_CLERK_LEVEL_ID)
.header(USER_HEADER, DEFAULT_USER)
.header(TENANT_HEADER, DEFAULT_TENANT))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(200))
.andReturn();
JsonNode root = objectMapper.readTree(result.getResponse().getContentAsString());
JsonNode data = root.get("data");
JsonNode parentNode = findNodeById(data, parent.getId());
assertThat(parentNode).isNotNull();
JsonNode children = parentNode.get("child");
assertThat(children.isArray()).isTrue();
JsonNode pricedNode = findNodeById(children, pricedChild.getId());
assertThat(pricedNode).isNotNull();
assertThat(pricedNode.get("price").decimalValue()).isEqualByComparingTo(new BigDecimal("99.90"));
JsonNode removedNode = findNodeById(children, unpricedChildId);
assertThat(removedNode).isNull();
}
@Test
// 测试用例:为店员同时配置上架与下架的类目,调用顾客查询接口,确认停用类目被隐藏,避免顾客看到不可售项目。
void customerCommodityQueryFiltersDisabledParents() throws Exception {
ensureTenantContext();
String enabledParentId = generateId("svc-enabled-parent-");
String disabledParentId = generateId("svc-disabled-parent-");
PlayCommodityInfoEntity enabledParent = createCommodity(
enabledParentId,
ROOT_PARENT_ID,
"顾客可见父类目",
"N/A",
SwitchState.ENABLED,
SortPriority.PRIMARY,
AutomaticSettlementPolicy.DISABLED);
PlayCommodityInfoEntity enabledChild = createCommodity(
generateId("svc-enabled-child-"),
enabledParent.getId(),
"顾客可见子项",
"30min",
SwitchState.ENABLED,
SortPriority.PRIMARY,
AutomaticSettlementPolicy.DISABLED);
PlayCommodityAndLevelInfoEntity enabledPrice = createPrice(enabledChild.getId(), new BigDecimal("45.00"));
PlayCommodityInfoEntity disabledParent = createCommodity(
disabledParentId,
ROOT_PARENT_ID,
"顾客屏蔽父类目",
"N/A",
SwitchState.ENABLED,
SortPriority.SECONDARY,
AutomaticSettlementPolicy.DISABLED);
PlayCommodityInfoEntity disabledChild = createCommodity(
generateId("svc-disabled-child-"),
disabledParent.getId(),
"顾客屏蔽子项",
"30min",
SwitchState.ENABLED,
SortPriority.PRIMARY,
AutomaticSettlementPolicy.DISABLED);
PlayCommodityAndLevelInfoEntity disabledPrice = createPrice(disabledChild.getId(), new BigDecimal("55.00"));
linkCommodityToClerk(enabledParent, SwitchState.ENABLED, SortPriority.PRIMARY);
linkCommodityToClerk(disabledParent, SwitchState.DISABLED, SortPriority.SECONDARY);
MvcResult result = mockMvc.perform(get("/wx/commodity/custom/queryClerkAllCommodity")
.param("id", ApiTestDataSeeder.DEFAULT_CLERK_ID)
.header(USER_HEADER, DEFAULT_USER)
.header(TENANT_HEADER, DEFAULT_TENANT))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(200))
.andReturn();
JsonNode root = objectMapper.readTree(result.getResponse().getContentAsString());
JsonNode data = root.get("data");
JsonNode enabledNode = findNodeById(data, enabledParent.getId());
assertThat(enabledNode).isNotNull();
JsonNode disabledNode = findNodeById(data, disabledParent.getId());
assertThat(disabledNode).isNull();
}
@Test
// 测试用例:顾客随机下单选择未配置价格的商品时,接口应返回“服务项目不存在”的业务错误,验证错误商品被拒单。
void randomOrderRejectsCommodityWithoutPricing() throws Exception {
ensureTenantContext();
resetCustomerBalance();
String customerToken = wxTokenService.createWxUserToken(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID);
customUserInfoService.updateTokenById(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID, customerToken);
PlayCommodityInfoEntity parent = createCommodity(
generateId("svc-parent-"),
ROOT_PARENT_ID,
"未定价父类目",
"N/A",
SwitchState.ENABLED,
SortPriority.PRIMARY,
AutomaticSettlementPolicy.DISABLED);
PlayCommodityInfoEntity child = createCommodity(
generateId("svc-child-"),
parent.getId(),
"未定价子项",
"45min",
SwitchState.ENABLED,
SortPriority.PRIMARY,
AutomaticSettlementPolicy.DISABLED);
ObjectNode payload = objectMapper.createObjectNode();
payload.put("sex", OrderConstant.Gender.FEMALE.getCode());
payload.put("levelId", ApiTestDataSeeder.DEFAULT_CLERK_LEVEL_ID);
payload.put("commodityId", child.getId());
payload.put("commodityQuantity", SINGLE_COMMODITY_QUANTITY);
payload.put("weiChatCode", "apitest-customer-wx");
payload.put("excludeHistory", HistoryFilter.INCLUDE.getCode());
payload.putArray("couponIds");
payload.put("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.toString()))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(500))
.andExpect(jsonPath("$.message").value("服务项目不存在"));
}
private JsonNode findNodeById(JsonNode arrayNode, String id) {
if (arrayNode == null || !arrayNode.isArray()) {
return null;
}
for (JsonNode node : arrayNode) {
if (node.has("id") && id.equals(node.get("id").asText())) {
return node;
}
}
return null;
}
private PlayCommodityInfoEntity createCommodity(
String id,
String parentId,
String name,
String duration,
SwitchState switchState,
SortPriority sortPriority,
AutomaticSettlementPolicy settlementPolicy) {
ensureTenantContext();
PlayCommodityInfoEntity entity = new PlayCommodityInfoEntity();
entity.setId(id);
entity.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
entity.setPId(parentId);
entity.setItemType("api-test");
entity.setItemName(name);
entity.setServiceDuration(duration);
entity.setAutomaticSettlementDuration(settlementPolicy.getSeconds());
entity.setEnableStace(switchState.getCode());
entity.setSort(sortPriority.getValue());
commodityInfoService.save(entity);
commodityIdsToCleanup.addFirst(entity.getId());
return entity;
}
private PlayCommodityAndLevelInfoEntity createPrice(String commodityId, BigDecimal price) {
ensureTenantContext();
PlayCommodityAndLevelInfoEntity entity = new PlayCommodityAndLevelInfoEntity();
entity.setId(IdUtils.getUuid());
entity.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
entity.setCommodityId(commodityId);
entity.setLevelId(ApiTestDataSeeder.DEFAULT_CLERK_LEVEL_ID);
entity.setPrice(price);
entity.setSort(DEFAULT_PRICE_SORT);
commodityAndLevelInfoService.save(entity);
priceIdsToCleanup.add(entity.getId());
return entity;
}
private PlayClerkCommodityEntity linkCommodityToClerk(
PlayCommodityInfoEntity parent,
SwitchState switchState,
SortPriority sortPriority) {
ensureTenantContext();
PlayClerkCommodityEntity entity = new PlayClerkCommodityEntity();
entity.setId(IdUtils.getUuid());
entity.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
entity.setClerkId(ApiTestDataSeeder.DEFAULT_CLERK_ID);
entity.setCommodityId(parent.getId());
entity.setCommodityName(parent.getItemName());
entity.setEnablingState(switchState.getCode());
entity.setSort(sortPriority.getValue());
clerkCommodityService.save(entity);
clerkCommodityIdsToCleanup.add(entity.getId());
return entity;
}
private String generateId(String prefix) {
return prefix + IdUtils.getUuid().replace("-", "");
}
@Override
protected void ensureTenantContext() {
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
}
}