fix: harden apitest seeder and pk schedulers
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package com.starry.admin.common.apitest;
|
||||
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.starry.admin.modules.clerk.mapper.PlayClerkUserInfoMapper;
|
||||
import com.starry.admin.modules.clerk.module.entity.PlayClerkCommodityEntity;
|
||||
import com.starry.admin.modules.clerk.module.entity.PlayClerkLevelInfoEntity;
|
||||
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;
|
||||
@@ -23,6 +24,7 @@ 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.SysUserMapper;
|
||||
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;
|
||||
@@ -77,6 +79,7 @@ public class ApiTestDataSeeder implements CommandLineRunner {
|
||||
private final ISysTenantPackageService tenantPackageService;
|
||||
private final ISysTenantService tenantService;
|
||||
private final SysUserService sysUserService;
|
||||
private final SysUserMapper sysUserMapper;
|
||||
private final IPlayPersonnelGroupInfoService personnelGroupInfoService;
|
||||
private final IPlayClerkLevelInfoService clerkLevelInfoService;
|
||||
private final IPlayClerkUserInfoService clerkUserInfoService;
|
||||
@@ -84,6 +87,7 @@ public class ApiTestDataSeeder implements CommandLineRunner {
|
||||
private final IPlayCommodityAndLevelInfoService commodityAndLevelInfoService;
|
||||
private final IPlayGiftInfoService giftInfoService;
|
||||
private final IPlayClerkCommodityService clerkCommodityService;
|
||||
private final PlayClerkUserInfoMapper clerkUserInfoMapper;
|
||||
private final IPlayClerkGiftInfoService playClerkGiftInfoService;
|
||||
private final IPlayCustomUserInfoService customUserInfoService;
|
||||
private final IPlayCustomGiftInfoService playCustomGiftInfoService;
|
||||
@@ -96,6 +100,7 @@ public class ApiTestDataSeeder implements CommandLineRunner {
|
||||
ISysTenantPackageService tenantPackageService,
|
||||
ISysTenantService tenantService,
|
||||
SysUserService sysUserService,
|
||||
SysUserMapper sysUserMapper,
|
||||
IPlayPersonnelGroupInfoService personnelGroupInfoService,
|
||||
IPlayClerkLevelInfoService clerkLevelInfoService,
|
||||
IPlayClerkUserInfoService clerkUserInfoService,
|
||||
@@ -103,6 +108,7 @@ public class ApiTestDataSeeder implements CommandLineRunner {
|
||||
IPlayCommodityAndLevelInfoService commodityAndLevelInfoService,
|
||||
IPlayGiftInfoService giftInfoService,
|
||||
IPlayClerkCommodityService clerkCommodityService,
|
||||
PlayClerkUserInfoMapper clerkUserInfoMapper,
|
||||
IPlayClerkGiftInfoService playClerkGiftInfoService,
|
||||
IPlayCustomUserInfoService customUserInfoService,
|
||||
IPlayCustomGiftInfoService playCustomGiftInfoService,
|
||||
@@ -113,6 +119,7 @@ public class ApiTestDataSeeder implements CommandLineRunner {
|
||||
this.tenantPackageService = tenantPackageService;
|
||||
this.tenantService = tenantService;
|
||||
this.sysUserService = sysUserService;
|
||||
this.sysUserMapper = sysUserMapper;
|
||||
this.personnelGroupInfoService = personnelGroupInfoService;
|
||||
this.clerkLevelInfoService = clerkLevelInfoService;
|
||||
this.clerkUserInfoService = clerkUserInfoService;
|
||||
@@ -120,6 +127,7 @@ public class ApiTestDataSeeder implements CommandLineRunner {
|
||||
this.commodityAndLevelInfoService = commodityAndLevelInfoService;
|
||||
this.giftInfoService = giftInfoService;
|
||||
this.clerkCommodityService = clerkCommodityService;
|
||||
this.clerkUserInfoMapper = clerkUserInfoMapper;
|
||||
this.playClerkGiftInfoService = playClerkGiftInfoService;
|
||||
this.customUserInfoService = customUserInfoService;
|
||||
this.playCustomGiftInfoService = playCustomGiftInfoService;
|
||||
@@ -200,7 +208,8 @@ public class ApiTestDataSeeder implements CommandLineRunner {
|
||||
}
|
||||
|
||||
private void seedAdminUser() {
|
||||
SysUserEntity existing = sysUserService.getById(DEFAULT_ADMIN_USER_ID);
|
||||
SysUserEntity existing = sysUserMapper.selectUserByUserNameAndTenantId(
|
||||
DEFAULT_ADMIN_USERNAME, DEFAULT_TENANT_ID);
|
||||
if (existing != null) {
|
||||
log.info("API test admin user {} already exists", DEFAULT_ADMIN_USER_ID);
|
||||
return;
|
||||
@@ -374,6 +383,15 @@ public class ApiTestDataSeeder implements CommandLineRunner {
|
||||
log.info("API test clerk {} already exists", DEFAULT_CLERK_ID);
|
||||
return;
|
||||
}
|
||||
PlayClerkUserInfoEntity existing = clerkUserInfoMapper.selectByIdIncludingDeleted(DEFAULT_CLERK_ID);
|
||||
if (existing != null) {
|
||||
clerkUserInfoService.update(Wrappers.<PlayClerkUserInfoEntity>lambdaUpdate()
|
||||
.eq(PlayClerkUserInfoEntity::getId, DEFAULT_CLERK_ID)
|
||||
.set(PlayClerkUserInfoEntity::getDeleted, Boolean.FALSE)
|
||||
.set(PlayClerkUserInfoEntity::getToken, clerkToken));
|
||||
log.info("API test clerk {} restored from deleted state", DEFAULT_CLERK_ID);
|
||||
return;
|
||||
}
|
||||
|
||||
PlayClerkUserInfoEntity entity = new PlayClerkUserInfoEntity();
|
||||
entity.setId(DEFAULT_CLERK_ID);
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
|
||||
import com.github.yulichang.base.MPJBaseMapper;
|
||||
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;
|
||||
import java.util.List;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
|
||||
/**
|
||||
@@ -17,4 +18,8 @@ public interface PlayClerkUserInfoMapper extends MPJBaseMapper<PlayClerkUserInfo
|
||||
@InterceptorIgnore(tenantLine = "true")
|
||||
@Select("SELECT id, tenant_id, album FROM play_clerk_user_info WHERE deleted = 0 AND album IS NOT NULL")
|
||||
List<PlayClerkUserInfoEntity> selectAllWithAlbumIgnoringTenant();
|
||||
|
||||
@InterceptorIgnore(tenantLine = "true")
|
||||
@Select("SELECT id, tenant_id, deleted FROM play_clerk_user_info WHERE id = #{id} LIMIT 1")
|
||||
PlayClerkUserInfoEntity selectByIdIncludingDeleted(@Param("id") String id);
|
||||
}
|
||||
|
||||
@@ -144,6 +144,7 @@ class PkIntegrationTest extends AbstractApiTest {
|
||||
if (stringRedisTemplate.getConnectionFactory() == null) {
|
||||
return;
|
||||
}
|
||||
SecurityUtils.setTenantId(DEFAULT_TENANT);
|
||||
stringRedisTemplate.getConnectionFactory().getConnection().flushDb();
|
||||
}
|
||||
|
||||
@@ -792,6 +793,56 @@ class PkIntegrationTest extends AbstractApiTest {
|
||||
assertThat(SecurityUtils.getTenantId()).isEqualTo(TENANT_ORIGIN);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Start Scheduler 应处理多个租户的PK")
|
||||
void startSchedulerShouldProcessMultipleTenants() {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
String pkIdA = IdUtils.getUuid();
|
||||
String pkIdB = IdUtils.getUuid();
|
||||
String originalTenant = SecurityUtils.getTenantId();
|
||||
|
||||
PlayClerkPkEntity pkA = buildPk(pkIdA, newClerkId(), newClerkId(),
|
||||
now.minusMinutes(MINUTES_BEFORE_START),
|
||||
now.plusMinutes(MINUTES_AFTER_START),
|
||||
ClerkPkEnum.TO_BE_STARTED.name(),
|
||||
SETTLED_FALSE);
|
||||
pkA.setTenantId(TENANT_A);
|
||||
PlayClerkPkEntity pkB = buildPk(pkIdB, newClerkId(), newClerkId(),
|
||||
now.minusMinutes(MINUTES_BEFORE_START),
|
||||
now.plusMinutes(MINUTES_AFTER_START),
|
||||
ClerkPkEnum.TO_BE_STARTED.name(),
|
||||
SETTLED_FALSE);
|
||||
pkB.setTenantId(TENANT_B);
|
||||
|
||||
SecurityUtils.setTenantId(TENANT_A);
|
||||
clerkPkService.save(pkA);
|
||||
SecurityUtils.setTenantId(TENANT_B);
|
||||
clerkPkService.save(pkB);
|
||||
|
||||
long beginEpochSeconds = pkA.getPkBeginTime().toInstant().getEpochSecond();
|
||||
stringRedisTemplate.opsForZSet().add(PkRedisKeyConstants.startScheduleKey(TENANT_A), pkIdA, beginEpochSeconds);
|
||||
stringRedisTemplate.opsForZSet().add(PkRedisKeyConstants.startScheduleKey(TENANT_B), pkIdB, beginEpochSeconds);
|
||||
|
||||
SysTenantEntity tenantA = buildTenant(TENANT_A);
|
||||
SysTenantEntity tenantB = buildTenant(TENANT_B);
|
||||
try {
|
||||
Mockito.doReturn(Arrays.asList(tenantA, tenantB))
|
||||
.when(sysTenantServiceSpy).listAll();
|
||||
startSchedulerJob.scanStartSchedule();
|
||||
} finally {
|
||||
Mockito.reset(sysTenantServiceSpy);
|
||||
SecurityUtils.setTenantId(originalTenant);
|
||||
}
|
||||
|
||||
SecurityUtils.setTenantId(TENANT_A);
|
||||
PlayClerkPkEntity persistedA = clerkPkService.selectPlayClerkPkById(pkIdA);
|
||||
assertThat(persistedA.getStatus()).isEqualTo(ClerkPkEnum.IN_PROGRESS.name());
|
||||
|
||||
SecurityUtils.setTenantId(TENANT_B);
|
||||
PlayClerkPkEntity persistedB = clerkPkService.selectPlayClerkPkById(pkIdB);
|
||||
assertThat(persistedB.getStatus()).isEqualTo(ClerkPkEnum.IN_PROGRESS.name());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Start Scheduler 不应启动未到时间的PK")
|
||||
void startSchedulerShouldSkipFuturePk() {
|
||||
@@ -872,6 +923,58 @@ class PkIntegrationTest extends AbstractApiTest {
|
||||
assertThat(stringRedisTemplate.opsForZSet().score(finishKey, pkId)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Finish Scheduler 应处理多个租户的PK")
|
||||
void finishSchedulerShouldProcessMultipleTenants() {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
String pkIdA = IdUtils.getUuid();
|
||||
String pkIdB = IdUtils.getUuid();
|
||||
String originalTenant = SecurityUtils.getTenantId();
|
||||
|
||||
PlayClerkPkEntity pkA = buildPk(pkIdA, newClerkId(), newClerkId(),
|
||||
now.minusMinutes(MINUTES_BEFORE_START),
|
||||
now.minusMinutes(MINUTES_AFTER_START),
|
||||
ClerkPkEnum.IN_PROGRESS.name(),
|
||||
SETTLED_FALSE);
|
||||
pkA.setTenantId(TENANT_A);
|
||||
PlayClerkPkEntity pkB = buildPk(pkIdB, newClerkId(), newClerkId(),
|
||||
now.minusMinutes(MINUTES_BEFORE_START),
|
||||
now.minusMinutes(MINUTES_AFTER_START),
|
||||
ClerkPkEnum.IN_PROGRESS.name(),
|
||||
SETTLED_FALSE);
|
||||
pkB.setTenantId(TENANT_B);
|
||||
|
||||
SecurityUtils.setTenantId(TENANT_A);
|
||||
clerkPkService.save(pkA);
|
||||
SecurityUtils.setTenantId(TENANT_B);
|
||||
clerkPkService.save(pkB);
|
||||
|
||||
long endEpochSeconds = pkA.getPkEndTime().toInstant().getEpochSecond();
|
||||
stringRedisTemplate.opsForZSet().add(PkRedisKeyConstants.finishScheduleKey(TENANT_A), pkIdA, endEpochSeconds);
|
||||
stringRedisTemplate.opsForZSet().add(PkRedisKeyConstants.finishScheduleKey(TENANT_B), pkIdB, endEpochSeconds);
|
||||
|
||||
SysTenantEntity tenantA = buildTenant(TENANT_A);
|
||||
SysTenantEntity tenantB = buildTenant(TENANT_B);
|
||||
try {
|
||||
Mockito.doReturn(Arrays.asList(tenantA, tenantB))
|
||||
.when(sysTenantServiceSpy).listAll();
|
||||
finishSchedulerJob.scanFinishSchedule();
|
||||
} finally {
|
||||
Mockito.reset(sysTenantServiceSpy);
|
||||
SecurityUtils.setTenantId(originalTenant);
|
||||
}
|
||||
|
||||
SecurityUtils.setTenantId(TENANT_A);
|
||||
PlayClerkPkEntity persistedA = clerkPkService.selectPlayClerkPkById(pkIdA);
|
||||
assertThat(persistedA.getStatus()).isEqualTo(ClerkPkEnum.FINISHED.name());
|
||||
assertThat(persistedA.getSettled()).isEqualTo(SETTLED_TRUE);
|
||||
|
||||
SecurityUtils.setTenantId(TENANT_B);
|
||||
PlayClerkPkEntity persistedB = clerkPkService.selectPlayClerkPkById(pkIdB);
|
||||
assertThat(persistedB.getStatus()).isEqualTo(ClerkPkEnum.FINISHED.name());
|
||||
assertThat(persistedB.getSettled()).isEqualTo(SETTLED_TRUE);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Finish Scheduler 应恢复租户上下文")
|
||||
void finishSchedulerShouldRestoreTenantContext() {
|
||||
|
||||
@@ -67,6 +67,7 @@ class WxPkApiTest extends AbstractApiTest {
|
||||
private static final int HISTORY_PAGE_NUM = 1;
|
||||
private static final int HISTORY_PAGE_SIZE = 10;
|
||||
private static final long REMAINING_SECONDS_MIN = 1L;
|
||||
private static final long TIME_SYNC_TOLERANCE_SECONDS = 2L;
|
||||
private static final int SETTLED_FALSE = 0;
|
||||
private static final int SETTLED_TRUE = 1;
|
||||
private static final double ZSET_SCORE_PAST = 1D;
|
||||
@@ -146,6 +147,7 @@ class WxPkApiTest extends AbstractApiTest {
|
||||
assertThat(data.get("remainingSeconds").asLong()).isGreaterThanOrEqualTo(REMAINING_SECONDS_MIN);
|
||||
assertThat(data.get("serverEpochSeconds").asLong()).isGreaterThanOrEqualTo(REMAINING_SECONDS_MIN);
|
||||
assertThat(data.get("pkEndEpochSeconds").asLong()).isGreaterThanOrEqualTo(REMAINING_SECONDS_MIN);
|
||||
assertTimeSync(data);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -639,4 +641,16 @@ class WxPkApiTest extends AbstractApiTest {
|
||||
JsonNode root = OBJECT_MAPPER.readTree(result.getResponse().getContentAsString());
|
||||
return root.get("data");
|
||||
}
|
||||
|
||||
private static void assertTimeSync(JsonNode data) {
|
||||
assertThat(data.get("remainingSeconds")).isNotNull();
|
||||
assertThat(data.get("serverEpochSeconds")).isNotNull();
|
||||
assertThat(data.get("pkEndEpochSeconds")).isNotNull();
|
||||
long remainingSeconds = data.get("remainingSeconds").asLong();
|
||||
long serverEpochSeconds = data.get("serverEpochSeconds").asLong();
|
||||
long pkEndEpochSeconds = data.get("pkEndEpochSeconds").asLong();
|
||||
long deltaSeconds = pkEndEpochSeconds - serverEpochSeconds;
|
||||
long drift = Math.abs(deltaSeconds - remainingSeconds);
|
||||
assertThat(drift).isLessThanOrEqualTo(TIME_SYNC_TOLERANCE_SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user