test(apitest): add e2e seed endpoints and coverage

This commit is contained in:
irving
2026-01-17 00:49:54 -05:00
parent e2300fc7d0
commit 6a3b4fef1f
7 changed files with 1228 additions and 10 deletions

View File

@@ -28,7 +28,9 @@ import com.starry.admin.modules.shop.service.IPlayClerkGiftInfoService;
import com.starry.admin.modules.shop.service.IPlayCommodityAndLevelInfoService;
import com.starry.admin.modules.shop.service.IPlayCommodityInfoService;
import com.starry.admin.modules.shop.service.IPlayGiftInfoService;
import com.starry.admin.modules.system.mapper.SysMenuMapper;
import com.starry.admin.modules.system.mapper.SysUserMapper;
import com.starry.admin.modules.system.module.entity.SysMenuEntity;
import com.starry.admin.modules.system.module.entity.SysTenantEntity;
import com.starry.admin.modules.system.module.entity.SysTenantPackageEntity;
import com.starry.admin.modules.system.module.entity.SysUserEntity;
@@ -37,12 +39,14 @@ import com.starry.admin.modules.system.service.ISysTenantService;
import com.starry.admin.modules.system.service.SysUserService;
import com.starry.admin.modules.weichat.service.WxTokenService;
import com.starry.admin.utils.SecurityUtils;
import com.starry.common.constant.UserConstants;
import com.starry.common.context.CustomSecurityContextHolder;
import com.starry.common.utils.IdUtils;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.Objects;
import java.util.Optional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.CommandLineRunner;
@@ -77,6 +81,7 @@ public class ApiTestDataSeeder implements CommandLineRunner {
public static final String DEFAULT_GIFT_ID = "gift-basic";
public static final String DEFAULT_GIFT_NAME = "API测试礼物";
public static final BigDecimal DEFAULT_COMMODITY_PRICE = new BigDecimal("120.00");
public static final BigDecimal E2E_CUSTOMER_BALANCE = new BigDecimal("1000.00");
private static final String GIFT_TYPE_REGULAR = "1";
private static final String GIFT_STATE_ACTIVE = "0";
private static final BigDecimal DEFAULT_CUSTOMER_BALANCE = new BigDecimal("200.00");
@@ -86,6 +91,7 @@ public class ApiTestDataSeeder implements CommandLineRunner {
private final ISysTenantService tenantService;
private final SysUserService sysUserService;
private final SysUserMapper sysUserMapper;
private final SysMenuMapper sysMenuMapper;
private final IPlayPersonnelGroupInfoService personnelGroupInfoService;
private final IPlayClerkLevelInfoService clerkLevelInfoService;
private final IPlayClerkUserInfoService clerkUserInfoService;
@@ -108,6 +114,7 @@ public class ApiTestDataSeeder implements CommandLineRunner {
ISysTenantService tenantService,
SysUserService sysUserService,
SysUserMapper sysUserMapper,
SysMenuMapper sysMenuMapper,
IPlayPersonnelGroupInfoService personnelGroupInfoService,
IPlayClerkLevelInfoService clerkLevelInfoService,
IPlayClerkUserInfoService clerkUserInfoService,
@@ -128,6 +135,7 @@ public class ApiTestDataSeeder implements CommandLineRunner {
this.tenantService = tenantService;
this.sysUserService = sysUserService;
this.sysUserMapper = sysUserMapper;
this.sysMenuMapper = sysMenuMapper;
this.personnelGroupInfoService = personnelGroupInfoService;
this.clerkLevelInfoService = clerkLevelInfoService;
this.clerkUserInfoService = clerkUserInfoService;
@@ -149,6 +157,7 @@ public class ApiTestDataSeeder implements CommandLineRunner {
@Override
@Transactional
public void run(String... args) {
seedPcTenantWagesMenu();
seedTenantPackage();
seedTenant();
@@ -173,6 +182,98 @@ public class ApiTestDataSeeder implements CommandLineRunner {
}
}
private void seedPcTenantWagesMenu() {
// Minimal menu tree for pc-tenant E2E: /play/clerk/wages -> play/clerk/wages/index.vue
// This is apitest-only; prod/dev menus are managed by ops/admin tooling.
SysMenuEntity playRoot = ensureMenu(
"陪聊管理",
"PlayManage",
0L,
UserConstants.TYPE_DIR,
"/play",
UserConstants.LAYOUT,
50);
SysMenuEntity clerkDir = ensureMenu(
"店员管理",
"ClerkManage",
playRoot.getMenuId(),
UserConstants.TYPE_DIR,
"clerk",
"",
1);
ensureMenu(
"收益管理",
"ClerkWages",
clerkDir.getMenuId(),
UserConstants.TYPE_MENU,
"wages",
"play/clerk/wages/index",
1);
}
private SysMenuEntity ensureMenu(
String menuName,
String menuCode,
Long parentId,
String menuType,
String path,
String component,
Integer sort) {
Optional<SysMenuEntity> existing = sysMenuMapper.selectList(Wrappers.<SysMenuEntity>lambdaQuery()
.eq(SysMenuEntity::getDeleted, false)
.eq(SysMenuEntity::getParentId, parentId)
.eq(SysMenuEntity::getMenuCode, menuCode)
.last("limit 1"))
.stream()
.findFirst();
if (existing.isPresent()) {
SysMenuEntity current = existing.get();
boolean changed = false;
if (!Objects.equals(current.getPath(), path)) {
current.setPath(path);
changed = true;
}
if (!Objects.equals(current.getComponent(), component)) {
current.setComponent(component);
changed = true;
}
if (changed) {
current.setUpdatedBy("apitest-seed");
current.setUpdatedTime(new Date());
sysMenuMapper.updateById(current);
log.info("Updated apitest sys_menu '{}' path='{}' component='{}'", menuName, path, component);
}
return current;
}
SysMenuEntity entity = new SysMenuEntity();
entity.setMenuName(menuName);
entity.setMenuCode(menuCode);
entity.setIcon("el-icon-menu");
entity.setPermission("");
entity.setMenuLevel(parentId == 0 ? 1L : 2L);
entity.setSort(sort);
entity.setParentId(parentId);
entity.setMenuType(menuType);
entity.setStatus(0);
entity.setRemark(menuName);
entity.setPath(path);
entity.setComponent(component);
entity.setRouterQuery("");
entity.setIsFrame(0);
entity.setVisible(1);
entity.setDeleted(Boolean.FALSE);
entity.setCreatedBy("apitest-seed");
entity.setCreatedTime(new Date());
entity.setUpdatedBy("apitest-seed");
entity.setUpdatedTime(new Date());
sysMenuMapper.insert(entity);
log.info("Inserted apitest sys_menu '{}' path='{}' parentId={}", menuName, path, parentId);
return entity;
}
private void seedTenantPackage() {
long existing = tenantPackageService.count(Wrappers.<SysTenantPackageEntity>lambdaQuery()
.eq(SysTenantPackageEntity::getPackageId, DEFAULT_PACKAGE_ID));
@@ -496,22 +597,27 @@ public class ApiTestDataSeeder implements CommandLineRunner {
private void seedClerkCommodity() {
PlayClerkCommodityEntity mapping = clerkCommodityService.getById(DEFAULT_CLERK_COMMODITY_ID);
if (mapping != null) {
log.info("API test clerk commodity {} already exists", DEFAULT_CLERK_COMMODITY_ID);
return;
}
String commodityName = DEFAULT_COMMODITY_PARENT_NAME;
PlayCommodityInfoEntity parent = commodityInfoService.getById(DEFAULT_COMMODITY_PARENT_ID);
if (parent != null && parent.getItemName() != null) {
commodityName = parent.getItemName();
}
if (mapping != null) {
clerkCommodityService.update(Wrappers.<PlayClerkCommodityEntity>lambdaUpdate()
.eq(PlayClerkCommodityEntity::getId, DEFAULT_CLERK_COMMODITY_ID)
.set(PlayClerkCommodityEntity::getCommodityId, DEFAULT_COMMODITY_PARENT_ID)
.set(PlayClerkCommodityEntity::getCommodityName, commodityName)
.set(PlayClerkCommodityEntity::getEnablingState, "1"));
log.info("API test clerk commodity {} already exists, state refreshed", DEFAULT_CLERK_COMMODITY_ID);
return;
}
PlayClerkCommodityEntity entity = new PlayClerkCommodityEntity();
entity.setId(DEFAULT_CLERK_COMMODITY_ID);
entity.setTenantId(DEFAULT_TENANT_ID);
entity.setClerkId(DEFAULT_CLERK_ID);
entity.setCommodityId(DEFAULT_COMMODITY_ID);
entity.setCommodityId(DEFAULT_COMMODITY_PARENT_ID);
entity.setCommodityName(commodityName);
entity.setEnablingState("1");
entity.setSort(1);

View File

@@ -52,6 +52,9 @@ public class WxCustomMpService {
@Resource
private WxMpService wxMpService;
@Value("${wechat.subscribe-check-enabled:true}")
private boolean subscribeCheckEnabled;
@Resource
private SysTenantServiceImpl tenantService;
@Resource
@@ -480,6 +483,9 @@ public class WxCustomMpService {
if (StrUtil.isBlankIfStr(openId)) {
throw new ServiceException("openId不能为空");
}
if (!subscribeCheckEnabled) {
return;
}
try {
WxMpUser wxMpUser = proxyWxMpService(tenantId).getUserService().userInfo(openId);
if (!wxMpUser.getSubscribe()) {

View File

@@ -78,7 +78,14 @@ apitest:
user-header: X-Test-User
defaults:
tenant-id: tenant-apitest
user-id: apitest-user
# Must exist in DB. ApiTestDataSeeder seeds DEFAULT_ADMIN_USER_ID=user-apitest-admin.
user-id: user-apitest-admin
roles:
- ROLE_TESTER
permissions: []
permissions:
- withdraw:deduction:create
- withdraw:deduction:read
# E2E/ApiTest: skip real WeChat subscribe check to keep flows deterministic.
wechat:
subscribe-check-enabled: false

View File

@@ -0,0 +1,52 @@
package com.starry.admin.api;
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.starry.admin.common.apitest.ApiTestDataSeeder;
import org.junit.jupiter.api.Test;
import org.springframework.http.MediaType;
import org.springframework.test.context.TestPropertySource;
@TestPropertySource(properties = "test.auth.secret=apitest-secret")
class WxOauthAdminTestAuthApiTest extends AbstractApiTest {
private static final String TEST_AUTH_HEADER = "X-Test-Auth";
private static final String TEST_AUTH_SECRET = "apitest-secret";
@Test
void adminLoginByUsernameRejectsWithoutSecretHeader() throws Exception {
mockMvc.perform(post("/wx/oauth2/admin/loginByUsername")
.header(USER_HEADER, DEFAULT_USER)
.header(TENANT_HEADER, DEFAULT_TENANT)
.header("User-Agent", "apitest")
.contentType(MediaType.APPLICATION_JSON)
.content("{" +
"\"userName\":\"" + ApiTestDataSeeder.DEFAULT_ADMIN_USERNAME + "\"," +
"\"passWord\":\"apitest-secret\"," +
"\"tenantKey\":\"" + ApiTestDataSeeder.DEFAULT_TENANT_KEY + "\"" +
"}"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(403));
}
@Test
void adminLoginByUsernameReturnsTokenWhenSecretHeaderValid() throws Exception {
mockMvc.perform(post("/wx/oauth2/admin/loginByUsername")
.header(USER_HEADER, DEFAULT_USER)
.header(TENANT_HEADER, DEFAULT_TENANT)
.header("User-Agent", "apitest")
.header(TEST_AUTH_HEADER, TEST_AUTH_SECRET)
.contentType(MediaType.APPLICATION_JSON)
.content("{" +
"\"userName\":\"" + ApiTestDataSeeder.DEFAULT_ADMIN_USERNAME + "\"," +
"\"passWord\":\"apitest-secret\"," +
"\"tenantKey\":\"" + ApiTestDataSeeder.DEFAULT_TENANT_KEY + "\"" +
"}"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(200))
.andExpect(jsonPath("$.data.tokenHead").isNotEmpty())
.andExpect(jsonPath("$.data.token").isNotEmpty());
}
}

View File

@@ -0,0 +1,51 @@
package com.starry.admin.api;
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.starry.admin.common.apitest.ApiTestDataSeeder;
import org.junit.jupiter.api.Test;
import org.springframework.http.MediaType;
import org.springframework.test.context.TestPropertySource;
@TestPropertySource(properties = "test.auth.secret=apitest-secret")
class WxOauthE2eSeedOrderApiTest extends AbstractApiTest {
private static final String TEST_AUTH_HEADER = "X-Test-Auth";
private static final String TEST_AUTH_SECRET = "apitest-secret";
@Test
void seedOrderRejectsWithoutSecretHeader() throws Exception {
mockMvc.perform(post("/wx/oauth2/e2e/seed/order")
.header(USER_HEADER, DEFAULT_USER)
.header(TENANT_HEADER, DEFAULT_TENANT)
.header("User-Agent", "apitest")
.contentType(MediaType.APPLICATION_JSON)
.content("{}"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(403));
}
@Test
void seedOrderReturnsFixtureWhenSecretHeaderValid() throws Exception {
mockMvc.perform(post("/wx/oauth2/e2e/seed/order")
.header(USER_HEADER, DEFAULT_USER)
.header(TENANT_HEADER, DEFAULT_TENANT)
.header("User-Agent", "apitest")
.header(TEST_AUTH_HEADER, TEST_AUTH_SECRET)
.contentType(MediaType.APPLICATION_JSON)
.content("{}"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(200))
.andExpect(jsonPath("$.data.tenantKey").value(ApiTestDataSeeder.DEFAULT_TENANT_KEY))
.andExpect(jsonPath("$.data.customerId").value(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID))
.andExpect(jsonPath("$.data.customerNickname").isNotEmpty())
.andExpect(jsonPath("$.data.clerkId").value(ApiTestDataSeeder.DEFAULT_CLERK_ID))
.andExpect(jsonPath("$.data.clerkNickname").isNotEmpty())
.andExpect(jsonPath("$.data.clerkLevelId").value(ApiTestDataSeeder.DEFAULT_CLERK_LEVEL_ID))
.andExpect(jsonPath("$.data.clerkSex").value("2"))
.andExpect(jsonPath("$.data.commodityId").value(ApiTestDataSeeder.DEFAULT_COMMODITY_ID))
.andExpect(jsonPath("$.data.giftId").value(ApiTestDataSeeder.DEFAULT_GIFT_ID));
}
}

View File

@@ -0,0 +1,48 @@
package com.starry.admin.api;
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.starry.admin.common.apitest.ApiTestDataSeeder;
import org.junit.jupiter.api.Test;
import org.springframework.http.MediaType;
import org.springframework.test.context.TestPropertySource;
@TestPropertySource(properties = "test.auth.secret=apitest-secret")
class WxOauthE2eSeedWageAdjustmentApiTest extends AbstractApiTest {
private static final String TEST_AUTH_HEADER = "X-Test-Auth";
private static final String TEST_AUTH_SECRET = "apitest-secret";
@Test
void seedWageAdjustmentRejectsWithoutSecretHeader() throws Exception {
mockMvc.perform(post("/wx/oauth2/e2e/seed/wage-adjustment")
.header(USER_HEADER, DEFAULT_USER)
.header(TENANT_HEADER, DEFAULT_TENANT)
.header("User-Agent", "apitest")
.contentType(MediaType.APPLICATION_JSON)
.content("{}"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(403));
}
@Test
void seedWageAdjustmentReturnsFixtureWhenSecretHeaderValid() throws Exception {
mockMvc.perform(post("/wx/oauth2/e2e/seed/wage-adjustment")
.header(USER_HEADER, DEFAULT_USER)
.header(TENANT_HEADER, DEFAULT_TENANT)
.header("User-Agent", "apitest")
.header(TEST_AUTH_HEADER, TEST_AUTH_SECRET)
.contentType(MediaType.APPLICATION_JSON)
.content("{}"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(200))
.andExpect(jsonPath("$.data.tenantKey").value(ApiTestDataSeeder.DEFAULT_TENANT_KEY))
.andExpect(jsonPath("$.data.adminUserName").value(ApiTestDataSeeder.DEFAULT_ADMIN_USERNAME))
.andExpect(jsonPath("$.data.clerkId").value(ApiTestDataSeeder.DEFAULT_CLERK_ID))
.andExpect(jsonPath("$.data.beginTime").isNotEmpty())
.andExpect(jsonPath("$.data.endTime").isNotEmpty())
.andExpect(jsonPath("$.data.baseAmount").value("150.00"));
}
}