feat(pk): implement PK (Player-Killer) system with lifecycle management
- Add PK entity fields: winner, scores, and setting_id - Implement force start/end API endpoints for clerk PK - Add PK lifecycle service with auto-start/end scheduling - Add Redis-based PK state management - Implement PK detail service with live/history/upcoming queries - Add WeChat PK controller with history and live PK endpoints - Add comprehensive PK integration tests - Create PK setting management with tenant-specific configs - Add database migrations for PK scores, winner, settings, and menu - Add PK-related DTOs and enums (status, menu paths) - Add TenantScope utility for tenant context management
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,529 @@
|
||||
package com.starry.admin.modules.pk;
|
||||
|
||||
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.starry.admin.api.AbstractApiTest;
|
||||
import com.starry.admin.modules.clerk.module.entity.ClerkPkEnum;
|
||||
import com.starry.admin.modules.clerk.module.entity.PlayClerkPkEntity;
|
||||
import com.starry.admin.modules.clerk.service.IPlayClerkPkService;
|
||||
import com.starry.admin.modules.pk.enums.PkLifecycleErrorCode;
|
||||
import com.starry.admin.modules.pk.redis.PkRedisKeyConstants;
|
||||
import com.starry.admin.utils.SecurityUtils;
|
||||
import com.starry.common.result.ResultCodeEnum;
|
||||
import com.starry.common.utils.IdUtils;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.Date;
|
||||
import javax.annotation.Resource;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.web.servlet.MvcResult;
|
||||
|
||||
class PlayClerkPkApiTest extends AbstractApiTest {
|
||||
|
||||
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
|
||||
private static final String MSG_PK_NOT_FOUND = "PK不存在";
|
||||
private static final String MSG_PK_START_NOT_REACHED = "PK开始时间尚未到达";
|
||||
private static final String CLERK_PREFIX = "pk-api-clerk-";
|
||||
private static final int MINUTES_BEFORE_START = 5;
|
||||
private static final int MINUTES_AFTER_START = 30;
|
||||
private static final int MINUTES_FUTURE_START = 15;
|
||||
private static final int MINUTES_PAST_END = 10;
|
||||
private static final int FORCE_START_DURATION_MINUTES = 30;
|
||||
private static final int SETTLED_TRUE = 1;
|
||||
private static final int SETTLED_FALSE = 0;
|
||||
private static final BigDecimal TIE_SCORE = new BigDecimal("10.00");
|
||||
private static final BigDecimal SCORE_A = new BigDecimal("12.50");
|
||||
private static final BigDecimal SCORE_B = new BigDecimal("7.25");
|
||||
private static final BigDecimal ZERO_SCORE = new BigDecimal("0.00");
|
||||
private static final int ORDER_COUNT_A = 3;
|
||||
private static final int ORDER_COUNT_B = 1;
|
||||
private static final long REMAINING_SECONDS_ZERO = 0L;
|
||||
private static final int ZERO_COUNT = 0;
|
||||
private static final int ZERO_VALUE = 0;
|
||||
|
||||
@Resource
|
||||
private IPlayClerkPkService clerkPkService;
|
||||
|
||||
@Resource
|
||||
private StringRedisTemplate stringRedisTemplate;
|
||||
|
||||
@Test
|
||||
void forceStartShouldCreateInProgressPk() throws Exception {
|
||||
String clerkAId = newClerkId();
|
||||
String clerkBId = newClerkId();
|
||||
String requestBody = buildForceStartBody(clerkAId, clerkBId, FORCE_START_DURATION_MINUTES);
|
||||
|
||||
MvcResult result = mockMvc.perform(post(buildForceStartPath())
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(requestBody)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.SUCCESS.getCode()))
|
||||
.andReturn();
|
||||
|
||||
String pkId = extractDataAsText(result);
|
||||
PlayClerkPkEntity persisted = loadPk(pkId);
|
||||
assertThat(persisted.getStatus()).isEqualTo(ClerkPkEnum.IN_PROGRESS.name());
|
||||
assertThat(persisted.getClerkA()).isEqualTo(clerkAId);
|
||||
assertThat(persisted.getClerkB()).isEqualTo(clerkBId);
|
||||
assertThat(persisted.getPkBeginTime()).isNotNull();
|
||||
assertThat(persisted.getPkEndTime()).isNotNull();
|
||||
assertThat(persisted.getTenantId()).isEqualTo(DEFAULT_TENANT);
|
||||
assertThat(persisted.getCreatedBy()).isNotNull();
|
||||
assertThat(persisted.getPkEndTime()).isAfter(persisted.getPkBeginTime());
|
||||
}
|
||||
|
||||
@Test
|
||||
void forceStartShouldRejectWhenConflict() throws Exception {
|
||||
String clerkAId = newClerkId();
|
||||
String clerkBId = newClerkId();
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
createPkForClerks(clerkAId, clerkBId, now.minusMinutes(MINUTES_BEFORE_START),
|
||||
now.plusMinutes(MINUTES_AFTER_START), ClerkPkEnum.IN_PROGRESS.name());
|
||||
|
||||
String requestBody = buildForceStartBody(clerkAId, clerkBId, FORCE_START_DURATION_MINUTES);
|
||||
mockMvc.perform(post(buildForceStartPath())
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(requestBody)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.FAILED.getCode()))
|
||||
.andExpect(jsonPath("$.message").value(PkLifecycleErrorCode.CLERK_CONFLICT.getMessage()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void forceStartShouldRejectWhenMissingClerkA() throws Exception {
|
||||
String requestBody = "{\"clerkAId\":\"\",\"clerkBId\":\"" + newClerkId()
|
||||
+ "\",\"durationMinutes\":" + FORCE_START_DURATION_MINUTES + "}";
|
||||
|
||||
mockMvc.perform(post(buildForceStartPath())
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(requestBody)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.FAILED.getCode()))
|
||||
.andExpect(jsonPath("$.message").value(PkLifecycleErrorCode.REQUEST_INVALID.getMessage()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void forceStartShouldRejectWhenMissingClerkB() throws Exception {
|
||||
String requestBody = "{\"clerkAId\":\"" + newClerkId()
|
||||
+ "\",\"clerkBId\":\"\",\"durationMinutes\":" + FORCE_START_DURATION_MINUTES + "}";
|
||||
|
||||
mockMvc.perform(post(buildForceStartPath())
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(requestBody)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.FAILED.getCode()))
|
||||
.andExpect(jsonPath("$.message").value(PkLifecycleErrorCode.REQUEST_INVALID.getMessage()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void forceStartShouldRejectWhenMissingDuration() throws Exception {
|
||||
String requestBody = "{\"clerkAId\":\"" + newClerkId()
|
||||
+ "\",\"clerkBId\":\"" + newClerkId() + "\"}";
|
||||
|
||||
mockMvc.perform(post(buildForceStartPath())
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(requestBody)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.FAILED.getCode()))
|
||||
.andExpect(jsonPath("$.message").value(PkLifecycleErrorCode.REQUEST_INVALID.getMessage()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void forceStartShouldRejectWhenSameClerk() throws Exception {
|
||||
String clerkId = newClerkId();
|
||||
String requestBody = buildForceStartBody(clerkId, clerkId, FORCE_START_DURATION_MINUTES);
|
||||
|
||||
mockMvc.perform(post(buildForceStartPath())
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(requestBody)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.FAILED.getCode()))
|
||||
.andExpect(jsonPath("$.message").value(PkLifecycleErrorCode.REQUEST_INVALID.getMessage()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void forceStartShouldRejectWhenDurationInvalid() throws Exception {
|
||||
String requestBody = buildForceStartBody(newClerkId(), newClerkId(), 0);
|
||||
|
||||
mockMvc.perform(post(buildForceStartPath())
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(requestBody)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.FAILED.getCode()))
|
||||
.andExpect(jsonPath("$.message").value(PkLifecycleErrorCode.REQUEST_INVALID.getMessage()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void forceStartShouldRejectWhenConflictOnClerkAOnly() throws Exception {
|
||||
String clerkAId = newClerkId();
|
||||
String clerkBId = newClerkId();
|
||||
String otherClerk = newClerkId();
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
createPkForClerks(clerkAId, otherClerk, now.minusMinutes(MINUTES_BEFORE_START),
|
||||
now.plusMinutes(MINUTES_AFTER_START), ClerkPkEnum.IN_PROGRESS.name());
|
||||
|
||||
String requestBody = buildForceStartBody(clerkAId, clerkBId, FORCE_START_DURATION_MINUTES);
|
||||
mockMvc.perform(post(buildForceStartPath())
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(requestBody)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.FAILED.getCode()))
|
||||
.andExpect(jsonPath("$.message").value(PkLifecycleErrorCode.CLERK_CONFLICT.getMessage()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void forceStartShouldRejectWhenConflictOnClerkBOnly() throws Exception {
|
||||
String clerkAId = newClerkId();
|
||||
String clerkBId = newClerkId();
|
||||
String otherClerk = newClerkId();
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
createPkForClerks(otherClerk, clerkBId, now.minusMinutes(MINUTES_BEFORE_START),
|
||||
now.plusMinutes(MINUTES_AFTER_START), ClerkPkEnum.IN_PROGRESS.name());
|
||||
|
||||
String requestBody = buildForceStartBody(clerkAId, clerkBId, FORCE_START_DURATION_MINUTES);
|
||||
mockMvc.perform(post(buildForceStartPath())
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(requestBody)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.FAILED.getCode()))
|
||||
.andExpect(jsonPath("$.message").value(PkLifecycleErrorCode.CLERK_CONFLICT.getMessage()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void startPkShouldRejectWhenStartTimeNotReached() throws Exception {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
PlayClerkPkEntity pk = buildPk(ClerkPkEnum.TO_BE_STARTED.name(),
|
||||
now.plusMinutes(MINUTES_FUTURE_START),
|
||||
now.plusMinutes(MINUTES_AFTER_START),
|
||||
SETTLED_FALSE);
|
||||
|
||||
mockMvc.perform(post(buildStartPath(pk.getId()))
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.FAILED.getCode()))
|
||||
.andExpect(jsonPath("$.message").value(MSG_PK_START_NOT_REACHED));
|
||||
}
|
||||
|
||||
@Test
|
||||
void startPkShouldFailWhenMissing() throws Exception {
|
||||
String pkId = IdUtils.getUuid();
|
||||
mockMvc.perform(post(buildStartPath(pkId))
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.FAILED.getCode()))
|
||||
.andExpect(jsonPath("$.message").value(MSG_PK_NOT_FOUND));
|
||||
}
|
||||
|
||||
@Test
|
||||
void startPkShouldAdvanceStatusWhenEligible() throws Exception {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
PlayClerkPkEntity pk = buildPk(ClerkPkEnum.TO_BE_STARTED.name(),
|
||||
now.minusMinutes(MINUTES_BEFORE_START),
|
||||
now.plusMinutes(MINUTES_AFTER_START),
|
||||
SETTLED_FALSE);
|
||||
|
||||
mockMvc.perform(post(buildStartPath(pk.getId()))
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.SUCCESS.getCode()));
|
||||
|
||||
PlayClerkPkEntity persisted = loadPk(pk.getId());
|
||||
assertThat(persisted.getStatus()).isEqualTo(ClerkPkEnum.IN_PROGRESS.name());
|
||||
}
|
||||
|
||||
@Test
|
||||
void finishPkShouldFailWhenMissing() throws Exception {
|
||||
String pkId = IdUtils.getUuid();
|
||||
mockMvc.perform(post(buildFinishPath(pkId))
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.FAILED.getCode()))
|
||||
.andExpect(jsonPath("$.message").value(MSG_PK_NOT_FOUND));
|
||||
}
|
||||
|
||||
@Test
|
||||
void finishPkShouldBeIdempotent() throws Exception {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
PlayClerkPkEntity pk = buildPk(ClerkPkEnum.FINISHED.name(),
|
||||
now.minusMinutes(MINUTES_BEFORE_START),
|
||||
now.minusMinutes(MINUTES_BEFORE_START),
|
||||
SETTLED_TRUE);
|
||||
|
||||
mockMvc.perform(post(buildFinishPath(pk.getId()))
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.SUCCESS.getCode()));
|
||||
|
||||
PlayClerkPkEntity persisted = loadPk(pk.getId());
|
||||
assertThat(persisted.getStatus()).isEqualTo(ClerkPkEnum.FINISHED.name());
|
||||
assertThat(persisted.getSettled()).isEqualTo(SETTLED_TRUE);
|
||||
}
|
||||
|
||||
@Test
|
||||
void finishPkShouldPersistZeroWhenRedisEmpty() throws Exception {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
PlayClerkPkEntity pk = buildPk(ClerkPkEnum.IN_PROGRESS.name(),
|
||||
now.minusMinutes(MINUTES_BEFORE_START),
|
||||
now.minusMinutes(MINUTES_PAST_END),
|
||||
SETTLED_FALSE);
|
||||
|
||||
mockMvc.perform(post(buildFinishPath(pk.getId()))
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.SUCCESS.getCode()));
|
||||
|
||||
PlayClerkPkEntity persisted = loadPk(pk.getId());
|
||||
assertThat(persisted.getClerkAScore()).isEqualByComparingTo(ZERO_SCORE);
|
||||
assertThat(persisted.getClerkBScore()).isEqualByComparingTo(ZERO_SCORE);
|
||||
assertThat(persisted.getClerkAOrderCount()).isEqualTo(ZERO_COUNT);
|
||||
assertThat(persisted.getClerkBOrderCount()).isEqualTo(ZERO_COUNT);
|
||||
assertThat(persisted.getWinnerClerkId()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void finishPkShouldAssignWinnerForClerkA() throws Exception {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
PlayClerkPkEntity pk = buildPk(ClerkPkEnum.IN_PROGRESS.name(),
|
||||
now.minusMinutes(MINUTES_BEFORE_START),
|
||||
now.minusMinutes(MINUTES_PAST_END),
|
||||
SETTLED_FALSE);
|
||||
|
||||
String scoreKey = PkRedisKeyConstants.scoreKey(pk.getId());
|
||||
stringRedisTemplate.opsForHash().put(scoreKey, PkRedisKeyConstants.FIELD_CLERK_A_SCORE, SCORE_A.toString());
|
||||
stringRedisTemplate.opsForHash().put(scoreKey, PkRedisKeyConstants.FIELD_CLERK_B_SCORE, SCORE_B.toString());
|
||||
|
||||
mockMvc.perform(post(buildFinishPath(pk.getId()))
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.SUCCESS.getCode()));
|
||||
|
||||
PlayClerkPkEntity persisted = loadPk(pk.getId());
|
||||
assertThat(persisted.getWinnerClerkId()).isEqualTo(pk.getClerkA());
|
||||
}
|
||||
|
||||
@Test
|
||||
void finishPkShouldAssignWinnerForClerkB() throws Exception {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
PlayClerkPkEntity pk = buildPk(ClerkPkEnum.IN_PROGRESS.name(),
|
||||
now.minusMinutes(MINUTES_BEFORE_START),
|
||||
now.minusMinutes(MINUTES_PAST_END),
|
||||
SETTLED_FALSE);
|
||||
|
||||
String scoreKey = PkRedisKeyConstants.scoreKey(pk.getId());
|
||||
stringRedisTemplate.opsForHash().put(scoreKey, PkRedisKeyConstants.FIELD_CLERK_A_SCORE, SCORE_B.toString());
|
||||
stringRedisTemplate.opsForHash().put(scoreKey, PkRedisKeyConstants.FIELD_CLERK_B_SCORE, SCORE_A.toString());
|
||||
|
||||
mockMvc.perform(post(buildFinishPath(pk.getId()))
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.SUCCESS.getCode()));
|
||||
|
||||
PlayClerkPkEntity persisted = loadPk(pk.getId());
|
||||
assertThat(persisted.getWinnerClerkId()).isEqualTo(pk.getClerkB());
|
||||
}
|
||||
|
||||
@Test
|
||||
void finishPkShouldKeepWinnerNullOnTie() throws Exception {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
PlayClerkPkEntity pk = buildPk(ClerkPkEnum.IN_PROGRESS.name(),
|
||||
now.minusMinutes(MINUTES_BEFORE_START),
|
||||
now.minusMinutes(MINUTES_BEFORE_START),
|
||||
SETTLED_FALSE);
|
||||
|
||||
String scoreKey = PkRedisKeyConstants.scoreKey(pk.getId());
|
||||
stringRedisTemplate.opsForHash().put(scoreKey, PkRedisKeyConstants.FIELD_CLERK_A_SCORE, TIE_SCORE.toString());
|
||||
stringRedisTemplate.opsForHash().put(scoreKey, PkRedisKeyConstants.FIELD_CLERK_B_SCORE, TIE_SCORE.toString());
|
||||
|
||||
mockMvc.perform(post(buildFinishPath(pk.getId()))
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.SUCCESS.getCode()));
|
||||
|
||||
PlayClerkPkEntity persisted = loadPk(pk.getId());
|
||||
assertThat(persisted.getWinnerClerkId()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void scoreboardShouldReturnRedisValues() throws Exception {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
PlayClerkPkEntity pk = buildPk(ClerkPkEnum.IN_PROGRESS.name(),
|
||||
now.minusMinutes(MINUTES_BEFORE_START),
|
||||
now.plusMinutes(MINUTES_AFTER_START),
|
||||
SETTLED_FALSE);
|
||||
|
||||
String scoreKey = PkRedisKeyConstants.scoreKey(pk.getId());
|
||||
stringRedisTemplate.opsForHash().put(scoreKey, PkRedisKeyConstants.FIELD_CLERK_A_SCORE, SCORE_A.toString());
|
||||
stringRedisTemplate.opsForHash().put(scoreKey, PkRedisKeyConstants.FIELD_CLERK_B_SCORE, SCORE_B.toString());
|
||||
stringRedisTemplate.opsForHash().put(scoreKey,
|
||||
PkRedisKeyConstants.FIELD_CLERK_A_ORDER_COUNT, String.valueOf(ORDER_COUNT_A));
|
||||
stringRedisTemplate.opsForHash().put(scoreKey,
|
||||
PkRedisKeyConstants.FIELD_CLERK_B_ORDER_COUNT, String.valueOf(ORDER_COUNT_B));
|
||||
|
||||
mockMvc.perform(get(buildScoreboardPath(pk.getId()))
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.SUCCESS.getCode()))
|
||||
.andExpect(jsonPath("$.data.clerkAScore").value(SCORE_A.doubleValue()))
|
||||
.andExpect(jsonPath("$.data.clerkBScore").value(SCORE_B.doubleValue()))
|
||||
.andExpect(jsonPath("$.data.clerkAOrderCount").value(ORDER_COUNT_A))
|
||||
.andExpect(jsonPath("$.data.clerkBOrderCount").value(ORDER_COUNT_B));
|
||||
}
|
||||
|
||||
@Test
|
||||
void scoreboardShouldReturnZeroWhenRedisEmpty() throws Exception {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
PlayClerkPkEntity pk = buildPk(ClerkPkEnum.IN_PROGRESS.name(),
|
||||
now.minusMinutes(MINUTES_BEFORE_START),
|
||||
now.plusMinutes(MINUTES_AFTER_START),
|
||||
SETTLED_FALSE);
|
||||
|
||||
mockMvc.perform(get(buildScoreboardPath(pk.getId()))
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.SUCCESS.getCode()))
|
||||
.andExpect(jsonPath("$.data.clerkAScore").value(ZERO_VALUE))
|
||||
.andExpect(jsonPath("$.data.clerkBScore").value(ZERO_VALUE))
|
||||
.andExpect(jsonPath("$.data.clerkAOrderCount").value(ZERO_COUNT))
|
||||
.andExpect(jsonPath("$.data.clerkBOrderCount").value(ZERO_COUNT));
|
||||
}
|
||||
|
||||
@Test
|
||||
void scoreboardShouldReturnZeroRemainingSecondsWhenEnded() throws Exception {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
PlayClerkPkEntity pk = buildPk(ClerkPkEnum.IN_PROGRESS.name(),
|
||||
now.minusMinutes(MINUTES_BEFORE_START),
|
||||
now.minusMinutes(MINUTES_PAST_END),
|
||||
SETTLED_FALSE);
|
||||
|
||||
mockMvc.perform(get(buildScoreboardPath(pk.getId()))
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.SUCCESS.getCode()))
|
||||
.andExpect(jsonPath("$.data.remainingSeconds").value(REMAINING_SECONDS_ZERO));
|
||||
}
|
||||
|
||||
@Test
|
||||
void scoreboardShouldFailWhenMissingPk() throws Exception {
|
||||
mockMvc.perform(get(buildScoreboardPath(IdUtils.getUuid()))
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.FAILED.getCode()))
|
||||
.andExpect(jsonPath("$.message").value(MSG_PK_NOT_FOUND));
|
||||
}
|
||||
|
||||
private PlayClerkPkEntity buildPk(String status, LocalDateTime beginTime, LocalDateTime endTime, int settled) {
|
||||
String pkId = IdUtils.getUuid();
|
||||
String clerkAId = newClerkId();
|
||||
String clerkBId = newClerkId();
|
||||
PlayClerkPkEntity pk = new PlayClerkPkEntity();
|
||||
pk.setId(pkId);
|
||||
pk.setTenantId(DEFAULT_TENANT);
|
||||
pk.setClerkA(clerkAId);
|
||||
pk.setClerkB(clerkBId);
|
||||
pk.setPkBeginTime(Date.from(beginTime.atZone(ZoneId.systemDefault()).toInstant()));
|
||||
pk.setPkEndTime(Date.from(endTime.atZone(ZoneId.systemDefault()).toInstant()));
|
||||
pk.setStatus(status);
|
||||
pk.setSettled(settled);
|
||||
SecurityUtils.setTenantId(DEFAULT_TENANT);
|
||||
clerkPkService.save(pk);
|
||||
return pk;
|
||||
}
|
||||
|
||||
private PlayClerkPkEntity createPkForClerks(String clerkAId, String clerkBId, LocalDateTime beginTime,
|
||||
LocalDateTime endTime, String status) {
|
||||
PlayClerkPkEntity pk = new PlayClerkPkEntity();
|
||||
pk.setId(IdUtils.getUuid());
|
||||
pk.setTenantId(DEFAULT_TENANT);
|
||||
pk.setClerkA(clerkAId);
|
||||
pk.setClerkB(clerkBId);
|
||||
pk.setPkBeginTime(Date.from(beginTime.atZone(ZoneId.systemDefault()).toInstant()));
|
||||
pk.setPkEndTime(Date.from(endTime.atZone(ZoneId.systemDefault()).toInstant()));
|
||||
pk.setStatus(status);
|
||||
pk.setSettled(SETTLED_FALSE);
|
||||
SecurityUtils.setTenantId(DEFAULT_TENANT);
|
||||
clerkPkService.save(pk);
|
||||
return pk;
|
||||
}
|
||||
|
||||
private PlayClerkPkEntity loadPk(String pkId) {
|
||||
SecurityUtils.setTenantId(DEFAULT_TENANT);
|
||||
PlayClerkPkEntity persisted = clerkPkService.selectPlayClerkPkById(pkId);
|
||||
assertThat(persisted).isNotNull();
|
||||
return persisted;
|
||||
}
|
||||
|
||||
private static String newClerkId() {
|
||||
return CLERK_PREFIX + IdUtils.getUuid();
|
||||
}
|
||||
|
||||
private static String buildStartPath(String pkId) {
|
||||
return "/play/pk/" + pkId + "/start";
|
||||
}
|
||||
|
||||
private static String buildFinishPath(String pkId) {
|
||||
return "/play/pk/" + pkId + "/finish";
|
||||
}
|
||||
|
||||
private static String buildScoreboardPath(String pkId) {
|
||||
return "/play/pk/" + pkId + "/scoreboard";
|
||||
}
|
||||
|
||||
private static String buildForceStartPath() {
|
||||
return "/play/pk/force-start";
|
||||
}
|
||||
|
||||
private static String buildForceStartBody(String clerkAId, String clerkBId, int durationMinutes) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append("{");
|
||||
builder.append("\"clerkAId\":\"").append(clerkAId).append("\",");
|
||||
builder.append("\"clerkBId\":\"").append(clerkBId).append("\",");
|
||||
builder.append("\"durationMinutes\":").append(durationMinutes);
|
||||
builder.append("}");
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private static String extractDataAsText(MvcResult result) throws Exception {
|
||||
JsonNode root = OBJECT_MAPPER.readTree(result.getResponse().getContentAsString());
|
||||
return root.path("data").asText();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,583 @@
|
||||
package com.starry.admin.modules.pk;
|
||||
|
||||
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.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.starry.admin.api.AbstractApiTest;
|
||||
import com.starry.admin.modules.clerk.module.entity.ClerkPkEnum;
|
||||
import com.starry.admin.modules.clerk.module.entity.PlayClerkPkEntity;
|
||||
import com.starry.admin.modules.clerk.service.IPlayClerkPkService;
|
||||
import com.starry.admin.modules.pk.setting.constants.PkSettingApiConstants;
|
||||
import com.starry.admin.modules.pk.setting.enums.PkRecurrenceType;
|
||||
import com.starry.admin.modules.pk.setting.enums.PkSettingErrorCode;
|
||||
import com.starry.admin.modules.pk.setting.enums.PkSettingStatus;
|
||||
import com.starry.admin.utils.SecurityUtils;
|
||||
import com.starry.common.result.ResultCodeEnum;
|
||||
import com.starry.common.utils.IdUtils;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import javax.annotation.Resource;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.web.servlet.MvcResult;
|
||||
|
||||
class PlayClerkPkSettingApiTest extends AbstractApiTest {
|
||||
|
||||
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
|
||||
private static final String SETTING_NAME = "PK排期测试";
|
||||
private static final String START_TIME_OF_DAY = "08:00:00";
|
||||
private static final int DURATION_MINUTES = 30;
|
||||
private static final String EFFECTIVE_START_DATE = "2025-01-01";
|
||||
private static final String EFFECTIVE_END_DATE = "2025-01-03";
|
||||
private static final String TIMEZONE = "Asia/Shanghai";
|
||||
private static final int EXPECTED_GENERATED_COUNT = 3;
|
||||
private static final int EXPECTED_ONCE_COUNT = 1;
|
||||
private static final int EXPECTED_ZERO_COUNT = 0;
|
||||
private static final int EXPECTED_BULK_COUNT = 2;
|
||||
private static final int INVALID_DAY_OF_MONTH = 32;
|
||||
private static final int INVALID_MONTH_OF_YEAR = 13;
|
||||
private static final int MONTH_OF_YEAR_JANUARY = 1;
|
||||
private static final int DAY_OF_MONTH_FIRST = 1;
|
||||
private static final int DAY_OF_MONTH_THIRTY_FIRST = 31;
|
||||
private static final int SETTLED_FALSE = 0;
|
||||
private static final String INVALID_TIMEZONE = "Invalid/Zone";
|
||||
private static final String EARLY_END_DATE = "2024-12-31";
|
||||
private static final String LONG_RANGE_START_DATE = "2025-01-01";
|
||||
private static final String LONG_RANGE_END_DATE = "2029-01-01";
|
||||
private static final String MONTHLY_SHORT_START_DATE = "2025-04-01";
|
||||
private static final String MONTHLY_SHORT_END_DATE = "2025-04-30";
|
||||
private static final String CONFLICT_START_DATE = "2025-01-01";
|
||||
private static final String CONFLICT_END_DATE = "2025-01-02";
|
||||
private static final String UPDATED_NAME = "PK排期更新";
|
||||
|
||||
@Resource
|
||||
private IPlayClerkPkService clerkPkService;
|
||||
|
||||
@Test
|
||||
void pkSettingFlowShouldWorkEndToEnd() throws Exception {
|
||||
String clerkAId = "clerk-" + IdUtils.getUuid();
|
||||
String clerkBId = "clerk-" + IdUtils.getUuid();
|
||||
String createRequestBody = buildRequestBody(SETTING_NAME, PkRecurrenceType.DAILY,
|
||||
null, null, null, PkSettingStatus.ENABLED, clerkAId, clerkBId, EFFECTIVE_START_DATE,
|
||||
EFFECTIVE_END_DATE, TIMEZONE);
|
||||
String updateRequestBody = buildRequestBody(UPDATED_NAME, PkRecurrenceType.DAILY,
|
||||
null, null, null, PkSettingStatus.DISABLED, clerkAId, clerkBId, EFFECTIVE_START_DATE,
|
||||
EFFECTIVE_END_DATE, TIMEZONE);
|
||||
String createPath = PkSettingApiConstants.BASE_PATH + PkSettingApiConstants.CREATE_PATH;
|
||||
MvcResult createResult = mockMvc.perform(post(createPath)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(createRequestBody)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.SUCCESS.getCode()))
|
||||
.andReturn();
|
||||
|
||||
String settingId = extractDataAsText(createResult);
|
||||
|
||||
String listPath = PkSettingApiConstants.BASE_PATH + PkSettingApiConstants.LIST_PATH;
|
||||
mockMvc.perform(get(listPath)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.SUCCESS.getCode()))
|
||||
.andExpect(jsonPath("$.data").isArray());
|
||||
|
||||
String detailPath = PkSettingApiConstants.BASE_PATH + "/"
|
||||
+ settingId;
|
||||
mockMvc.perform(get(detailPath)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.SUCCESS.getCode()))
|
||||
.andExpect(jsonPath("$.data.id").value(settingId))
|
||||
.andExpect(jsonPath("$.data.name").value(SETTING_NAME));
|
||||
|
||||
String updatePath = PkSettingApiConstants.BASE_PATH + "/update/" + settingId;
|
||||
mockMvc.perform(post(updatePath)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(updateRequestBody)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.SUCCESS.getCode()));
|
||||
|
||||
mockMvc.perform(get(detailPath)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.SUCCESS.getCode()))
|
||||
.andExpect(jsonPath("$.data.name").value(UPDATED_NAME));
|
||||
|
||||
String enablePath = PkSettingApiConstants.BASE_PATH + "/" + settingId + "/enable";
|
||||
mockMvc.perform(post(enablePath)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.SUCCESS.getCode()));
|
||||
|
||||
SecurityUtils.setTenantId(DEFAULT_TENANT);
|
||||
List<PlayClerkPkEntity> generated = clerkPkService.list(
|
||||
Wrappers.<PlayClerkPkEntity>lambdaQuery().eq(PlayClerkPkEntity::getSettingId, settingId));
|
||||
assertThat(generated).hasSize(EXPECTED_GENERATED_COUNT);
|
||||
}
|
||||
|
||||
@Test
|
||||
void createSettingShouldRejectMissingWeeklyFields() throws Exception {
|
||||
String createPath = PkSettingApiConstants.BASE_PATH + PkSettingApiConstants.CREATE_PATH;
|
||||
String clerkAId = "clerk-" + IdUtils.getUuid();
|
||||
String clerkBId = "clerk-" + IdUtils.getUuid();
|
||||
String invalidWeeklyRequestBody = buildRequestBody("无效周排期", PkRecurrenceType.WEEKLY,
|
||||
null, null, null, PkSettingStatus.ENABLED, clerkAId, clerkBId, EFFECTIVE_START_DATE,
|
||||
EFFECTIVE_END_DATE, TIMEZONE);
|
||||
mockMvc.perform(post(createPath)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(invalidWeeklyRequestBody)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.FAILED.getCode()))
|
||||
.andExpect(jsonPath("$.message").value(PkSettingErrorCode.RECURRENCE_INVALID.getMessage()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void createSettingShouldRejectInvalidMonthlyDay() throws Exception {
|
||||
String createPath = PkSettingApiConstants.BASE_PATH + PkSettingApiConstants.CREATE_PATH;
|
||||
String clerkAId = "clerk-" + IdUtils.getUuid();
|
||||
String clerkBId = "clerk-" + IdUtils.getUuid();
|
||||
String invalidMonthlyRequestBody = buildRequestBody("无效月排期", PkRecurrenceType.MONTHLY,
|
||||
null, INVALID_DAY_OF_MONTH, null, PkSettingStatus.ENABLED, clerkAId, clerkBId, EFFECTIVE_START_DATE,
|
||||
EFFECTIVE_END_DATE, TIMEZONE);
|
||||
mockMvc.perform(post(createPath)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(invalidMonthlyRequestBody)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.FAILED.getCode()))
|
||||
.andExpect(jsonPath("$.message").value(PkSettingErrorCode.RECURRENCE_INVALID.getMessage()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateShouldRejectClerkConflict() throws Exception {
|
||||
String createPath = PkSettingApiConstants.BASE_PATH + PkSettingApiConstants.CREATE_PATH;
|
||||
String clerkAId = "clerk-" + IdUtils.getUuid();
|
||||
String clerkBId = "clerk-" + IdUtils.getUuid();
|
||||
String createRequestBody = buildRequestBody(SETTING_NAME, PkRecurrenceType.DAILY,
|
||||
null, null, null, PkSettingStatus.ENABLED, clerkAId, clerkBId, EFFECTIVE_START_DATE,
|
||||
EFFECTIVE_END_DATE, TIMEZONE);
|
||||
String conflictRequestBody = buildRequestBody("冲突排期", PkRecurrenceType.DAILY,
|
||||
null, null, null, PkSettingStatus.ENABLED, clerkAId, clerkBId, EFFECTIVE_START_DATE,
|
||||
EFFECTIVE_END_DATE, TIMEZONE);
|
||||
|
||||
mockMvc.perform(post(createPath)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(createRequestBody)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.SUCCESS.getCode()));
|
||||
|
||||
mockMvc.perform(post(createPath)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(conflictRequestBody)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.FAILED.getCode()))
|
||||
.andExpect(jsonPath("$.message").value(PkSettingErrorCode.CLERK_CONFLICT.getMessage()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void bulkCreateShouldCreateMultipleSettings() throws Exception {
|
||||
String bulkCreatePath = PkSettingApiConstants.BASE_PATH + PkSettingApiConstants.BULK_CREATE_PATH;
|
||||
String clerkAId = "clerk-" + IdUtils.getUuid();
|
||||
String clerkBId = "clerk-" + IdUtils.getUuid();
|
||||
String clerkCId = "clerk-" + IdUtils.getUuid();
|
||||
String clerkDId = "clerk-" + IdUtils.getUuid();
|
||||
String firstRequestBody = buildRequestBody("批量排期A", PkRecurrenceType.DAILY,
|
||||
null, null, null, PkSettingStatus.ENABLED, clerkAId, clerkBId, EFFECTIVE_START_DATE,
|
||||
EFFECTIVE_END_DATE, TIMEZONE);
|
||||
String secondRequestBody = buildRequestBody("批量排期B", PkRecurrenceType.DAILY,
|
||||
null, null, null, PkSettingStatus.ENABLED, clerkCId, clerkDId, EFFECTIVE_START_DATE,
|
||||
EFFECTIVE_END_DATE, TIMEZONE);
|
||||
String bulkRequestBody = buildBulkRequestBody(List.of(firstRequestBody, secondRequestBody));
|
||||
|
||||
MvcResult result = mockMvc.perform(post(bulkCreatePath)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(bulkRequestBody)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.SUCCESS.getCode()))
|
||||
.andReturn();
|
||||
|
||||
JsonNode data = OBJECT_MAPPER.readTree(result.getResponse().getContentAsString()).path("data");
|
||||
assertThat(data.isArray()).isTrue();
|
||||
assertThat(data.size()).isEqualTo(EXPECTED_BULK_COUNT);
|
||||
}
|
||||
|
||||
@Test
|
||||
void bulkCreateShouldRejectDuplicatePairs() throws Exception {
|
||||
String bulkCreatePath = PkSettingApiConstants.BASE_PATH + PkSettingApiConstants.BULK_CREATE_PATH;
|
||||
String clerkAId = "clerk-" + IdUtils.getUuid();
|
||||
String clerkBId = "clerk-" + IdUtils.getUuid();
|
||||
String firstRequestBody = buildRequestBody("批量排期重复A", PkRecurrenceType.DAILY,
|
||||
null, null, null, PkSettingStatus.ENABLED, clerkAId, clerkBId, EFFECTIVE_START_DATE,
|
||||
EFFECTIVE_END_DATE, TIMEZONE);
|
||||
String secondRequestBody = buildRequestBody("批量排期重复B", PkRecurrenceType.DAILY,
|
||||
null, null, null, PkSettingStatus.ENABLED, clerkBId, clerkAId, EFFECTIVE_START_DATE,
|
||||
EFFECTIVE_END_DATE, TIMEZONE);
|
||||
String bulkRequestBody = buildBulkRequestBody(List.of(firstRequestBody, secondRequestBody));
|
||||
|
||||
mockMvc.perform(post(bulkCreatePath)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(bulkRequestBody)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.FAILED.getCode()))
|
||||
.andExpect(jsonPath("$.message").value(PkSettingErrorCode.REQUEST_INVALID.getMessage()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void createSettingShouldRejectMissingMonthlyDay() throws Exception {
|
||||
String createPath = PkSettingApiConstants.BASE_PATH + PkSettingApiConstants.CREATE_PATH;
|
||||
String clerkAId = "clerk-" + IdUtils.getUuid();
|
||||
String clerkBId = "clerk-" + IdUtils.getUuid();
|
||||
String invalidMonthlyRequestBody = buildRequestBody("缺失月日", PkRecurrenceType.MONTHLY,
|
||||
null, null, null, PkSettingStatus.ENABLED, clerkAId, clerkBId, EFFECTIVE_START_DATE,
|
||||
EFFECTIVE_END_DATE, TIMEZONE);
|
||||
mockMvc.perform(post(createPath)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(invalidMonthlyRequestBody)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.FAILED.getCode()))
|
||||
.andExpect(jsonPath("$.message").value(PkSettingErrorCode.RECURRENCE_INVALID.getMessage()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void createSettingShouldRejectMissingYearlyFields() throws Exception {
|
||||
String createPath = PkSettingApiConstants.BASE_PATH + PkSettingApiConstants.CREATE_PATH;
|
||||
String clerkAId = "clerk-" + IdUtils.getUuid();
|
||||
String clerkBId = "clerk-" + IdUtils.getUuid();
|
||||
String invalidYearlyRequestBody = buildRequestBody("缺失年字段", PkRecurrenceType.YEARLY,
|
||||
null, DAY_OF_MONTH_FIRST, null, PkSettingStatus.ENABLED, clerkAId, clerkBId, EFFECTIVE_START_DATE,
|
||||
EFFECTIVE_END_DATE, TIMEZONE);
|
||||
mockMvc.perform(post(createPath)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(invalidYearlyRequestBody)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.FAILED.getCode()))
|
||||
.andExpect(jsonPath("$.message").value(PkSettingErrorCode.RECURRENCE_INVALID.getMessage()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void createSettingShouldRejectInvalidMonthOfYear() throws Exception {
|
||||
String createPath = PkSettingApiConstants.BASE_PATH + PkSettingApiConstants.CREATE_PATH;
|
||||
String clerkAId = "clerk-" + IdUtils.getUuid();
|
||||
String clerkBId = "clerk-" + IdUtils.getUuid();
|
||||
String invalidYearlyRequestBody = buildRequestBody("无效月份", PkRecurrenceType.YEARLY,
|
||||
null, DAY_OF_MONTH_FIRST, INVALID_MONTH_OF_YEAR, PkSettingStatus.ENABLED, clerkAId, clerkBId,
|
||||
EFFECTIVE_START_DATE, EFFECTIVE_END_DATE, TIMEZONE);
|
||||
mockMvc.perform(post(createPath)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(invalidYearlyRequestBody)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.FAILED.getCode()))
|
||||
.andExpect(jsonPath("$.message").value(PkSettingErrorCode.RECURRENCE_INVALID.getMessage()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void createSettingShouldRejectInvalidTimeRange() throws Exception {
|
||||
String createPath = PkSettingApiConstants.BASE_PATH + PkSettingApiConstants.CREATE_PATH;
|
||||
String clerkAId = "clerk-" + IdUtils.getUuid();
|
||||
String clerkBId = "clerk-" + IdUtils.getUuid();
|
||||
String invalidRangeRequestBody = buildRequestBody("无效时间范围", PkRecurrenceType.DAILY,
|
||||
null, null, null, PkSettingStatus.ENABLED, clerkAId, clerkBId, EFFECTIVE_START_DATE,
|
||||
EARLY_END_DATE, TIMEZONE);
|
||||
mockMvc.perform(post(createPath)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(invalidRangeRequestBody)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.FAILED.getCode()))
|
||||
.andExpect(jsonPath("$.message").value(PkSettingErrorCode.TIME_RANGE_INVALID.getMessage()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void createSettingShouldRejectSameClerk() throws Exception {
|
||||
String createPath = PkSettingApiConstants.BASE_PATH + PkSettingApiConstants.CREATE_PATH;
|
||||
String clerkId = "clerk-" + IdUtils.getUuid();
|
||||
String invalidRequestBody = buildRequestBody("同店员", PkRecurrenceType.DAILY,
|
||||
null, null, null, PkSettingStatus.ENABLED, clerkId, clerkId, EFFECTIVE_START_DATE,
|
||||
EFFECTIVE_END_DATE, TIMEZONE);
|
||||
mockMvc.perform(post(createPath)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(invalidRequestBody)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.FAILED.getCode()))
|
||||
.andExpect(jsonPath("$.message").value(PkSettingErrorCode.REQUEST_INVALID.getMessage()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void createSettingShouldRejectInvalidTimezone() throws Exception {
|
||||
String createPath = PkSettingApiConstants.BASE_PATH + PkSettingApiConstants.CREATE_PATH;
|
||||
String clerkAId = "clerk-" + IdUtils.getUuid();
|
||||
String clerkBId = "clerk-" + IdUtils.getUuid();
|
||||
String invalidRequestBody = buildRequestBody("无效时区", PkRecurrenceType.DAILY,
|
||||
null, null, null, PkSettingStatus.ENABLED, clerkAId, clerkBId, EFFECTIVE_START_DATE,
|
||||
EFFECTIVE_END_DATE, INVALID_TIMEZONE);
|
||||
mockMvc.perform(post(createPath)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(invalidRequestBody)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.FAILED.getCode()))
|
||||
.andExpect(jsonPath("$.message").value(PkSettingErrorCode.REQUEST_INVALID.getMessage()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateShouldRespectThreeYearLimit() throws Exception {
|
||||
String clerkAId = "clerk-" + IdUtils.getUuid();
|
||||
String clerkBId = "clerk-" + IdUtils.getUuid();
|
||||
String requestBody = buildRequestBody("三年上限", PkRecurrenceType.YEARLY,
|
||||
null, DAY_OF_MONTH_FIRST, MONTH_OF_YEAR_JANUARY, PkSettingStatus.ENABLED, clerkAId, clerkBId,
|
||||
LONG_RANGE_START_DATE, LONG_RANGE_END_DATE, TIMEZONE);
|
||||
String createPath = PkSettingApiConstants.BASE_PATH + PkSettingApiConstants.CREATE_PATH;
|
||||
String settingId = extractDataAsText(mockMvc.perform(post(createPath)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(requestBody)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.SUCCESS.getCode()))
|
||||
.andReturn());
|
||||
|
||||
SecurityUtils.setTenantId(DEFAULT_TENANT);
|
||||
List<PlayClerkPkEntity> generated = clerkPkService.list(
|
||||
Wrappers.<PlayClerkPkEntity>lambdaQuery().eq(PlayClerkPkEntity::getSettingId, settingId));
|
||||
assertThat(generated).isNotEmpty();
|
||||
LocalDate maxDate = generated.stream()
|
||||
.map(PlayClerkPkEntity::getPkBeginTime)
|
||||
.filter(date -> date != null)
|
||||
.map(date -> date.toInstant().atZone(java.time.ZoneId.systemDefault()).toLocalDate())
|
||||
.max(LocalDate::compareTo)
|
||||
.orElse(LocalDate.parse(LONG_RANGE_START_DATE));
|
||||
LocalDate maxAllowed = LocalDate.parse(LONG_RANGE_START_DATE).plusYears(3);
|
||||
assertThat(maxDate).isBefore(maxAllowed.plusDays(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void monthlyInvalidDayShouldSkipWithoutError() throws Exception {
|
||||
String clerkAId = "clerk-" + IdUtils.getUuid();
|
||||
String clerkBId = "clerk-" + IdUtils.getUuid();
|
||||
String requestBody = buildRequestBody("无效日期跳过", PkRecurrenceType.MONTHLY,
|
||||
null, DAY_OF_MONTH_THIRTY_FIRST, null, PkSettingStatus.ENABLED, clerkAId, clerkBId,
|
||||
MONTHLY_SHORT_START_DATE, MONTHLY_SHORT_END_DATE, TIMEZONE);
|
||||
String createPath = PkSettingApiConstants.BASE_PATH + PkSettingApiConstants.CREATE_PATH;
|
||||
String settingId = extractDataAsText(mockMvc.perform(post(createPath)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(requestBody)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.SUCCESS.getCode()))
|
||||
.andReturn());
|
||||
SecurityUtils.setTenantId(DEFAULT_TENANT);
|
||||
List<PlayClerkPkEntity> generated = clerkPkService.list(
|
||||
Wrappers.<PlayClerkPkEntity>lambdaQuery().eq(PlayClerkPkEntity::getSettingId, settingId));
|
||||
assertThat(generated).hasSize(EXPECTED_ZERO_COUNT);
|
||||
}
|
||||
|
||||
@Test
|
||||
void onceScheduleShouldGenerateSingleInstance() throws Exception {
|
||||
String clerkAId = "clerk-" + IdUtils.getUuid();
|
||||
String clerkBId = "clerk-" + IdUtils.getUuid();
|
||||
String requestBody = buildRequestBody("单次排期", PkRecurrenceType.ONCE,
|
||||
null, null, null, PkSettingStatus.ENABLED, clerkAId, clerkBId, EFFECTIVE_START_DATE,
|
||||
EFFECTIVE_START_DATE, TIMEZONE);
|
||||
String createPath = PkSettingApiConstants.BASE_PATH + PkSettingApiConstants.CREATE_PATH;
|
||||
String settingId = extractDataAsText(mockMvc.perform(post(createPath)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(requestBody)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.SUCCESS.getCode()))
|
||||
.andReturn());
|
||||
SecurityUtils.setTenantId(DEFAULT_TENANT);
|
||||
List<PlayClerkPkEntity> generated = clerkPkService.list(
|
||||
Wrappers.<PlayClerkPkEntity>lambdaQuery().eq(PlayClerkPkEntity::getSettingId, settingId));
|
||||
assertThat(generated).hasSize(EXPECTED_ONCE_COUNT);
|
||||
}
|
||||
|
||||
@Test
|
||||
void disabledSettingShouldNotGenerateInstances() throws Exception {
|
||||
String clerkAId = "clerk-" + IdUtils.getUuid();
|
||||
String clerkBId = "clerk-" + IdUtils.getUuid();
|
||||
String requestBody = buildRequestBody("禁用生成", PkRecurrenceType.DAILY,
|
||||
null, null, null, PkSettingStatus.DISABLED, clerkAId, clerkBId, EFFECTIVE_START_DATE,
|
||||
EFFECTIVE_END_DATE, TIMEZONE);
|
||||
String createPath = PkSettingApiConstants.BASE_PATH + PkSettingApiConstants.CREATE_PATH;
|
||||
String settingId = extractDataAsText(mockMvc.perform(post(createPath)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(requestBody)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.SUCCESS.getCode()))
|
||||
.andReturn());
|
||||
SecurityUtils.setTenantId(DEFAULT_TENANT);
|
||||
List<PlayClerkPkEntity> generated = clerkPkService.list(
|
||||
Wrappers.<PlayClerkPkEntity>lambdaQuery().eq(PlayClerkPkEntity::getSettingId, settingId));
|
||||
assertThat(generated).hasSize(EXPECTED_ZERO_COUNT);
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateShouldRollbackWhenConflictDetected() throws Exception {
|
||||
String clerkAId = "clerk-" + IdUtils.getUuid();
|
||||
String clerkBId = "clerk-" + IdUtils.getUuid();
|
||||
String requestBody = buildRequestBody("冲突回滚", PkRecurrenceType.DAILY,
|
||||
null, null, null, PkSettingStatus.DISABLED, clerkAId, clerkBId, CONFLICT_START_DATE,
|
||||
CONFLICT_END_DATE, TIMEZONE);
|
||||
String createPath = PkSettingApiConstants.BASE_PATH + PkSettingApiConstants.CREATE_PATH;
|
||||
String settingId = extractDataAsText(mockMvc.perform(post(createPath)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(requestBody)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.SUCCESS.getCode()))
|
||||
.andReturn());
|
||||
|
||||
LocalDateTime conflictStart = LocalDateTime.of(LocalDate.parse(CONFLICT_END_DATE),
|
||||
LocalTime.parse(START_TIME_OF_DAY));
|
||||
LocalDateTime conflictEnd = conflictStart.plusMinutes(DURATION_MINUTES);
|
||||
ZoneId zoneId = ZoneId.of(TIMEZONE);
|
||||
PlayClerkPkEntity conflictPk = new PlayClerkPkEntity();
|
||||
conflictPk.setId(IdUtils.getUuid());
|
||||
conflictPk.setTenantId(DEFAULT_TENANT);
|
||||
conflictPk.setClerkA(clerkAId);
|
||||
conflictPk.setClerkB(clerkBId);
|
||||
conflictPk.setPkBeginTime(Date.from(conflictStart.atZone(zoneId).toInstant()));
|
||||
conflictPk.setPkEndTime(Date.from(conflictEnd.atZone(zoneId).toInstant()));
|
||||
conflictPk.setStatus(ClerkPkEnum.TO_BE_STARTED.name());
|
||||
conflictPk.setSettled(SETTLED_FALSE);
|
||||
SecurityUtils.setTenantId(DEFAULT_TENANT);
|
||||
clerkPkService.save(conflictPk);
|
||||
|
||||
String enablePath = PkSettingApiConstants.BASE_PATH + "/" + settingId + "/enable";
|
||||
mockMvc.perform(post(enablePath)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.FAILED.getCode()))
|
||||
.andExpect(jsonPath("$.message").value(PkSettingErrorCode.CLERK_CONFLICT.getMessage()));
|
||||
|
||||
SecurityUtils.setTenantId(DEFAULT_TENANT);
|
||||
List<PlayClerkPkEntity> generated = clerkPkService.list(
|
||||
Wrappers.<PlayClerkPkEntity>lambdaQuery().eq(PlayClerkPkEntity::getSettingId, settingId));
|
||||
assertThat(generated).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void listShouldExcludeOtherTenantSettings() throws Exception {
|
||||
String tenantA = "tenant-" + IdUtils.getUuid();
|
||||
String tenantB = "tenant-" + IdUtils.getUuid();
|
||||
String clerkAId = "clerk-" + IdUtils.getUuid();
|
||||
String clerkBId = "clerk-" + IdUtils.getUuid();
|
||||
String requestBody = buildRequestBody("多租户排期", PkRecurrenceType.DAILY,
|
||||
null, null, null, PkSettingStatus.ENABLED, clerkAId, clerkBId, EFFECTIVE_START_DATE,
|
||||
EFFECTIVE_END_DATE, TIMEZONE);
|
||||
String createPath = PkSettingApiConstants.BASE_PATH + PkSettingApiConstants.CREATE_PATH;
|
||||
mockMvc.perform(post(createPath)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(requestBody)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, tenantB))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.SUCCESS.getCode()));
|
||||
|
||||
String listPath = PkSettingApiConstants.BASE_PATH + PkSettingApiConstants.LIST_PATH;
|
||||
MvcResult listResult = mockMvc.perform(get(listPath)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, tenantA))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.SUCCESS.getCode()))
|
||||
.andReturn();
|
||||
JsonNode data = OBJECT_MAPPER.readTree(listResult.getResponse().getContentAsString()).path("data");
|
||||
if (data.isArray()) {
|
||||
for (JsonNode node : data) {
|
||||
String tenantId = node.path("tenantId").asText();
|
||||
assertThat(tenantId).isNotEqualTo(tenantB);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String extractDataAsText(MvcResult result) throws Exception {
|
||||
JsonNode root = OBJECT_MAPPER.readTree(result.getResponse().getContentAsString());
|
||||
return root.path("data").asText();
|
||||
}
|
||||
|
||||
private static String buildRequestBody(String name, PkRecurrenceType recurrenceType, String dayOfWeek,
|
||||
Integer dayOfMonth, Integer monthOfYear, PkSettingStatus status, String clerkAId, String clerkBId,
|
||||
String effectiveStartDate, String effectiveEndDate, String timezone) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append("{");
|
||||
builder.append("\"name\":\"").append(name).append("\",");
|
||||
builder.append("\"recurrenceType\":\"").append(recurrenceType.getValue()).append("\",");
|
||||
if (dayOfWeek != null) {
|
||||
builder.append("\"dayOfWeek\":\"").append(dayOfWeek).append("\",");
|
||||
}
|
||||
if (dayOfMonth != null) {
|
||||
builder.append("\"dayOfMonth\":").append(dayOfMonth).append(",");
|
||||
}
|
||||
if (monthOfYear != null) {
|
||||
builder.append("\"monthOfYear\":").append(monthOfYear).append(",");
|
||||
}
|
||||
builder.append("\"startTimeOfDay\":\"").append(START_TIME_OF_DAY).append("\",");
|
||||
builder.append("\"durationMinutes\":").append(DURATION_MINUTES).append(",");
|
||||
builder.append("\"effectiveStartDate\":\"").append(effectiveStartDate).append("\",");
|
||||
builder.append("\"effectiveEndDate\":\"").append(effectiveEndDate).append("\",");
|
||||
builder.append("\"timezone\":\"").append(timezone).append("\",");
|
||||
builder.append("\"clerkAId\":\"").append(clerkAId).append("\",");
|
||||
builder.append("\"clerkBId\":\"").append(clerkBId).append("\",");
|
||||
builder.append("\"status\":\"").append(status.getValue()).append("\"");
|
||||
builder.append("}");
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private static String buildBulkRequestBody(List<String> settings) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append("{\"settings\":[");
|
||||
for (int i = 0; i < settings.size(); i++) {
|
||||
builder.append(settings.get(i));
|
||||
if (i < settings.size() - 1) {
|
||||
builder.append(",");
|
||||
}
|
||||
}
|
||||
builder.append("]}");
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,642 @@
|
||||
package com.starry.admin.modules.pk;
|
||||
|
||||
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.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.starry.admin.api.AbstractApiTest;
|
||||
import com.starry.admin.common.exception.handler.GlobalExceptionHandler;
|
||||
import com.starry.admin.modules.clerk.module.entity.ClerkPkEnum;
|
||||
import com.starry.admin.modules.clerk.module.entity.PlayClerkPkEntity;
|
||||
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;
|
||||
import com.starry.admin.modules.clerk.service.IPlayClerkPkService;
|
||||
import com.starry.admin.modules.clerk.service.IPlayClerkUserInfoService;
|
||||
import com.starry.admin.modules.custom.module.entity.PlayCustomUserInfoEntity;
|
||||
import com.starry.admin.modules.custom.service.IPlayCustomUserInfoService;
|
||||
import com.starry.admin.modules.order.module.constant.OrderConstant;
|
||||
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
|
||||
import com.starry.admin.modules.order.service.IPlayOrderInfoService;
|
||||
import com.starry.admin.modules.pk.enums.PkWxState;
|
||||
import com.starry.admin.modules.pk.redis.PkRedisKeyConstants;
|
||||
import com.starry.admin.utils.SecurityUtils;
|
||||
import com.starry.common.result.ResultCodeEnum;
|
||||
import com.starry.common.utils.IdUtils;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.Date;
|
||||
import javax.annotation.Resource;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.test.web.servlet.MvcResult;
|
||||
|
||||
class WxPkApiTest extends AbstractApiTest {
|
||||
|
||||
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
|
||||
private static final String CLERK_PREFIX = "wx-pk-clerk-";
|
||||
private static final String CUSTOM_PREFIX = "wx-pk-custom-";
|
||||
private static final String OPENID_PREFIX = "openid-";
|
||||
private static final String CLERK_NAME_PREFIX = "店员-";
|
||||
private static final String CUSTOM_NAME_PREFIX = "贡献者-";
|
||||
private static final String PK_PREFIX = "wx-pk-";
|
||||
private static final String PARAM_CLERK_ID = "clerkId";
|
||||
private static final String PARAM_ID = "id";
|
||||
private static final String PARAM_LIMIT = "limit";
|
||||
private static final String MSG_PARAM_INVALID = GlobalExceptionHandler.PARAMETER_FORMAT_ERROR;
|
||||
private static final String PARAM_PAGE_NUM = "pageNum";
|
||||
private static final String PARAM_PAGE_SIZE = "pageSize";
|
||||
private static final int MINUTES_BEFORE_START = 5;
|
||||
private static final int MINUTES_AFTER_START = 30;
|
||||
private static final int MINUTES_FUTURE_START = 10;
|
||||
private static final int MINUTES_ORDER_OFFSET = 2;
|
||||
private static final int HISTORY_FIRST_DAY_OFFSET = 2;
|
||||
private static final int HISTORY_SECOND_DAY_OFFSET = 1;
|
||||
private static final int FUTURE_FIRST_OFFSET_MINUTES = 30;
|
||||
private static final int FUTURE_SECOND_OFFSET_MINUTES = 60;
|
||||
private static final int PAST_OFFSET_MINUTES = 10;
|
||||
private static final int FUTURE_THIRD_OFFSET_MINUTES = 90;
|
||||
private static final int DEFAULT_LIMIT = 3;
|
||||
private static final int LIMIT_ZERO = 0;
|
||||
private static final int LIMIT_NEGATIVE = -1;
|
||||
private static final int LIMIT_LARGE = 100;
|
||||
private static final int SCHEDULE_LIMIT = 2;
|
||||
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 int SETTLED_FALSE = 0;
|
||||
private static final int SETTLED_TRUE = 1;
|
||||
private static final double ZSET_SCORE_PAST = 1D;
|
||||
private static final BigDecimal AMOUNT_HIGH = new BigDecimal("120.00");
|
||||
private static final BigDecimal AMOUNT_MEDIUM = new BigDecimal("100.00");
|
||||
private static final BigDecimal AMOUNT_LOW = new BigDecimal("50.00");
|
||||
private static final int EXPECTED_HISTORY_COUNT = 2;
|
||||
|
||||
@Resource
|
||||
private IPlayClerkPkService clerkPkService;
|
||||
|
||||
@Resource
|
||||
private StringRedisTemplate stringRedisTemplate;
|
||||
|
||||
@Resource
|
||||
private IPlayClerkUserInfoService clerkUserInfoService;
|
||||
|
||||
@Resource
|
||||
private IPlayCustomUserInfoService customUserInfoService;
|
||||
|
||||
@Resource
|
||||
private IPlayOrderInfoService orderInfoService;
|
||||
|
||||
@BeforeEach
|
||||
void clearRedis() {
|
||||
if (stringRedisTemplate.getConnectionFactory() == null) {
|
||||
return;
|
||||
}
|
||||
stringRedisTemplate.getConnectionFactory().getConnection().flushDb();
|
||||
}
|
||||
|
||||
@Test
|
||||
void clerkLiveShouldRejectWhenClerkIdMissing() throws Exception {
|
||||
mockMvc.perform(get("/wx/pk/clerk/live")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.FAILED.getCode()))
|
||||
.andExpect(jsonPath("$.message").value(MSG_PARAM_INVALID));
|
||||
}
|
||||
|
||||
@Test
|
||||
void clerkLiveShouldReturnInactiveWhenNoActivePk() throws Exception {
|
||||
String clerkId = newClerkId();
|
||||
mockMvc.perform(get("/wx/pk/clerk/live")
|
||||
.param(PARAM_CLERK_ID, clerkId)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.data.state").value(PkWxState.INACTIVE.getValue()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void clerkLiveShouldReturnActiveWhenInProgress() throws Exception {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
String pkId = newPkId();
|
||||
String clerkAId = newClerkId();
|
||||
String clerkBId = newClerkId();
|
||||
|
||||
SecurityUtils.setTenantId(DEFAULT_TENANT);
|
||||
PlayClerkPkEntity pk = buildPk(pkId, clerkAId, clerkBId,
|
||||
now.minusMinutes(MINUTES_BEFORE_START),
|
||||
now.plusMinutes(MINUTES_AFTER_START),
|
||||
ClerkPkEnum.IN_PROGRESS.name());
|
||||
clerkPkService.save(pk);
|
||||
|
||||
MvcResult result = mockMvc.perform(get("/wx/pk/clerk/live")
|
||||
.param(PARAM_CLERK_ID, clerkAId)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.data.state").value(PkWxState.ACTIVE.getValue()))
|
||||
.andExpect(jsonPath("$.data.id").value(pkId))
|
||||
.andReturn();
|
||||
|
||||
JsonNode data = extractData(result);
|
||||
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);
|
||||
}
|
||||
|
||||
@Test
|
||||
void clerkLiveShouldReturnInactiveWhenToBeStarted() throws Exception {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
String clerkId = newClerkId();
|
||||
PlayClerkPkEntity pk = buildPk(newPkId(), clerkId, newClerkId(),
|
||||
now.plusMinutes(MINUTES_FUTURE_START),
|
||||
now.plusMinutes(MINUTES_AFTER_START),
|
||||
ClerkPkEnum.TO_BE_STARTED.name());
|
||||
SecurityUtils.setTenantId(DEFAULT_TENANT);
|
||||
clerkPkService.save(pk);
|
||||
|
||||
mockMvc.perform(get("/wx/pk/clerk/live")
|
||||
.param(PARAM_CLERK_ID, clerkId)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.data.state").value(PkWxState.INACTIVE.getValue()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void upcomingShouldReturnInactiveWhenRedisEmpty() throws Exception {
|
||||
mockMvc.perform(get("/wx/pk/upcoming")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.data.state").value(PkWxState.INACTIVE.getValue()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void upcomingShouldReturnUpcomingWhenRedisHasPk() throws Exception {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
String pkId = newPkId();
|
||||
PlayClerkPkEntity pk = buildPk(pkId, newClerkId(), newClerkId(),
|
||||
now.plusMinutes(MINUTES_FUTURE_START),
|
||||
now.plusMinutes(MINUTES_AFTER_START),
|
||||
ClerkPkEnum.TO_BE_STARTED.name());
|
||||
SecurityUtils.setTenantId(DEFAULT_TENANT);
|
||||
clerkPkService.save(pk);
|
||||
|
||||
String key = PkRedisKeyConstants.upcomingKey(DEFAULT_TENANT);
|
||||
long score = pk.getPkBeginTime().toInstant().getEpochSecond();
|
||||
stringRedisTemplate.opsForZSet().add(key, pkId, score);
|
||||
|
||||
mockMvc.perform(get("/wx/pk/upcoming")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.data.state").value(PkWxState.UPCOMING.getValue()))
|
||||
.andExpect(jsonPath("$.data.id").value(pkId));
|
||||
}
|
||||
|
||||
@Test
|
||||
void upcomingShouldReturnInactiveWhenPkMissing() throws Exception {
|
||||
String key = PkRedisKeyConstants.upcomingKey(DEFAULT_TENANT);
|
||||
String pkId = newPkId();
|
||||
stringRedisTemplate.opsForZSet().add(key, pkId, ZSET_SCORE_PAST);
|
||||
|
||||
mockMvc.perform(get("/wx/pk/upcoming")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.data.state").value(PkWxState.INACTIVE.getValue()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void detailShouldReturnInactiveWhenNotFound() throws Exception {
|
||||
mockMvc.perform(get("/wx/pk/detail")
|
||||
.param(PARAM_ID, newPkId())
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.data.state").value(PkWxState.INACTIVE.getValue()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void detailShouldReturnActiveWhenInProgress() throws Exception {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
String pkId = newPkId();
|
||||
PlayClerkPkEntity pk = buildPk(pkId, newClerkId(), newClerkId(),
|
||||
now.minusMinutes(MINUTES_BEFORE_START),
|
||||
now.plusMinutes(MINUTES_AFTER_START),
|
||||
ClerkPkEnum.IN_PROGRESS.name());
|
||||
SecurityUtils.setTenantId(DEFAULT_TENANT);
|
||||
clerkPkService.save(pk);
|
||||
|
||||
MvcResult result = mockMvc.perform(get("/wx/pk/detail")
|
||||
.param(PARAM_ID, pkId)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.data.state").value(PkWxState.ACTIVE.getValue()))
|
||||
.andExpect(jsonPath("$.data.id").value(pkId))
|
||||
.andReturn();
|
||||
|
||||
JsonNode data = extractData(result);
|
||||
assertThat(data.get("remainingSeconds").asLong()).isGreaterThanOrEqualTo(REMAINING_SECONDS_MIN);
|
||||
}
|
||||
|
||||
@Test
|
||||
void detailShouldReturnUpcomingWhenToBeStarted() throws Exception {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
String pkId = newPkId();
|
||||
PlayClerkPkEntity pk = buildPk(pkId, newClerkId(), newClerkId(),
|
||||
now.plusMinutes(MINUTES_FUTURE_START),
|
||||
now.plusMinutes(MINUTES_AFTER_START),
|
||||
ClerkPkEnum.TO_BE_STARTED.name());
|
||||
SecurityUtils.setTenantId(DEFAULT_TENANT);
|
||||
clerkPkService.save(pk);
|
||||
|
||||
mockMvc.perform(get("/wx/pk/detail")
|
||||
.param(PARAM_ID, pkId)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.data.state").value(PkWxState.UPCOMING.getValue()))
|
||||
.andExpect(jsonPath("$.data.id").value(pkId));
|
||||
}
|
||||
|
||||
@Test
|
||||
void detailShouldReturnContributorsAndHistory() throws Exception {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
String pkId = newPkId();
|
||||
String clerkAId = newClerkId();
|
||||
String clerkBId = newClerkId();
|
||||
SecurityUtils.setTenantId(DEFAULT_TENANT);
|
||||
|
||||
PlayClerkPkEntity pk = buildPk(pkId, clerkAId, clerkBId,
|
||||
now.minusMinutes(MINUTES_AFTER_START),
|
||||
now.plusMinutes(MINUTES_AFTER_START),
|
||||
ClerkPkEnum.IN_PROGRESS.name());
|
||||
clerkPkService.save(pk);
|
||||
|
||||
String clerkAName = CLERK_NAME_PREFIX + clerkAId;
|
||||
String clerkBName = CLERK_NAME_PREFIX + clerkBId;
|
||||
clerkUserInfoService.save(buildClerk(clerkAId, clerkAName));
|
||||
clerkUserInfoService.save(buildClerk(clerkBId, clerkBName));
|
||||
|
||||
String customAId = newCustomId();
|
||||
String customBId = newCustomId();
|
||||
customUserInfoService.save(buildCustomUser(customAId, CUSTOM_NAME_PREFIX + "A"));
|
||||
customUserInfoService.save(buildCustomUser(customBId, CUSTOM_NAME_PREFIX + "B"));
|
||||
|
||||
orderInfoService.save(buildOrder(customAId, clerkAId, AMOUNT_MEDIUM, now.minusMinutes(MINUTES_ORDER_OFFSET)));
|
||||
orderInfoService.save(buildOrder(customBId, clerkAId, AMOUNT_LOW, now.minusMinutes(MINUTES_ORDER_OFFSET)));
|
||||
orderInfoService.save(buildOrder(customBId, clerkBId, AMOUNT_HIGH, now.minusMinutes(MINUTES_ORDER_OFFSET)));
|
||||
|
||||
clerkPkService.save(buildFinishedPk(newPkId(), clerkAId, clerkBId,
|
||||
now.minusDays(HISTORY_FIRST_DAY_OFFSET), AMOUNT_LOW, AMOUNT_HIGH));
|
||||
clerkPkService.save(buildFinishedPk(newPkId(), clerkBId, clerkAId,
|
||||
now.minusDays(HISTORY_SECOND_DAY_OFFSET), AMOUNT_HIGH, AMOUNT_LOW));
|
||||
|
||||
mockMvc.perform(get("/wx/pk/detail")
|
||||
.param(PARAM_ID, pkId)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.data.contributors[0].userId").value(customBId))
|
||||
.andExpect(jsonPath("$.data.contributors[0].amount").value(AMOUNT_HIGH.add(AMOUNT_LOW).doubleValue()))
|
||||
.andExpect(jsonPath("$.data.history.length()").value(EXPECTED_HISTORY_COUNT))
|
||||
.andExpect(jsonPath("$.data.history[0].clerkAName").value(clerkBName));
|
||||
}
|
||||
|
||||
@Test
|
||||
void clerkHistoryShouldReturnSummaryAndTopContributor() throws Exception {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
String clerkAId = newClerkId();
|
||||
String clerkBId = newClerkId();
|
||||
SecurityUtils.setTenantId(DEFAULT_TENANT);
|
||||
|
||||
PlayClerkPkEntity newerPk = buildFinishedPk(newPkId(), clerkAId, clerkBId,
|
||||
now.minusDays(HISTORY_SECOND_DAY_OFFSET), AMOUNT_HIGH, AMOUNT_LOW);
|
||||
newerPk.setWinnerClerkId(clerkAId);
|
||||
clerkPkService.save(newerPk);
|
||||
|
||||
PlayClerkPkEntity olderPk = buildFinishedPk(newPkId(), clerkAId, clerkBId,
|
||||
now.minusDays(HISTORY_FIRST_DAY_OFFSET), AMOUNT_LOW, AMOUNT_HIGH);
|
||||
olderPk.setWinnerClerkId(clerkBId);
|
||||
clerkPkService.save(olderPk);
|
||||
|
||||
String customHighId = newCustomId();
|
||||
String customLowId = newCustomId();
|
||||
customUserInfoService.save(buildCustomUser(customHighId, CUSTOM_NAME_PREFIX + "高"));
|
||||
customUserInfoService.save(buildCustomUser(customLowId, CUSTOM_NAME_PREFIX + "低"));
|
||||
|
||||
orderInfoService.save(buildOrder(customLowId, clerkAId, AMOUNT_LOW, now.minusDays(HISTORY_SECOND_DAY_OFFSET)
|
||||
.plusMinutes(MINUTES_ORDER_OFFSET)));
|
||||
orderInfoService.save(buildOrder(customHighId, clerkBId, AMOUNT_HIGH, now.minusDays(HISTORY_SECOND_DAY_OFFSET)
|
||||
.plusMinutes(MINUTES_ORDER_OFFSET)));
|
||||
|
||||
mockMvc.perform(get("/wx/pk/clerk/history")
|
||||
.param(PARAM_CLERK_ID, clerkAId)
|
||||
.param(PARAM_PAGE_NUM, String.valueOf(HISTORY_PAGE_NUM))
|
||||
.param(PARAM_PAGE_SIZE, String.valueOf(HISTORY_PAGE_SIZE))
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.data.items.length()").value(EXPECTED_HISTORY_COUNT))
|
||||
.andExpect(jsonPath("$.data.summary.totalCount").value(EXPECTED_HISTORY_COUNT))
|
||||
.andExpect(jsonPath("$.data.summary.winCount").value(1))
|
||||
.andExpect(jsonPath("$.data.items[0].topContributorName").value(CUSTOM_NAME_PREFIX + "高"))
|
||||
.andExpect(jsonPath("$.data.items[0].winnerClerkId").value(clerkAId))
|
||||
.andExpect(jsonPath("$.data.items[0].id").isNotEmpty())
|
||||
.andExpect(jsonPath("$.data.items[0].clerkAId").value(clerkAId))
|
||||
.andExpect(jsonPath("$.data.items[0].clerkBId").value(clerkBId))
|
||||
.andExpect(jsonPath("$.data.items[0].topContributorAmount").value(AMOUNT_HIGH.doubleValue()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void clerkScheduleShouldRejectWhenClerkIdMissing() throws Exception {
|
||||
mockMvc.perform(get("/wx/pk/clerk/schedule")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.FAILED.getCode()))
|
||||
.andExpect(jsonPath("$.message").value(MSG_PARAM_INVALID));
|
||||
}
|
||||
|
||||
@Test
|
||||
void clerkScheduleShouldReturnEmptyWhenNoFuturePk() throws Exception {
|
||||
String clerkId = newClerkId();
|
||||
mockMvc.perform(get("/wx/pk/clerk/schedule")
|
||||
.param(PARAM_CLERK_ID, clerkId)
|
||||
.param(PARAM_LIMIT, String.valueOf(SCHEDULE_LIMIT))
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.data.length()").value(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
void clerkScheduleShouldReturnUpcomingOrdered() throws Exception {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
String clerkId = newClerkId();
|
||||
SecurityUtils.setTenantId(DEFAULT_TENANT);
|
||||
|
||||
PlayClerkPkEntity first = buildPk(newPkId(), clerkId, newClerkId(),
|
||||
now.plusMinutes(FUTURE_FIRST_OFFSET_MINUTES),
|
||||
now.plusMinutes(FUTURE_FIRST_OFFSET_MINUTES + MINUTES_AFTER_START),
|
||||
ClerkPkEnum.TO_BE_STARTED.name());
|
||||
PlayClerkPkEntity second = buildPk(newPkId(), clerkId, newClerkId(),
|
||||
now.plusMinutes(FUTURE_SECOND_OFFSET_MINUTES),
|
||||
now.plusMinutes(FUTURE_SECOND_OFFSET_MINUTES + MINUTES_AFTER_START),
|
||||
ClerkPkEnum.TO_BE_STARTED.name());
|
||||
clerkPkService.save(second);
|
||||
clerkPkService.save(first);
|
||||
|
||||
mockMvc.perform(get("/wx/pk/clerk/schedule")
|
||||
.param(PARAM_CLERK_ID, clerkId)
|
||||
.param(PARAM_LIMIT, String.valueOf(SCHEDULE_LIMIT))
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.data.length()").value(SCHEDULE_LIMIT))
|
||||
.andExpect(jsonPath("$.data[0].id").value(first.getId()))
|
||||
.andExpect(jsonPath("$.data[1].id").value(second.getId()))
|
||||
.andExpect(jsonPath("$.data[0].state").value(PkWxState.UPCOMING.getValue()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void clerkScheduleShouldApplyLimitDefaultWhenMissing() throws Exception {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
String clerkId = newClerkId();
|
||||
SecurityUtils.setTenantId(DEFAULT_TENANT);
|
||||
|
||||
clerkPkService.save(buildPk(newPkId(), clerkId, newClerkId(),
|
||||
now.plusMinutes(FUTURE_FIRST_OFFSET_MINUTES),
|
||||
now.plusMinutes(FUTURE_FIRST_OFFSET_MINUTES + MINUTES_AFTER_START),
|
||||
ClerkPkEnum.TO_BE_STARTED.name()));
|
||||
clerkPkService.save(buildPk(newPkId(), clerkId, newClerkId(),
|
||||
now.plusMinutes(FUTURE_SECOND_OFFSET_MINUTES),
|
||||
now.plusMinutes(FUTURE_SECOND_OFFSET_MINUTES + MINUTES_AFTER_START),
|
||||
ClerkPkEnum.TO_BE_STARTED.name()));
|
||||
clerkPkService.save(buildPk(newPkId(), clerkId, newClerkId(),
|
||||
now.plusMinutes(FUTURE_THIRD_OFFSET_MINUTES),
|
||||
now.plusMinutes(FUTURE_THIRD_OFFSET_MINUTES + MINUTES_AFTER_START),
|
||||
ClerkPkEnum.TO_BE_STARTED.name()));
|
||||
|
||||
mockMvc.perform(get("/wx/pk/clerk/schedule")
|
||||
.param(PARAM_CLERK_ID, clerkId)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.data.length()").value(DEFAULT_LIMIT));
|
||||
}
|
||||
|
||||
@Test
|
||||
void clerkScheduleShouldClampLimitWhenZeroOrNegative() throws Exception {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
String clerkId = newClerkId();
|
||||
SecurityUtils.setTenantId(DEFAULT_TENANT);
|
||||
|
||||
clerkPkService.save(buildPk(newPkId(), clerkId, newClerkId(),
|
||||
now.plusMinutes(FUTURE_FIRST_OFFSET_MINUTES),
|
||||
now.plusMinutes(FUTURE_FIRST_OFFSET_MINUTES + MINUTES_AFTER_START),
|
||||
ClerkPkEnum.TO_BE_STARTED.name()));
|
||||
|
||||
mockMvc.perform(get("/wx/pk/clerk/schedule")
|
||||
.param(PARAM_CLERK_ID, clerkId)
|
||||
.param(PARAM_LIMIT, String.valueOf(LIMIT_ZERO))
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.data.length()").value(1));
|
||||
|
||||
mockMvc.perform(get("/wx/pk/clerk/schedule")
|
||||
.param(PARAM_CLERK_ID, clerkId)
|
||||
.param(PARAM_LIMIT, String.valueOf(LIMIT_NEGATIVE))
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.data.length()").value(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void clerkScheduleShouldClampLimitWhenTooLarge() throws Exception {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
String clerkId = newClerkId();
|
||||
SecurityUtils.setTenantId(DEFAULT_TENANT);
|
||||
|
||||
clerkPkService.save(buildPk(newPkId(), clerkId, newClerkId(),
|
||||
now.plusMinutes(FUTURE_FIRST_OFFSET_MINUTES),
|
||||
now.plusMinutes(FUTURE_FIRST_OFFSET_MINUTES + MINUTES_AFTER_START),
|
||||
ClerkPkEnum.TO_BE_STARTED.name()));
|
||||
|
||||
mockMvc.perform(get("/wx/pk/clerk/schedule")
|
||||
.param(PARAM_CLERK_ID, clerkId)
|
||||
.param(PARAM_LIMIT, String.valueOf(LIMIT_LARGE))
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.data.length()").value(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void clerkScheduleShouldExcludePastOrNonToBeStarted() throws Exception {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
String clerkId = newClerkId();
|
||||
SecurityUtils.setTenantId(DEFAULT_TENANT);
|
||||
|
||||
clerkPkService.save(buildPk(newPkId(), clerkId, newClerkId(),
|
||||
now.minusMinutes(PAST_OFFSET_MINUTES),
|
||||
now.minusMinutes(PAST_OFFSET_MINUTES - MINUTES_AFTER_START),
|
||||
ClerkPkEnum.TO_BE_STARTED.name()));
|
||||
clerkPkService.save(buildPk(newPkId(), clerkId, newClerkId(),
|
||||
now.plusMinutes(FUTURE_FIRST_OFFSET_MINUTES),
|
||||
now.plusMinutes(FUTURE_FIRST_OFFSET_MINUTES + MINUTES_AFTER_START),
|
||||
ClerkPkEnum.IN_PROGRESS.name()));
|
||||
PlayClerkPkEntity future = buildPk(newPkId(), clerkId, newClerkId(),
|
||||
now.plusMinutes(FUTURE_SECOND_OFFSET_MINUTES),
|
||||
now.plusMinutes(FUTURE_SECOND_OFFSET_MINUTES + MINUTES_AFTER_START),
|
||||
ClerkPkEnum.TO_BE_STARTED.name());
|
||||
clerkPkService.save(future);
|
||||
|
||||
mockMvc.perform(get("/wx/pk/clerk/schedule")
|
||||
.param(PARAM_CLERK_ID, clerkId)
|
||||
.param(PARAM_LIMIT, String.valueOf(SCHEDULE_LIMIT))
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.data.length()").value(1))
|
||||
.andExpect(jsonPath("$.data[0].id").value(future.getId()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void clerkScheduleShouldIsolateTenants() throws Exception {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
String clerkId = newClerkId();
|
||||
SecurityUtils.setTenantId(DEFAULT_TENANT);
|
||||
clerkPkService.save(buildPk(newPkId(), clerkId, newClerkId(),
|
||||
now.plusMinutes(FUTURE_FIRST_OFFSET_MINUTES),
|
||||
now.plusMinutes(FUTURE_FIRST_OFFSET_MINUTES + MINUTES_AFTER_START),
|
||||
ClerkPkEnum.TO_BE_STARTED.name()));
|
||||
|
||||
String otherTenant = "tenant-other";
|
||||
PlayClerkPkEntity other = buildPk(newPkId(), clerkId, newClerkId(),
|
||||
now.plusMinutes(FUTURE_SECOND_OFFSET_MINUTES),
|
||||
now.plusMinutes(FUTURE_SECOND_OFFSET_MINUTES + MINUTES_AFTER_START),
|
||||
ClerkPkEnum.TO_BE_STARTED.name());
|
||||
other.setTenantId(otherTenant);
|
||||
clerkPkService.save(other);
|
||||
|
||||
mockMvc.perform(get("/wx/pk/clerk/schedule")
|
||||
.param(PARAM_CLERK_ID, clerkId)
|
||||
.param(PARAM_LIMIT, String.valueOf(SCHEDULE_LIMIT))
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.data.length()").value(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void clerkScheduleShouldIgnoreNonParticipant() throws Exception {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
String clerkId = newClerkId();
|
||||
SecurityUtils.setTenantId(DEFAULT_TENANT);
|
||||
|
||||
clerkPkService.save(buildPk(newPkId(), newClerkId(), newClerkId(),
|
||||
now.plusMinutes(FUTURE_FIRST_OFFSET_MINUTES),
|
||||
now.plusMinutes(FUTURE_FIRST_OFFSET_MINUTES + MINUTES_AFTER_START),
|
||||
ClerkPkEnum.TO_BE_STARTED.name()));
|
||||
|
||||
mockMvc.perform(get("/wx/pk/clerk/schedule")
|
||||
.param(PARAM_CLERK_ID, clerkId)
|
||||
.param(PARAM_LIMIT, String.valueOf(SCHEDULE_LIMIT))
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.data.length()").value(0));
|
||||
}
|
||||
|
||||
private static PlayClerkPkEntity buildPk(String pkId, String clerkAId, String clerkBId,
|
||||
LocalDateTime begin, LocalDateTime end, String status) {
|
||||
PlayClerkPkEntity pk = new PlayClerkPkEntity();
|
||||
pk.setId(pkId);
|
||||
pk.setTenantId(DEFAULT_TENANT);
|
||||
pk.setClerkA(clerkAId);
|
||||
pk.setClerkB(clerkBId);
|
||||
pk.setPkBeginTime(Date.from(begin.atZone(ZoneId.systemDefault()).toInstant()));
|
||||
pk.setPkEndTime(Date.from(end.atZone(ZoneId.systemDefault()).toInstant()));
|
||||
pk.setStatus(status);
|
||||
pk.setSettled(SETTLED_FALSE);
|
||||
return pk;
|
||||
}
|
||||
|
||||
private static PlayClerkPkEntity buildFinishedPk(String pkId, String clerkAId, String clerkBId,
|
||||
LocalDateTime begin, BigDecimal scoreA, BigDecimal scoreB) {
|
||||
PlayClerkPkEntity pk = new PlayClerkPkEntity();
|
||||
pk.setId(pkId);
|
||||
pk.setTenantId(DEFAULT_TENANT);
|
||||
pk.setClerkA(clerkAId);
|
||||
pk.setClerkB(clerkBId);
|
||||
pk.setPkBeginTime(Date.from(begin.atZone(ZoneId.systemDefault()).toInstant()));
|
||||
pk.setPkEndTime(Date.from(begin.plusMinutes(MINUTES_AFTER_START).atZone(ZoneId.systemDefault()).toInstant()));
|
||||
pk.setStatus(ClerkPkEnum.FINISHED.name());
|
||||
pk.setClerkAScore(scoreA);
|
||||
pk.setClerkBScore(scoreB);
|
||||
pk.setSettled(SETTLED_TRUE);
|
||||
return pk;
|
||||
}
|
||||
|
||||
private static PlayClerkUserInfoEntity buildClerk(String id, String nickname) {
|
||||
PlayClerkUserInfoEntity clerk = new PlayClerkUserInfoEntity();
|
||||
clerk.setId(id);
|
||||
clerk.setTenantId(DEFAULT_TENANT);
|
||||
clerk.setOpenid(OPENID_PREFIX + id);
|
||||
clerk.setNickname(nickname);
|
||||
clerk.setDeleted(false);
|
||||
return clerk;
|
||||
}
|
||||
|
||||
private static PlayCustomUserInfoEntity buildCustomUser(String id, String nickname) {
|
||||
PlayCustomUserInfoEntity custom = new PlayCustomUserInfoEntity();
|
||||
custom.setId(id);
|
||||
custom.setTenantId(DEFAULT_TENANT);
|
||||
custom.setNickname(nickname);
|
||||
custom.setDeleted(false);
|
||||
return custom;
|
||||
}
|
||||
|
||||
private static PlayOrderInfoEntity buildOrder(String customerId, String clerkId, BigDecimal amount,
|
||||
LocalDateTime endTime) {
|
||||
PlayOrderInfoEntity order = new PlayOrderInfoEntity();
|
||||
order.setId(IdUtils.getUuid());
|
||||
order.setTenantId(DEFAULT_TENANT);
|
||||
order.setOrderStatus(OrderConstant.OrderStatus.COMPLETED.getCode());
|
||||
order.setOrderType(OrderConstant.OrderType.NORMAL.getCode());
|
||||
order.setPlaceType(OrderConstant.PlaceType.SPECIFIED.getCode());
|
||||
order.setPurchaserBy(customerId);
|
||||
order.setAcceptBy(clerkId);
|
||||
order.setFinalAmount(amount);
|
||||
order.setOrderEndTime(endTime);
|
||||
order.setDeleted(false);
|
||||
return order;
|
||||
}
|
||||
|
||||
private static String newClerkId() {
|
||||
return CLERK_PREFIX + IdUtils.getUuid();
|
||||
}
|
||||
|
||||
private static String newCustomId() {
|
||||
return CUSTOM_PREFIX + IdUtils.getUuid();
|
||||
}
|
||||
|
||||
private static String newPkId() {
|
||||
return PK_PREFIX + IdUtils.getUuid();
|
||||
}
|
||||
|
||||
private static JsonNode extractData(MvcResult result) throws Exception {
|
||||
JsonNode root = OBJECT_MAPPER.readTree(result.getResponse().getContentAsString());
|
||||
return root.get("data");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user