test: add wechat integration test suite
Some checks failed
Build and Push Backend / docker (push) Has been cancelled
Some checks failed
Build and Push Backend / docker (push) Has been cancelled
- Add llm/wechat-subsystem-test-matrix.md and tests covering Wx controllers/services\n- Make ApiTestDataSeeder personnel group seeding idempotent for full-suite stability
This commit is contained in:
@@ -24,7 +24,7 @@ public class MockWxMpServiceConfig {
|
||||
WxMpService service = mock(WxMpService.class, Mockito.RETURNS_DEEP_STUBS);
|
||||
WxMpTemplateMsgService templateMsgService = mock(WxMpTemplateMsgService.class);
|
||||
when(service.getTemplateMsgService()).thenReturn(templateMsgService);
|
||||
when(service.switchoverTo(Mockito.anyString())).thenReturn(service);
|
||||
when(service.switchoverTo(Mockito.nullable(String.class))).thenReturn(service);
|
||||
when(service.switchover(Mockito.anyString())).thenReturn(true);
|
||||
return service;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,264 @@
|
||||
package com.starry.admin.api;
|
||||
|
||||
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.starry.admin.common.apitest.ApiTestDataSeeder;
|
||||
import com.starry.admin.modules.clerk.module.entity.PlayClerkArticleInfoEntity;
|
||||
import com.starry.admin.modules.clerk.module.entity.PlayCustomArticleInfoEntity;
|
||||
import com.starry.admin.modules.clerk.service.IPlayClerkArticleInfoService;
|
||||
import com.starry.admin.modules.clerk.service.IPlayClerkUserInfoService;
|
||||
import com.starry.admin.modules.clerk.service.IPlayCustomArticleInfoService;
|
||||
import com.starry.admin.modules.custom.service.IPlayCustomUserInfoService;
|
||||
import com.starry.admin.modules.weichat.service.WxTokenService;
|
||||
import com.starry.admin.utils.SecurityUtils;
|
||||
import com.starry.common.constant.Constants;
|
||||
import com.starry.common.utils.IdUtils;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Collections;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.MediaType;
|
||||
|
||||
class WxArticleControllerApiTest extends AbstractApiTest {
|
||||
|
||||
@Autowired
|
||||
private IPlayClerkArticleInfoService clerkArticleInfoService;
|
||||
|
||||
@Autowired
|
||||
private IPlayCustomArticleInfoService customArticleInfoService;
|
||||
|
||||
@Autowired
|
||||
private WxTokenService wxTokenService;
|
||||
|
||||
@Autowired
|
||||
private IPlayClerkUserInfoService clerkUserInfoService;
|
||||
|
||||
@Autowired
|
||||
private IPlayCustomUserInfoService customUserInfoService;
|
||||
|
||||
private final java.util.List<String> createdArticleIds = new java.util.ArrayList<>();
|
||||
private final java.util.List<String> createdCustomLinkIds = new java.util.ArrayList<>();
|
||||
private String clerkToken;
|
||||
private String customerToken;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
clerkToken = wxTokenService.createWxUserToken(ApiTestDataSeeder.DEFAULT_CLERK_ID);
|
||||
clerkUserInfoService.updateTokenById(ApiTestDataSeeder.DEFAULT_CLERK_ID, clerkToken);
|
||||
|
||||
customerToken = wxTokenService.createWxUserToken(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID);
|
||||
customUserInfoService.updateTokenById(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID, customerToken);
|
||||
|
||||
// Article create() blocks when there is any unaudited record for the clerk; keep tests deterministic.
|
||||
clerkArticleInfoService.lambdaUpdate()
|
||||
.eq(PlayClerkArticleInfoEntity::getClerkId, ApiTestDataSeeder.DEFAULT_CLERK_ID)
|
||||
.eq(PlayClerkArticleInfoEntity::getReviewState, "0")
|
||||
.remove();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
if (!createdCustomLinkIds.isEmpty()) {
|
||||
customArticleInfoService.removeByIds(createdCustomLinkIds);
|
||||
createdCustomLinkIds.clear();
|
||||
}
|
||||
if (!createdArticleIds.isEmpty()) {
|
||||
clerkArticleInfoService.removeByIds(createdArticleIds);
|
||||
createdArticleIds.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void clerkAddCreatesArticleWithSessionClerkId__covers_ART_001() throws Exception {
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
String marker = "apitest-article-" + IdUtils.getUuid();
|
||||
|
||||
String payload = "{" +
|
||||
"\"articleCon\":\"" + marker + "\"," +
|
||||
"\"annexType\":\"0\"," +
|
||||
"\"annexCon\":[\"https://example.com/a.png\"]" +
|
||||
"}";
|
||||
|
||||
mockMvc.perform(post("/wx/article/clerk/add")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CLERK_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + clerkToken)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(payload))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200))
|
||||
.andExpect(jsonPath("$.message").value("操作成功"));
|
||||
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
PlayClerkArticleInfoEntity created = clerkArticleInfoService.lambdaQuery()
|
||||
.eq(PlayClerkArticleInfoEntity::getClerkId, ApiTestDataSeeder.DEFAULT_CLERK_ID)
|
||||
.eq(PlayClerkArticleInfoEntity::getArticleCon, marker)
|
||||
.last("limit 1")
|
||||
.one();
|
||||
if (created == null) {
|
||||
throw new AssertionError("Expected article row to be created");
|
||||
}
|
||||
createdArticleIds.add(created.getId());
|
||||
if (!ApiTestDataSeeder.DEFAULT_CLERK_ID.equals(created.getClerkId())) {
|
||||
throw new AssertionError("Expected clerkId to be set from session");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void clerkDeleteByIdRemovesArticleAndCustomLinks__covers_ART_002() throws Exception {
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
String articleId = insertApprovedArticleForClerk();
|
||||
|
||||
PlayCustomArticleInfoEntity link = new PlayCustomArticleInfoEntity();
|
||||
link.setId("apitest-link-" + IdUtils.getUuid());
|
||||
link.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
link.setArticleId(articleId);
|
||||
link.setClerkId(ApiTestDataSeeder.DEFAULT_CLERK_ID);
|
||||
link.setCustomId(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID);
|
||||
link.setEndorseType("1");
|
||||
link.setEndorseState("1");
|
||||
link.setEndorseTime(LocalDateTime.now());
|
||||
customArticleInfoService.save(link);
|
||||
createdCustomLinkIds.add(link.getId());
|
||||
|
||||
mockMvc.perform(get("/wx/article/clerk/deleteById")
|
||||
.param("id", articleId)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CLERK_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + clerkToken))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200))
|
||||
.andExpect(jsonPath("$.message").value("操作成功"));
|
||||
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
if (clerkArticleInfoService.getById(articleId) != null) {
|
||||
throw new AssertionError("Expected article to be deleted");
|
||||
}
|
||||
long remainingLinks = customArticleInfoService.lambdaQuery()
|
||||
.eq(PlayCustomArticleInfoEntity::getArticleId, articleId)
|
||||
.count();
|
||||
if (remainingLinks != 0) {
|
||||
throw new AssertionError("Expected custom article links to be deleted");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void customUpdateGreedStateUpsertsAndUpdatesEndorseTime__covers_ART_003() throws Exception {
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
String articleId = insertApprovedArticleForClerk();
|
||||
|
||||
LocalDateTime before = LocalDateTime.now().minusSeconds(1);
|
||||
|
||||
mockMvc.perform(post("/wx/article/custom/updateGreedState")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CUSTOM_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + customerToken)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("{\"id\":\"" + articleId + "\",\"greedState\":\"1\"}"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200));
|
||||
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
java.util.List<PlayCustomArticleInfoEntity> likedRows = customArticleInfoService.lambdaQuery()
|
||||
.eq(PlayCustomArticleInfoEntity::getArticleId, articleId)
|
||||
.eq(PlayCustomArticleInfoEntity::getCustomId, ApiTestDataSeeder.DEFAULT_CUSTOMER_ID)
|
||||
.eq(PlayCustomArticleInfoEntity::getEndorseType, "1")
|
||||
.list();
|
||||
if (likedRows == null || likedRows.isEmpty()) {
|
||||
throw new AssertionError("Expected custom article greed record(s) to be created");
|
||||
}
|
||||
for (PlayCustomArticleInfoEntity item : likedRows) {
|
||||
createdCustomLinkIds.add(item.getId());
|
||||
}
|
||||
boolean anyLiked = likedRows.stream().anyMatch(item -> "1".equals(item.getEndorseState()));
|
||||
if (!anyLiked) {
|
||||
throw new AssertionError("Expected at least one greed record endorseState=1");
|
||||
}
|
||||
boolean anyRecent = likedRows.stream()
|
||||
.anyMatch(item -> item.getEndorseTime() != null && !item.getEndorseTime().isBefore(before));
|
||||
if (!anyRecent) {
|
||||
throw new AssertionError("Expected endorseTime to be set");
|
||||
}
|
||||
mockMvc.perform(post("/wx/article/custom/updateGreedState")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CUSTOM_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + customerToken)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("{\"id\":\"" + articleId + "\",\"greedState\":\"0\"}"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200));
|
||||
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
java.util.List<PlayCustomArticleInfoEntity> afterUnlikeRows = customArticleInfoService.lambdaQuery()
|
||||
.eq(PlayCustomArticleInfoEntity::getArticleId, articleId)
|
||||
.eq(PlayCustomArticleInfoEntity::getCustomId, ApiTestDataSeeder.DEFAULT_CUSTOMER_ID)
|
||||
.eq(PlayCustomArticleInfoEntity::getEndorseType, "1")
|
||||
.list();
|
||||
if (afterUnlikeRows == null || afterUnlikeRows.isEmpty()) {
|
||||
throw new AssertionError("Expected custom article greed record(s) to exist after update");
|
||||
}
|
||||
for (PlayCustomArticleInfoEntity item : afterUnlikeRows) {
|
||||
createdCustomLinkIds.add(item.getId());
|
||||
}
|
||||
boolean anyUnliked = afterUnlikeRows.stream().anyMatch(item -> "0".equals(item.getEndorseState()));
|
||||
if (!anyUnliked) {
|
||||
throw new AssertionError("Expected at least one greed record endorseState=0 after update");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void customUpdateFollowStateUpsertsAndUpdatesEndorseTime__covers_ART_004() throws Exception {
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
String articleId = insertApprovedArticleForClerk();
|
||||
|
||||
mockMvc.perform(post("/wx/article/custom/updateFollowState")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CUSTOM_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + customerToken)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("{\"id\":\"" + articleId + "\",\"followState\":\"1\"}"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200));
|
||||
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
PlayCustomArticleInfoEntity row = customArticleInfoService.selectByArticleId(
|
||||
articleId, ApiTestDataSeeder.DEFAULT_CUSTOMER_ID, "0");
|
||||
if (row == null) {
|
||||
throw new AssertionError("Expected follow record to be created");
|
||||
}
|
||||
createdCustomLinkIds.add(row.getId());
|
||||
if (!"0".equals(row.getEndorseType())) {
|
||||
throw new AssertionError("Expected endorseType=0 for follow");
|
||||
}
|
||||
if (!"1".equals(row.getEndorseState())) {
|
||||
throw new AssertionError("Expected endorseState=1");
|
||||
}
|
||||
if (row.getEndorseTime() == null) {
|
||||
throw new AssertionError("Expected endorseTime to be set");
|
||||
}
|
||||
}
|
||||
|
||||
private String insertApprovedArticleForClerk() {
|
||||
PlayClerkArticleInfoEntity entity = new PlayClerkArticleInfoEntity();
|
||||
String id = "apitest-clerk-article-" + IdUtils.getUuid();
|
||||
entity.setId(id);
|
||||
entity.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
entity.setClerkId(ApiTestDataSeeder.DEFAULT_CLERK_ID);
|
||||
entity.setArticleCon("seed");
|
||||
entity.setAnnexType("0");
|
||||
entity.setAnnexCon(Collections.singletonList("https://example.com/seed.png"));
|
||||
entity.setReviewState("1");
|
||||
entity.setReleaseTime(LocalDateTime.now());
|
||||
entity.setDeleted(Boolean.FALSE);
|
||||
clerkArticleInfoService.save(entity);
|
||||
createdArticleIds.add(id);
|
||||
return id;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
package com.starry.admin.api;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.when;
|
||||
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.starry.admin.common.apitest.ApiTestDataSeeder;
|
||||
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;
|
||||
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.weichat.service.WxTokenService;
|
||||
import com.starry.admin.utils.SecurityUtils;
|
||||
import com.starry.common.constant.Constants;
|
||||
import me.chanjar.weixin.mp.api.WxMpService;
|
||||
import me.chanjar.weixin.mp.bean.result.WxMpUser;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
class WxAuthAspectApiTest extends AbstractApiTest {
|
||||
|
||||
@Autowired
|
||||
private WxTokenService wxTokenService;
|
||||
|
||||
@Autowired
|
||||
private IPlayCustomUserInfoService customUserInfoService;
|
||||
|
||||
@Autowired
|
||||
private IPlayClerkUserInfoService clerkUserInfoService;
|
||||
|
||||
@Autowired
|
||||
private WxMpService wxMpService;
|
||||
|
||||
@Test
|
||||
void customUserLoginAspectRejectsMissingTokenHeader__covers_AOP_001() throws Exception {
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
|
||||
mockMvc.perform(get("/wx/oauth2/checkSubscribe")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(401))
|
||||
.andExpect(jsonPath("$.message").value("token为空"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void clerkUserLoginAspectRejectsMissingTokenHeader__covers_AOP_002() throws Exception {
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
|
||||
mockMvc.perform(get("/wx/oauth2/clerk/logout")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(401))
|
||||
.andExpect(jsonPath("$.message").value("token为空"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void customUserLoginAspectRejectsInvalidTokenFormat__covers_AOP_003() throws Exception {
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
|
||||
mockMvc.perform(get("/wx/oauth2/checkSubscribe")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CUSTOM_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + "not-a-jwt"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(401))
|
||||
.andExpect(jsonPath("$.message").value("获取用户信息异常"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void customUserLoginAspectRejectsTokenMismatchAgainstDatabase__covers_AOP_004() throws Exception {
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
|
||||
String userId = ApiTestDataSeeder.DEFAULT_CUSTOMER_ID;
|
||||
String token = wxTokenService.createWxUserToken(userId);
|
||||
customUserInfoService.updateTokenById(userId, "some-other-token");
|
||||
|
||||
mockMvc.perform(get("/wx/oauth2/checkSubscribe")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CUSTOM_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + token))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(401))
|
||||
.andExpect(jsonPath("$.message").value("token异常"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void clerkUserLoginAspectRejectsOffboardedClerkAndInvalidatesSession__covers_AOP_005() throws Exception {
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
|
||||
String clerkId = "clerk-aop-offboarded";
|
||||
String openId = "openid-clerk-aop-offboarded";
|
||||
String token = wxTokenService.createWxUserToken(clerkId);
|
||||
|
||||
PlayClerkUserInfoEntity existing = clerkUserInfoService.getById(clerkId);
|
||||
if (existing == null) {
|
||||
PlayClerkUserInfoEntity entity = new PlayClerkUserInfoEntity();
|
||||
entity.setId(clerkId);
|
||||
entity.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
entity.setOpenid(openId);
|
||||
entity.setNickname("API Test Offboarded Clerk");
|
||||
entity.setAvatar("https://example.com/avatar.png");
|
||||
entity.setSysUserId("");
|
||||
entity.setOnboardingState("0");
|
||||
entity.setListingState("1");
|
||||
entity.setClerkState("1");
|
||||
entity.setOnlineState("1");
|
||||
entity.setToken(token);
|
||||
clerkUserInfoService.save(entity);
|
||||
} else {
|
||||
PlayClerkUserInfoEntity patch = new PlayClerkUserInfoEntity();
|
||||
patch.setId(clerkId);
|
||||
patch.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
patch.setOpenid(openId);
|
||||
patch.setAvatar("https://example.com/avatar.png");
|
||||
patch.setSysUserId("");
|
||||
patch.setOnboardingState("0");
|
||||
patch.setListingState("1");
|
||||
patch.setClerkState("1");
|
||||
patch.setOnlineState("1");
|
||||
patch.setDeleted(Boolean.FALSE);
|
||||
patch.setToken(token);
|
||||
clerkUserInfoService.updateById(patch);
|
||||
}
|
||||
|
||||
WxMpUser wxMpUser = new WxMpUser();
|
||||
wxMpUser.setSubscribe(true);
|
||||
when(wxMpService.getUserService().userInfo(anyString())).thenReturn(wxMpUser);
|
||||
|
||||
mockMvc.perform(get("/wx/oauth2/clerk/logout")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CLERK_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + token))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(401))
|
||||
.andExpect(jsonPath("$.message").value("你已离职,需要复职请联系店铺管理员"));
|
||||
|
||||
PlayClerkUserInfoEntity after = clerkUserInfoService.getById(clerkId);
|
||||
if (after == null) {
|
||||
throw new AssertionError("Expected clerk to exist");
|
||||
}
|
||||
if (!"empty".equals(after.getToken())) {
|
||||
throw new AssertionError("Expected clerk token to be invalidated to 'empty'");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,293 @@
|
||||
package com.starry.admin.api;
|
||||
|
||||
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.starry.admin.common.apitest.ApiTestDataSeeder;
|
||||
import com.starry.admin.modules.blindbox.mapper.BlindBoxPoolMapper;
|
||||
import com.starry.admin.modules.blindbox.mapper.BlindBoxRewardMapper;
|
||||
import com.starry.admin.modules.blindbox.module.entity.BlindBoxConfigEntity;
|
||||
import com.starry.admin.modules.blindbox.module.entity.BlindBoxPoolEntity;
|
||||
import com.starry.admin.modules.blindbox.module.entity.BlindBoxRewardEntity;
|
||||
import com.starry.admin.modules.blindbox.service.BlindBoxConfigService;
|
||||
import com.starry.admin.modules.shop.module.constant.GiftHistory;
|
||||
import com.starry.admin.modules.shop.module.constant.GiftState;
|
||||
import com.starry.admin.modules.shop.module.constant.GiftType;
|
||||
import com.starry.admin.modules.shop.module.entity.PlayGiftInfoEntity;
|
||||
import com.starry.admin.modules.shop.service.IPlayGiftInfoService;
|
||||
import com.starry.admin.utils.SecurityUtils;
|
||||
import com.starry.common.constant.Constants;
|
||||
import com.starry.common.context.CustomSecurityContextHolder;
|
||||
import com.starry.common.utils.IdUtils;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Objects;
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.web.servlet.MvcResult;
|
||||
|
||||
class WxBlindBoxControllerApiTest extends WxCustomOrderApiTestSupport {
|
||||
|
||||
@Autowired
|
||||
private BlindBoxConfigService blindBoxConfigService;
|
||||
|
||||
@Autowired
|
||||
private BlindBoxPoolMapper blindBoxPoolMapper;
|
||||
|
||||
@Autowired
|
||||
private IPlayGiftInfoService giftInfoService;
|
||||
|
||||
@Autowired
|
||||
private BlindBoxRewardMapper blindBoxRewardMapper;
|
||||
|
||||
private String customerToken;
|
||||
|
||||
private final java.util.List<String> configIdsToCleanup = new java.util.ArrayList<>();
|
||||
private final java.util.List<Long> poolIdsToCleanup = new java.util.ArrayList<>();
|
||||
private final java.util.List<String> giftIdsToCleanup = new java.util.ArrayList<>();
|
||||
private final java.util.List<String> rewardIdsToCleanup = new java.util.ArrayList<>();
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
ensureTenantContext();
|
||||
resetCustomerBalance();
|
||||
customerToken = wxTokenService.createWxUserToken(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID);
|
||||
customUserInfoService.updateTokenById(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID, customerToken);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
ensureTenantContext();
|
||||
for (String id : rewardIdsToCleanup) {
|
||||
blindBoxRewardMapper.deleteById(id);
|
||||
}
|
||||
for (Long id : poolIdsToCleanup) {
|
||||
blindBoxPoolMapper.deleteById(id);
|
||||
}
|
||||
for (String id : configIdsToCleanup) {
|
||||
blindBoxConfigService.removeById(id);
|
||||
}
|
||||
for (String id : giftIdsToCleanup) {
|
||||
giftInfoService.removeById(id);
|
||||
}
|
||||
CustomSecurityContextHolder.remove();
|
||||
}
|
||||
|
||||
@Test
|
||||
void listConfigsRejectsMissingToken__covers_BB_001() throws Exception {
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
|
||||
mockMvc.perform(get("/wx/blind-box/config/list")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(401))
|
||||
.andExpect(jsonPath("$.message").value("token为空"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void listConfigsReturnsOnlyActiveConfigsForTenant__covers_BB_002() throws Exception {
|
||||
ensureTenantContext();
|
||||
BlindBoxConfigEntity active = new BlindBoxConfigEntity();
|
||||
active.setId("bb-active-" + IdUtils.getUuid().substring(0, 8));
|
||||
active.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
active.setName("API盲盒-上架");
|
||||
active.setPrice(new BigDecimal("9.99"));
|
||||
active.setStatus(1);
|
||||
blindBoxConfigService.save(active);
|
||||
configIdsToCleanup.add(active.getId());
|
||||
|
||||
BlindBoxConfigEntity inactive = new BlindBoxConfigEntity();
|
||||
inactive.setId("bb-off-" + IdUtils.getUuid().substring(0, 8));
|
||||
inactive.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
inactive.setName("API盲盒-下架");
|
||||
inactive.setPrice(new BigDecimal("9.99"));
|
||||
inactive.setStatus(0);
|
||||
blindBoxConfigService.save(inactive);
|
||||
configIdsToCleanup.add(inactive.getId());
|
||||
|
||||
mockMvc.perform(get("/wx/blind-box/config/list")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CUSTOM_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + customerToken))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200))
|
||||
.andExpect(jsonPath("$.data[*].id").value(org.hamcrest.Matchers.hasItem(active.getId())))
|
||||
.andExpect(jsonPath("$.data[*].id").value(org.hamcrest.Matchers.not(org.hamcrest.Matchers.hasItem(inactive.getId()))));
|
||||
}
|
||||
|
||||
@Test
|
||||
void purchaseRejectsTenantMismatch__covers_BB_003() throws Exception {
|
||||
ensureTenantContext();
|
||||
BlindBoxConfigEntity config = new BlindBoxConfigEntity();
|
||||
config.setId("bb-other-" + IdUtils.getUuid().substring(0, 8));
|
||||
config.setTenantId("other-tenant");
|
||||
config.setName("API盲盒-其他租户");
|
||||
config.setPrice(new BigDecimal("9.99"));
|
||||
config.setStatus(1);
|
||||
blindBoxConfigService.save(config);
|
||||
configIdsToCleanup.add(config.getId());
|
||||
|
||||
String payload = objectMapper.createObjectNode()
|
||||
.put("blindBoxId", config.getId())
|
||||
.put("clerkId", ApiTestDataSeeder.DEFAULT_CLERK_ID)
|
||||
.put("weiChatCode", "apitest-customer-wx")
|
||||
.toString();
|
||||
|
||||
mockMvc.perform(post("/wx/blind-box/order/purchase")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CUSTOM_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + customerToken)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(payload))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(500))
|
||||
.andExpect(jsonPath("$.message").value("盲盒不存在"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void dispatchRejectsWhenRewardNotFound__covers_BB_005() throws Exception {
|
||||
ensureTenantContext();
|
||||
String payload = objectMapper.createObjectNode()
|
||||
.put("clerkId", ApiTestDataSeeder.DEFAULT_CLERK_ID)
|
||||
.toString();
|
||||
|
||||
mockMvc.perform(post("/wx/blind-box/reward/not-found-" + IdUtils.getUuid().substring(0, 6) + "/dispatch")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CUSTOM_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + customerToken)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(payload))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(500))
|
||||
.andExpect(jsonPath("$.message").value("盲盒奖励不存在"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void listRewardsFiltersByStatus__covers_BB_007() throws Exception {
|
||||
ensureTenantContext();
|
||||
String configId = "bb-list-" + IdUtils.getUuid().substring(0, 6);
|
||||
String giftId = "bb-gift-" + IdUtils.getUuid().substring(0, 6);
|
||||
Long poolId = null;
|
||||
|
||||
BlindBoxConfigEntity config = new BlindBoxConfigEntity();
|
||||
config.setId(configId);
|
||||
config.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
config.setName("API盲盒-列表");
|
||||
config.setPrice(new BigDecimal("19.90"));
|
||||
config.setStatus(1);
|
||||
blindBoxConfigService.save(config);
|
||||
configIdsToCleanup.add(configId);
|
||||
|
||||
PlayGiftInfoEntity gift = new PlayGiftInfoEntity();
|
||||
gift.setId(giftId);
|
||||
gift.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
gift.setName("盲盒礼物-列表");
|
||||
gift.setHistory(GiftHistory.CURRENT.getCode());
|
||||
gift.setState(GiftState.ACTIVE.getCode());
|
||||
gift.setType(GiftType.NORMAL.getCode());
|
||||
gift.setUrl("https://example.com/apitest/blindbox-list.png");
|
||||
gift.setPrice(new BigDecimal("9.99"));
|
||||
giftInfoService.save(gift);
|
||||
giftIdsToCleanup.add(giftId);
|
||||
|
||||
BlindBoxPoolEntity entry = new BlindBoxPoolEntity();
|
||||
entry.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
entry.setBlindBoxId(configId);
|
||||
entry.setRewardGiftId(giftId);
|
||||
entry.setRewardPrice(gift.getPrice());
|
||||
entry.setWeight(100);
|
||||
entry.setRemainingStock(10);
|
||||
entry.setStatus(1);
|
||||
entry.setValidFrom(LocalDateTime.now().minusDays(1));
|
||||
entry.setValidTo(LocalDateTime.now().plusDays(1));
|
||||
blindBoxPoolMapper.insert(entry);
|
||||
poolId = entry.getId();
|
||||
poolIdsToCleanup.add(poolId);
|
||||
|
||||
String purchasePayload = objectMapper.createObjectNode()
|
||||
.put("blindBoxId", configId)
|
||||
.put("clerkId", ApiTestDataSeeder.DEFAULT_CLERK_ID)
|
||||
.put("weiChatCode", "apitest-customer-wx")
|
||||
.toString();
|
||||
|
||||
MvcResult firstPurchase = mockMvc.perform(post("/wx/blind-box/order/purchase")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CUSTOM_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + customerToken)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(purchasePayload))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200))
|
||||
.andReturn();
|
||||
|
||||
JsonNode rewardNode = objectMapper.readTree(firstPurchase.getResponse().getContentAsString())
|
||||
.path("data").path("reward");
|
||||
String usedRewardId = rewardNode.path("rewardId").asText();
|
||||
Assertions.assertThat(usedRewardId).isNotBlank();
|
||||
rewardIdsToCleanup.add(usedRewardId);
|
||||
|
||||
mockMvc.perform(post("/wx/blind-box/reward/" + usedRewardId + "/dispatch")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CUSTOM_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + customerToken)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(objectMapper.createObjectNode()
|
||||
.put("clerkId", ApiTestDataSeeder.DEFAULT_CLERK_ID)
|
||||
.toString()))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200))
|
||||
.andExpect(jsonPath("$.data.status").value("USED"));
|
||||
|
||||
MvcResult secondPurchase = mockMvc.perform(post("/wx/blind-box/order/purchase")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CUSTOM_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + customerToken)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(purchasePayload))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200))
|
||||
.andReturn();
|
||||
|
||||
String unusedRewardId = objectMapper.readTree(secondPurchase.getResponse().getContentAsString())
|
||||
.path("data").path("reward").path("rewardId").asText();
|
||||
Assertions.assertThat(unusedRewardId).isNotBlank();
|
||||
rewardIdsToCleanup.add(unusedRewardId);
|
||||
|
||||
mockMvc.perform(get("/wx/blind-box/reward/list")
|
||||
.param("status", "UNUSED")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CUSTOM_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + customerToken))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200))
|
||||
.andExpect(jsonPath("$.data[*].id").value(org.hamcrest.Matchers.hasItem(unusedRewardId)))
|
||||
.andExpect(jsonPath("$.data[*].id").value(org.hamcrest.Matchers.not(org.hamcrest.Matchers.hasItem(usedRewardId))));
|
||||
|
||||
mockMvc.perform(get("/wx/blind-box/reward/list")
|
||||
.param("status", "USED")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CUSTOM_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + customerToken))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200))
|
||||
.andExpect(jsonPath("$.data[*].id").value(org.hamcrest.Matchers.hasItem(usedRewardId)))
|
||||
.andExpect(jsonPath("$.data[*].id").value(org.hamcrest.Matchers.not(org.hamcrest.Matchers.hasItem(unusedRewardId))));
|
||||
|
||||
BlindBoxRewardEntity storedUsed = blindBoxRewardMapper.selectById(usedRewardId);
|
||||
Assertions.assertThat(storedUsed).isNotNull();
|
||||
Assertions.assertThat(storedUsed.getStatus()).isEqualTo("USED");
|
||||
Assertions.assertThat(storedUsed.getUsedClerkId()).isEqualTo(ApiTestDataSeeder.DEFAULT_CLERK_ID);
|
||||
|
||||
if (Objects.nonNull(poolId)) {
|
||||
blindBoxPoolMapper.deleteById(poolId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -49,7 +49,7 @@ class WxBlindBoxOrderApiTest extends WxCustomOrderApiTestSupport {
|
||||
private IPlayClerkGiftInfoService clerkGiftInfoService;
|
||||
|
||||
@Test
|
||||
void blindBoxPurchaseFailsWhenBalanceInsufficient() throws Exception {
|
||||
void purchaseRejectsWhenBalanceInsufficient__covers_BB_008() throws Exception {
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
String configId = "blind-" + IdUtils.getUuid();
|
||||
try {
|
||||
@@ -99,7 +99,7 @@ class WxBlindBoxOrderApiTest extends WxCustomOrderApiTestSupport {
|
||||
}
|
||||
|
||||
@Test
|
||||
void blindBoxPurchaseAndDispatchSucceedWhenGiftInactive() throws Exception {
|
||||
void purchaseCreatesCompletedOrderAndRewardAndDispatchMarksUsed__covers_BB_004__covers_BB_006() throws Exception {
|
||||
String configId = "blind-inactive-" + IdUtils.getUuid().substring(0, 6);
|
||||
String giftId = "gift-inactive-" + IdUtils.getUuid().substring(0, 6);
|
||||
Long poolId = null;
|
||||
|
||||
@@ -24,6 +24,7 @@ import com.starry.admin.modules.weichat.entity.clerk.PlayClerkUserInfoResultVo;
|
||||
import com.starry.admin.modules.weichat.service.WxTokenService;
|
||||
import com.starry.admin.utils.SecurityUtils;
|
||||
import com.starry.common.constant.Constants;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -210,7 +211,7 @@ class WxClerkAlbumUpdateApiTest extends AbstractApiTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateAlbumRejectsEmptyAlbumPayload() throws Exception {
|
||||
void updateAlbumRejectsEmptyAlbumPayload__covers_CLK_010() throws Exception {
|
||||
ensureTenantContext();
|
||||
String clerkId = ApiTestDataSeeder.DEFAULT_CLERK_ID;
|
||||
String clerkToken = wxTokenService.createWxUserToken(clerkId);
|
||||
@@ -228,18 +229,53 @@ class WxClerkAlbumUpdateApiTest extends AbstractApiTest {
|
||||
.andExpect(status().isOk())
|
||||
.andReturn();
|
||||
|
||||
String body = result.getResponse().getContentAsString();
|
||||
String body = result.getResponse().getContentAsString(StandardCharsets.UTF_8);
|
||||
com.fasterxml.jackson.databind.JsonNode root = new ObjectMapper().readTree(body);
|
||||
assertThat(root.path("code").asInt())
|
||||
.as("empty album should be rejected, response=%s", body)
|
||||
.isEqualTo(500);
|
||||
assertThat(root.path("message").asText())
|
||||
.as("error message for empty album should be present, response=%s", body)
|
||||
.isNotBlank();
|
||||
.as("error message for empty album should be pinned, response=%s", body)
|
||||
.isEqualTo("最少上传一张照片");
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateAlbumAllowsMixedLegacyUrlsAndNewMediaIdsForReview() throws Exception {
|
||||
void updateAlbumRejectsInvalidNewMediaOwnershipOrStatus__covers_CLK_011() throws Exception {
|
||||
ensureTenantContext();
|
||||
String clerkId = ApiTestDataSeeder.DEFAULT_CLERK_ID;
|
||||
String clerkToken = wxTokenService.createWxUserToken(clerkId);
|
||||
clerkUserInfoService.updateTokenById(clerkId, clerkToken);
|
||||
|
||||
PlayMediaEntity invalid = new PlayMediaEntity();
|
||||
invalid.setId("media-invalid-" + com.starry.common.utils.IdUtils.getUuid().substring(0, 8));
|
||||
invalid.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
invalid.setOwnerType(com.starry.admin.modules.media.enums.MediaOwnerType.CLERK);
|
||||
invalid.setOwnerId("other-clerk");
|
||||
invalid.setKind("image");
|
||||
invalid.setStatus(com.starry.admin.modules.media.enums.MediaStatus.READY.getCode());
|
||||
invalid.setUrl("https://example.com/invalid.png");
|
||||
mediaService.save(invalid);
|
||||
|
||||
ObjectNode payload = objectMapper.createObjectNode();
|
||||
payload.putArray("album").add(invalid.getId());
|
||||
|
||||
MvcResult result = mockMvc.perform(post("/wx/clerk/user/updateAlbum")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CLERK_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + clerkToken)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(payload.toString()))
|
||||
.andExpect(status().isOk())
|
||||
.andReturn();
|
||||
|
||||
String body = result.getResponse().getContentAsString(StandardCharsets.UTF_8);
|
||||
com.fasterxml.jackson.databind.JsonNode root = new ObjectMapper().readTree(body);
|
||||
assertThat(root.path("code").asInt()).isEqualTo(500);
|
||||
assertThat(root.path("message").asText()).isEqualTo("存在无效的照片/视频,请刷新后重试");
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateAlbumAllowsMixedLegacyUrlsAndNewMediaIdsForReview__covers_CLK_012() throws Exception {
|
||||
ensureTenantContext();
|
||||
String clerkId = ApiTestDataSeeder.DEFAULT_CLERK_ID;
|
||||
String clerkToken = wxTokenService.createWxUserToken(clerkId);
|
||||
@@ -286,6 +322,17 @@ class WxClerkAlbumUpdateApiTest extends AbstractApiTest {
|
||||
assertThat(reviewCountAfter)
|
||||
.as("mixed legacy URLs and new media ids should create exactly one new review record")
|
||||
.isEqualTo(reviewCountBefore + 1);
|
||||
|
||||
ensureTenantContext();
|
||||
com.starry.admin.modules.clerk.module.entity.PlayClerkDataReviewInfoEntity review =
|
||||
dataReviewInfoService.lambdaQuery()
|
||||
.eq(com.starry.admin.modules.clerk.module.entity.PlayClerkDataReviewInfoEntity::getClerkId, clerkId)
|
||||
.eq(com.starry.admin.modules.clerk.module.entity.PlayClerkDataReviewInfoEntity::getDataType, "2")
|
||||
.orderByDesc(com.starry.admin.modules.clerk.module.entity.PlayClerkDataReviewInfoEntity::getCreatedTime)
|
||||
.last("limit 1")
|
||||
.one();
|
||||
assertThat(review).isNotNull();
|
||||
assertThat(review.getDataContent()).contains(legacyUrl1, legacyUrl2, media.getId());
|
||||
}
|
||||
|
||||
private PlayMediaEntity seedMedia(String clerkId) {
|
||||
|
||||
@@ -0,0 +1,488 @@
|
||||
package com.starry.admin.api;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.hamcrest.Matchers.hasSize;
|
||||
import static org.mockito.Mockito.doNothing;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
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 cn.hutool.crypto.SecureUtil;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import com.starry.admin.common.apitest.ApiTestDataSeeder;
|
||||
import com.starry.admin.common.exception.ServiceException;
|
||||
import com.starry.admin.modules.clerk.module.entity.PlayClerkDataReviewInfoEntity;
|
||||
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;
|
||||
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserReviewInfoEntity;
|
||||
import com.starry.admin.modules.clerk.service.IPlayClerkDataReviewInfoService;
|
||||
import com.starry.admin.modules.clerk.service.IPlayClerkUserInfoService;
|
||||
import com.starry.admin.modules.clerk.service.IPlayClerkUserReviewInfoService;
|
||||
import com.starry.admin.modules.order.module.constant.OrderConstant;
|
||||
import com.starry.admin.modules.order.module.entity.PlayOrderEvaluateInfoEntity;
|
||||
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
|
||||
import com.starry.admin.modules.order.service.IPlayOrderEvaluateInfoService;
|
||||
import com.starry.admin.modules.order.service.IPlayOrderInfoService;
|
||||
import com.starry.admin.modules.weichat.service.WxCustomMpService;
|
||||
import com.starry.admin.modules.weichat.service.WxTokenService;
|
||||
import com.starry.admin.utils.SecurityUtils;
|
||||
import com.starry.common.constant.Constants;
|
||||
import com.starry.common.context.CustomSecurityContextHolder;
|
||||
import com.starry.common.redis.RedisCache;
|
||||
import com.starry.common.utils.IdUtils;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.web.servlet.MvcResult;
|
||||
|
||||
class WxClerkControllerUserApiTest extends AbstractApiTest {
|
||||
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
@Autowired
|
||||
private WxTokenService wxTokenService;
|
||||
|
||||
@Autowired
|
||||
private IPlayClerkUserInfoService clerkUserInfoService;
|
||||
|
||||
@Autowired
|
||||
private RedisCache redisCache;
|
||||
|
||||
@Autowired
|
||||
private IPlayClerkDataReviewInfoService dataReviewInfoService;
|
||||
|
||||
@Autowired
|
||||
private IPlayClerkUserReviewInfoService clerkUserReviewInfoService;
|
||||
|
||||
@Autowired
|
||||
private IPlayOrderEvaluateInfoService orderEvaluateInfoService;
|
||||
|
||||
@Autowired
|
||||
private IPlayOrderInfoService orderInfoService;
|
||||
|
||||
@MockBean
|
||||
private WxCustomMpService wxCustomMpService;
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
CustomSecurityContextHolder.remove();
|
||||
}
|
||||
|
||||
@Test
|
||||
void queryPerformanceInfoIsStableForSameInput__covers_CLK_001() throws Exception {
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
String clerkToken = ensureClerkToken(ApiTestDataSeeder.DEFAULT_CLERK_ID);
|
||||
|
||||
String payload = "{\"startTime\":\"2026-01-01\",\"endTime\":\"2026-01-02\"}";
|
||||
MvcResult first = mockMvc.perform(post("/wx/clerk/user/queryPerformanceInfo")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CLERK_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + clerkToken)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(payload))
|
||||
.andExpect(status().isOk())
|
||||
.andReturn();
|
||||
|
||||
MvcResult second = mockMvc.perform(post("/wx/clerk/user/queryPerformanceInfo")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CLERK_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + clerkToken)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(payload))
|
||||
.andExpect(status().isOk())
|
||||
.andReturn();
|
||||
|
||||
assertThat(second.getResponse().getContentAsString(StandardCharsets.UTF_8))
|
||||
.isEqualTo(first.getResponse().getContentAsString(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
@Test
|
||||
void queryLevelInfoReturnsPinnedRankingList__covers_CLK_002() throws Exception {
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
String clerkToken = ensureClerkToken(ApiTestDataSeeder.DEFAULT_CLERK_ID);
|
||||
|
||||
mockMvc.perform(get("/wx/clerk/user/queryLevelInfo")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CLERK_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + clerkToken))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200))
|
||||
.andExpect(jsonPath("$.data.levelAndRanking", hasSize(6)))
|
||||
.andExpect(jsonPath("$.data.levelAndRanking[0].levelName").value("女神"))
|
||||
.andExpect(jsonPath("$.data.levelAndRanking[5].levelName").value("普通"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void sendCodeWritesRedisKeyAndReturnsCode__covers_CLK_003() throws Exception {
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
String clerkToken = ensureClerkToken(ApiTestDataSeeder.DEFAULT_CLERK_ID);
|
||||
|
||||
String areaCode = "+86";
|
||||
String phone = "13900001234";
|
||||
String payload = "{\"areaCode\":\"" + areaCode + "\",\"phone\":\"" + phone + "\"}";
|
||||
|
||||
MvcResult result = mockMvc.perform(post("/wx/clerk/user/sendCode")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CLERK_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + clerkToken)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(payload))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200))
|
||||
.andReturn();
|
||||
|
||||
JsonNode root = objectMapper.readTree(result.getResponse().getContentAsString(StandardCharsets.UTF_8));
|
||||
String code = root.path("data").asText();
|
||||
assertThat(code).hasSize(4);
|
||||
|
||||
String codeKey = "login_codes:" + ApiTestDataSeeder.DEFAULT_TENANT_ID + "_" + SecureUtil.md5(areaCode + phone);
|
||||
String stored = redisCache.getCacheObject(codeKey);
|
||||
assertThat(stored).isEqualTo(code);
|
||||
|
||||
Long ttlSeconds = redisCache.redisTemplate.getExpire(codeKey, TimeUnit.SECONDS);
|
||||
assertThat(ttlSeconds).isNotNull();
|
||||
assertThat(ttlSeconds).isGreaterThan(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
void bindCodeRejectsWrongCode__covers_CLK_004() throws Exception {
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
String clerkToken = ensureClerkToken(ApiTestDataSeeder.DEFAULT_CLERK_ID);
|
||||
|
||||
String areaCode = "+86";
|
||||
String phone = "13900002222";
|
||||
String codeKey = "login_codes:" + ApiTestDataSeeder.DEFAULT_TENANT_ID + "_" + SecureUtil.md5(areaCode + phone);
|
||||
redisCache.setCacheObject(codeKey, "1234", 5L, TimeUnit.MINUTES);
|
||||
|
||||
String payload = "{\"areaCode\":\"" + areaCode + "\",\"phone\":\"" + phone + "\",\"code\":\"0000\"}";
|
||||
mockMvc.perform(post("/wx/clerk/user/bindCode")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CLERK_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + clerkToken)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(payload))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(500))
|
||||
.andExpect(jsonPath("$.message").value("验证码错误"));
|
||||
|
||||
assertThat(redisCache.<String>getCacheObject(codeKey)).isEqualTo("1234");
|
||||
}
|
||||
|
||||
@Test
|
||||
void bindCodeUpdatesPhoneAndClearsRedis__covers_CLK_005() throws Exception {
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
String clerkToken = ensureClerkToken(ApiTestDataSeeder.DEFAULT_CLERK_ID);
|
||||
|
||||
PlayClerkUserInfoEntity before = clerkUserInfoService.getById(ApiTestDataSeeder.DEFAULT_CLERK_ID);
|
||||
assertThat(before).isNotNull();
|
||||
String originalPhone = before.getPhone();
|
||||
|
||||
String areaCode = "+86";
|
||||
String phone = "13900003333";
|
||||
String codeKey = "login_codes:" + ApiTestDataSeeder.DEFAULT_TENANT_ID + "_" + SecureUtil.md5(areaCode + phone);
|
||||
redisCache.setCacheObject(codeKey, "9999", 5L, TimeUnit.MINUTES);
|
||||
|
||||
try {
|
||||
String payload = "{\"areaCode\":\"" + areaCode + "\",\"phone\":\"" + phone + "\",\"code\":\"9999\"}";
|
||||
mockMvc.perform(post("/wx/clerk/user/bindCode")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CLERK_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + clerkToken)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(payload))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200))
|
||||
.andExpect(jsonPath("$.data").value("成功"));
|
||||
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
PlayClerkUserInfoEntity after = clerkUserInfoService.getById(ApiTestDataSeeder.DEFAULT_CLERK_ID);
|
||||
assertThat(after.getPhone()).isEqualTo(phone);
|
||||
assertThat(redisCache.<String>getCacheObject(codeKey)).isNull();
|
||||
} finally {
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
PlayClerkUserInfoEntity patch = new PlayClerkUserInfoEntity();
|
||||
patch.setId(ApiTestDataSeeder.DEFAULT_CLERK_ID);
|
||||
patch.setPhone(originalPhone);
|
||||
clerkUserInfoService.updateById(patch);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void userAddRejectsWhenAlreadyClerk__covers_CLK_006() throws Exception {
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
String clerkToken = ensureClerkToken(ApiTestDataSeeder.DEFAULT_CLERK_ID);
|
||||
doNothing().when(wxCustomMpService).checkSubscribeThrowsExp(org.mockito.ArgumentMatchers.anyString(),
|
||||
org.mockito.ArgumentMatchers.anyString());
|
||||
|
||||
ObjectNode payload = objectMapper.createObjectNode();
|
||||
payload.put("nickname", "申请人");
|
||||
payload.put("sex", "2");
|
||||
payload.put("age", 18);
|
||||
payload.put("weiChatCode", "wx-apply");
|
||||
payload.put("province", "Guangdong");
|
||||
payload.put("city", "Shenzhen");
|
||||
payload.put("audio", "https://oss.example/audio.mp3");
|
||||
payload.set("album", objectMapper.createArrayNode());
|
||||
|
||||
mockMvc.perform(post("/wx/clerk/user/add")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CLERK_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + clerkToken)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(payload.toString()))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(500))
|
||||
.andExpect(jsonPath("$.message").value("当前用户已经是店员"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void userAddRejectsWhenPendingReviewExists__covers_CLK_007() throws Exception {
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
doNothing().when(wxCustomMpService).checkSubscribeThrowsExp(org.mockito.ArgumentMatchers.anyString(),
|
||||
org.mockito.ArgumentMatchers.anyString());
|
||||
|
||||
String clerkId = "clerk-pending-" + IdUtils.getUuid().substring(0, 8);
|
||||
ensureClerk(clerkId, "openid-" + clerkId, "0", "");
|
||||
String clerkToken = ensureClerkToken(clerkId);
|
||||
|
||||
PlayClerkUserReviewInfoEntity review = new PlayClerkUserReviewInfoEntity();
|
||||
review.setId("review-" + IdUtils.getUuid().substring(0, 8));
|
||||
review.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
review.setClerkId(clerkId);
|
||||
review.setReviewState("0");
|
||||
review.setNickname("pending");
|
||||
review.setSex("2");
|
||||
review.setAge(18);
|
||||
review.setProvince("GD");
|
||||
review.setCity("SZ");
|
||||
review.setWeiChatCode("wx");
|
||||
review.setAudio("https://oss.example/audio.mp3");
|
||||
review.setAddTime(LocalDateTime.now());
|
||||
clerkUserReviewInfoService.save(review);
|
||||
|
||||
ObjectNode payload = objectMapper.createObjectNode();
|
||||
payload.put("nickname", "申请人");
|
||||
payload.put("sex", "2");
|
||||
payload.put("age", 18);
|
||||
payload.put("weiChatCode", "wx-apply");
|
||||
payload.put("province", "Guangdong");
|
||||
payload.put("city", "Shenzhen");
|
||||
payload.put("audio", "https://oss.example/audio.mp3");
|
||||
payload.set("album", objectMapper.createArrayNode());
|
||||
|
||||
mockMvc.perform(post("/wx/clerk/user/add")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CLERK_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + clerkToken)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(payload.toString()))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(500))
|
||||
.andExpect(jsonPath("$.message").value("已有申请未审核"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void userAddRequiresSubscribe__covers_CLK_008() throws Exception {
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
String clerkId = "clerk-unsub-" + IdUtils.getUuid().substring(0, 8);
|
||||
ensureClerk(clerkId, "openid-" + clerkId, "0", "");
|
||||
String clerkToken = ensureClerkToken(clerkId);
|
||||
|
||||
doThrow(new ServiceException("请先关注公众号然后再来使用系统~"))
|
||||
.when(wxCustomMpService)
|
||||
.checkSubscribeThrowsExp(org.mockito.ArgumentMatchers.anyString(), org.mockito.ArgumentMatchers.anyString());
|
||||
|
||||
ObjectNode payload = objectMapper.createObjectNode();
|
||||
payload.put("nickname", "申请人");
|
||||
payload.put("sex", "2");
|
||||
payload.put("age", 18);
|
||||
payload.put("weiChatCode", "wx-apply");
|
||||
payload.put("province", "Guangdong");
|
||||
payload.put("city", "Shenzhen");
|
||||
payload.put("audio", "https://oss.example/audio.mp3");
|
||||
payload.set("album", objectMapper.createArrayNode());
|
||||
|
||||
mockMvc.perform(post("/wx/clerk/user/add")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CLERK_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + clerkToken)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(payload.toString()))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(500))
|
||||
.andExpect(jsonPath("$.message").value("请先关注公众号然后再来使用系统~"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateNicknameCreatesDataReviewRow__covers_CLK_009() throws Exception {
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
String clerkToken = ensureClerkToken(ApiTestDataSeeder.DEFAULT_CLERK_ID);
|
||||
|
||||
String nickname = "nick-" + IdUtils.getUuid().substring(0, 6);
|
||||
String payload = "{\"nickname\":\"" + nickname + "\"}";
|
||||
|
||||
mockMvc.perform(post("/wx/clerk/user/updateNickname")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CLERK_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + clerkToken)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(payload))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200));
|
||||
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
PlayClerkDataReviewInfoEntity latest = dataReviewInfoService.lambdaQuery()
|
||||
.eq(PlayClerkDataReviewInfoEntity::getClerkId, ApiTestDataSeeder.DEFAULT_CLERK_ID)
|
||||
.eq(PlayClerkDataReviewInfoEntity::getDataType, "0")
|
||||
.orderByDesc(PlayClerkDataReviewInfoEntity::getCreatedTime)
|
||||
.last("limit 1")
|
||||
.one();
|
||||
assertThat(latest).isNotNull();
|
||||
assertThat(latest.getReviewState()).isEqualTo("0");
|
||||
assertThat(latest.getDataContent()).isEqualTo(List.of(nickname));
|
||||
}
|
||||
|
||||
@Test
|
||||
void queryEvaluateByPageForcesHiddenVisible__covers_CLK_020() throws Exception {
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
|
||||
String visibleOrderId = "ordevl-v-" + IdUtils.getUuid().substring(0, 8);
|
||||
String hiddenOrderId = "ordevl-h-" + IdUtils.getUuid().substring(0, 8);
|
||||
String visibleId = "eval-visible-" + IdUtils.getUuid().substring(0, 8);
|
||||
String hiddenId = "eval-hidden-" + IdUtils.getUuid().substring(0, 8);
|
||||
String keyword = "kw-" + IdUtils.getUuid().substring(0, 6);
|
||||
try {
|
||||
PlayOrderInfoEntity visibleOrder = new PlayOrderInfoEntity();
|
||||
visibleOrder.setId(visibleOrderId);
|
||||
visibleOrder.setOrderNo("EVAL-" + IdUtils.getUuid().substring(0, 4));
|
||||
visibleOrder.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
visibleOrder.setOrderStatus(OrderConstant.OrderStatus.COMPLETED.getCode());
|
||||
visibleOrder.setOrderType(OrderConstant.OrderType.NORMAL.getCode());
|
||||
visibleOrder.setPlaceType(OrderConstant.PlaceType.RANDOM.getCode());
|
||||
visibleOrder.setPurchaserBy(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID);
|
||||
visibleOrder.setAcceptBy(ApiTestDataSeeder.DEFAULT_CLERK_ID);
|
||||
visibleOrder.setOrderMoney(new java.math.BigDecimal("99.00"));
|
||||
visibleOrder.setFinalAmount(new java.math.BigDecimal("99.00"));
|
||||
visibleOrder.setPayMethod("0");
|
||||
visibleOrder.setPayState("1");
|
||||
visibleOrder.setCommodityId("svc-basic");
|
||||
visibleOrder.setCommodityType("1");
|
||||
visibleOrder.setCommodityName("Weixin Order");
|
||||
visibleOrder.setCommodityPrice(new java.math.BigDecimal("99.00"));
|
||||
visibleOrder.setCommodityNumber("1");
|
||||
visibleOrder.setServiceDuration("60min");
|
||||
visibleOrder.setCreatedTime(new java.util.Date());
|
||||
visibleOrder.setUpdatedTime(new java.util.Date());
|
||||
orderInfoService.save(visibleOrder);
|
||||
|
||||
PlayOrderInfoEntity hiddenOrder = new PlayOrderInfoEntity();
|
||||
hiddenOrder.setId(hiddenOrderId);
|
||||
hiddenOrder.setOrderNo("EVAL-" + IdUtils.getUuid().substring(0, 4));
|
||||
hiddenOrder.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
hiddenOrder.setOrderStatus(OrderConstant.OrderStatus.COMPLETED.getCode());
|
||||
hiddenOrder.setOrderType(OrderConstant.OrderType.NORMAL.getCode());
|
||||
hiddenOrder.setPlaceType(OrderConstant.PlaceType.RANDOM.getCode());
|
||||
hiddenOrder.setPurchaserBy(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID);
|
||||
hiddenOrder.setAcceptBy(ApiTestDataSeeder.DEFAULT_CLERK_ID);
|
||||
hiddenOrder.setOrderMoney(new java.math.BigDecimal("99.00"));
|
||||
hiddenOrder.setFinalAmount(new java.math.BigDecimal("99.00"));
|
||||
hiddenOrder.setPayMethod("0");
|
||||
hiddenOrder.setPayState("1");
|
||||
hiddenOrder.setCommodityId("svc-basic");
|
||||
hiddenOrder.setCommodityType("1");
|
||||
hiddenOrder.setCommodityName("Weixin Order");
|
||||
hiddenOrder.setCommodityPrice(new java.math.BigDecimal("99.00"));
|
||||
hiddenOrder.setCommodityNumber("1");
|
||||
hiddenOrder.setServiceDuration("60min");
|
||||
hiddenOrder.setCreatedTime(new java.util.Date());
|
||||
hiddenOrder.setUpdatedTime(new java.util.Date());
|
||||
orderInfoService.save(hiddenOrder);
|
||||
|
||||
PlayOrderEvaluateInfoEntity visible = new PlayOrderEvaluateInfoEntity();
|
||||
visible.setId(visibleId);
|
||||
visible.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
visible.setClerkId(ApiTestDataSeeder.DEFAULT_CLERK_ID);
|
||||
visible.setCustomId(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID);
|
||||
visible.setOrderId(visibleOrderId);
|
||||
visible.setAnonymous("0");
|
||||
visible.setEvaluateType("0");
|
||||
visible.setEvaluateLevel(5);
|
||||
visible.setEvaluateCon(keyword);
|
||||
visible.setEvaluateTime(new java.util.Date());
|
||||
visible.setHidden("0");
|
||||
orderEvaluateInfoService.save(visible);
|
||||
|
||||
PlayOrderEvaluateInfoEntity hidden = new PlayOrderEvaluateInfoEntity();
|
||||
hidden.setId(hiddenId);
|
||||
hidden.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
hidden.setClerkId(ApiTestDataSeeder.DEFAULT_CLERK_ID);
|
||||
hidden.setCustomId(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID);
|
||||
hidden.setOrderId(hiddenOrderId);
|
||||
hidden.setAnonymous("0");
|
||||
hidden.setEvaluateType("0");
|
||||
hidden.setEvaluateLevel(1);
|
||||
hidden.setEvaluateCon(keyword);
|
||||
hidden.setEvaluateTime(new java.util.Date());
|
||||
hidden.setHidden("1");
|
||||
orderEvaluateInfoService.save(hidden);
|
||||
|
||||
String payload = "{\"pageNum\":1,\"pageSize\":20,\"clerkId\":\"" + ApiTestDataSeeder.DEFAULT_CLERK_ID + "\","
|
||||
+ "\"hidden\":\"1\",\"evaluateCon\":\"" + keyword + "\"}";
|
||||
MvcResult result = mockMvc.perform(post("/wx/clerk/user/queryEvaluateByPage")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(payload))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200))
|
||||
.andReturn();
|
||||
|
||||
JsonNode records = objectMapper.readTree(result.getResponse().getContentAsString(StandardCharsets.UTF_8))
|
||||
.path("data");
|
||||
assertThat(records)
|
||||
.anyMatch(node -> visibleId.equals(node.path("id").asText()))
|
||||
.noneMatch(node -> hiddenId.equals(node.path("id").asText()));
|
||||
} finally {
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
orderEvaluateInfoService.removeById(visibleId);
|
||||
orderEvaluateInfoService.removeById(hiddenId);
|
||||
orderInfoService.removeById(visibleOrderId);
|
||||
orderInfoService.removeById(hiddenOrderId);
|
||||
}
|
||||
}
|
||||
|
||||
private String ensureClerkToken(String clerkId) {
|
||||
String token = wxTokenService.createWxUserToken(clerkId);
|
||||
clerkUserInfoService.updateTokenById(clerkId, token);
|
||||
return token;
|
||||
}
|
||||
|
||||
private void ensureClerk(String clerkId, String openid, String clerkState, String sysUserId) {
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
PlayClerkUserInfoEntity patch = new PlayClerkUserInfoEntity();
|
||||
patch.setId(clerkId);
|
||||
patch.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
patch.setOpenid(openid);
|
||||
patch.setNickname("API Clerk " + clerkId);
|
||||
patch.setSysUserId(sysUserId);
|
||||
patch.setClerkState(clerkState);
|
||||
patch.setOnboardingState("1");
|
||||
patch.setListingState("1");
|
||||
patch.setOnlineState("1");
|
||||
if (clerkUserInfoService.getById(clerkId) == null) {
|
||||
clerkUserInfoService.save(patch);
|
||||
} else {
|
||||
patch.setDeleted(Boolean.FALSE);
|
||||
clerkUserInfoService.updateById(patch);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.starry.admin.api;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
@@ -31,9 +32,11 @@ import com.starry.admin.modules.weichat.service.WxTokenService;
|
||||
import com.starry.admin.utils.SecurityUtils;
|
||||
import com.starry.common.constant.Constants;
|
||||
import com.starry.common.enums.ClerkReviewState;
|
||||
import com.starry.common.utils.IdUtils;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Comparator;
|
||||
@@ -43,6 +46,7 @@ import javax.imageio.ImageIO;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.dao.DuplicateKeyException;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.mock.web.MockMultipartFile;
|
||||
import org.springframework.test.web.servlet.MvcResult;
|
||||
@@ -73,7 +77,31 @@ class WxClerkMediaControllerApiTest extends AbstractApiTest {
|
||||
private IOssFileService ossFileService;
|
||||
|
||||
@Test
|
||||
void clerkCanUploadImageMediaAndPersistUrl() throws Exception {
|
||||
void uploadRejectsEmptyFile__covers_MED_001() throws Exception {
|
||||
ensureTenantContext();
|
||||
String clerkToken = wxTokenService.createWxUserToken(ApiTestDataSeeder.DEFAULT_CLERK_ID);
|
||||
clerkUserInfoService.updateTokenById(ApiTestDataSeeder.DEFAULT_CLERK_ID, clerkToken);
|
||||
|
||||
MockMultipartFile emptyFile = new MockMultipartFile(
|
||||
"file",
|
||||
"empty.png",
|
||||
"image/png",
|
||||
new byte[0]);
|
||||
|
||||
mockMvc.perform(multipart("/wx/clerk/media/upload")
|
||||
.file(emptyFile)
|
||||
.param("usage", "profile")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CLERK_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + clerkToken)
|
||||
.contentType(MediaType.MULTIPART_FORM_DATA))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(500))
|
||||
.andExpect(jsonPath("$.message").value("请选择要上传的文件"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void clerkCanUploadImageMediaAndPersistUrl__covers_MED_002() throws Exception {
|
||||
ensureTenantContext();
|
||||
String clerkToken = wxTokenService.createWxUserToken(ApiTestDataSeeder.DEFAULT_CLERK_ID);
|
||||
clerkUserInfoService.updateTokenById(ApiTestDataSeeder.DEFAULT_CLERK_ID, clerkToken);
|
||||
@@ -119,6 +147,104 @@ class WxClerkMediaControllerApiTest extends AbstractApiTest {
|
||||
assertThat(persisted.getUrl()).isEqualTo(ossUrl);
|
||||
}
|
||||
|
||||
@Test
|
||||
void uploadRejectsOversizedVideo__covers_MED_003() throws Exception {
|
||||
ensureTenantContext();
|
||||
String clerkToken = wxTokenService.createWxUserToken(ApiTestDataSeeder.DEFAULT_CLERK_ID);
|
||||
clerkUserInfoService.updateTokenById(ApiTestDataSeeder.DEFAULT_CLERK_ID, clerkToken);
|
||||
|
||||
byte[] oversized = new byte[(int) (30L * 1024 * 1024 + 1)];
|
||||
MockMultipartFile file = new MockMultipartFile(
|
||||
"file",
|
||||
"big.mp4",
|
||||
"video/mp4",
|
||||
oversized);
|
||||
|
||||
mockMvc.perform(multipart("/wx/clerk/media/upload")
|
||||
.file(file)
|
||||
.param("usage", "profile")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CLERK_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + clerkToken)
|
||||
.contentType(MediaType.MULTIPART_FORM_DATA))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(500))
|
||||
.andExpect(jsonPath("$.message").value("视频大小不能超过30MB"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void uploadRejectsVideoDurationOver45Seconds__covers_MED_004() throws Exception {
|
||||
ensureTenantContext();
|
||||
String clerkToken = wxTokenService.createWxUserToken(ApiTestDataSeeder.DEFAULT_CLERK_ID);
|
||||
clerkUserInfoService.updateTokenById(ApiTestDataSeeder.DEFAULT_CLERK_ID, clerkToken);
|
||||
|
||||
Path videoPath = Files.createTempFile("apitest-long-video-", ".mp4");
|
||||
try {
|
||||
Process process = new ProcessBuilder(
|
||||
"ffmpeg",
|
||||
"-y",
|
||||
"-f",
|
||||
"lavfi",
|
||||
"-i",
|
||||
"color=c=black:s=16x16:r=1",
|
||||
"-t",
|
||||
"46",
|
||||
"-pix_fmt",
|
||||
"yuv420p",
|
||||
videoPath.toString())
|
||||
.redirectErrorStream(true)
|
||||
.start();
|
||||
int exitCode = process.waitFor();
|
||||
if (exitCode != 0) {
|
||||
throw new AssertionError("ffmpeg failed with exitCode=" + exitCode + ": "
|
||||
+ new String(process.getInputStream().readAllBytes()));
|
||||
}
|
||||
|
||||
MockMultipartFile file = new MockMultipartFile(
|
||||
"file",
|
||||
"long.mp4",
|
||||
"video/mp4",
|
||||
Files.readAllBytes(videoPath));
|
||||
|
||||
mockMvc.perform(multipart("/wx/clerk/media/upload")
|
||||
.file(file)
|
||||
.param("usage", "profile")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CLERK_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + clerkToken)
|
||||
.contentType(MediaType.MULTIPART_FORM_DATA))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(500))
|
||||
.andExpect(jsonPath("$.message").value("视频时长不能超过45秒"));
|
||||
} finally {
|
||||
Files.deleteIfExists(videoPath);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void uploadPinsDbUniqueConstraintOnClerkUsageMedia__covers_MED_009() throws Exception {
|
||||
ensureTenantContext();
|
||||
String clerkToken = wxTokenService.createWxUserToken(ApiTestDataSeeder.DEFAULT_CLERK_ID);
|
||||
clerkUserInfoService.updateTokenById(ApiTestDataSeeder.DEFAULT_CLERK_ID, clerkToken);
|
||||
|
||||
when(ossFileService.upload(any(), eq(ApiTestDataSeeder.DEFAULT_TENANT_ID), anyString()))
|
||||
.thenReturn("https://oss.mock/apitest/dup.png");
|
||||
String mediaId = extractMediaIdFromUpload(buildTinyPng("dup.png"), clerkToken);
|
||||
assertThat(mediaId).isNotBlank();
|
||||
|
||||
PlayClerkMediaAssetEntity duplicate = new PlayClerkMediaAssetEntity();
|
||||
duplicate.setId("asset-dup-" + IdUtils.getUuid().substring(0, 8));
|
||||
duplicate.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
duplicate.setClerkId(ApiTestDataSeeder.DEFAULT_CLERK_ID);
|
||||
duplicate.setUsage(ClerkMediaUsage.PROFILE.getCode());
|
||||
duplicate.setMediaId(mediaId);
|
||||
duplicate.setReviewState(ClerkMediaReviewState.DRAFT.getCode());
|
||||
duplicate.setDeleted(false);
|
||||
|
||||
assertThatThrownBy(() -> mediaAssetService.save(duplicate))
|
||||
.isInstanceOf(DuplicateKeyException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void clerkCanUploadVideoMediaAndPersistUrl() throws Exception {
|
||||
ensureTenantContext();
|
||||
|
||||
@@ -0,0 +1,276 @@
|
||||
package com.starry.admin.api;
|
||||
|
||||
import static org.hamcrest.Matchers.containsInAnyOrder;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
|
||||
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.ObjectMapper;
|
||||
import com.starry.admin.common.apitest.ApiTestDataSeeder;
|
||||
import com.starry.admin.common.oss.service.IOssFileService;
|
||||
import com.starry.admin.modules.clerk.enums.ClerkMediaReviewState;
|
||||
import com.starry.admin.modules.clerk.module.entity.PlayClerkMediaAssetEntity;
|
||||
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;
|
||||
import com.starry.admin.modules.clerk.service.IPlayClerkMediaAssetService;
|
||||
import com.starry.admin.modules.clerk.service.IPlayClerkUserInfoService;
|
||||
import com.starry.admin.modules.media.entity.PlayMediaEntity;
|
||||
import com.starry.admin.modules.media.enums.MediaOwnerType;
|
||||
import com.starry.admin.modules.media.enums.MediaStatus;
|
||||
import com.starry.admin.modules.media.service.IPlayMediaService;
|
||||
import com.starry.admin.modules.weichat.service.WxTokenService;
|
||||
import com.starry.admin.utils.SecurityUtils;
|
||||
import com.starry.common.constant.Constants;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.util.List;
|
||||
import javax.imageio.ImageIO;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.mock.web.MockMultipartFile;
|
||||
import org.springframework.test.web.servlet.MvcResult;
|
||||
|
||||
class WxClerkMediaControllerEndpointsApiTest extends AbstractApiTest {
|
||||
|
||||
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
|
||||
|
||||
@Autowired
|
||||
private WxTokenService wxTokenService;
|
||||
|
||||
@Autowired
|
||||
private IPlayClerkUserInfoService clerkUserInfoService;
|
||||
|
||||
@Autowired
|
||||
private IPlayClerkMediaAssetService clerkMediaAssetService;
|
||||
|
||||
@Autowired
|
||||
private IPlayMediaService mediaService;
|
||||
|
||||
@MockBean
|
||||
private IOssFileService ossFileService;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateOrderDistinctsMediaIdsAndPersistsOrderIndex__covers_MED_005() throws Exception {
|
||||
String suffix = Long.toString(System.nanoTime(), 36);
|
||||
String clerkId = ensureActiveClerk("cm-end-" + suffix, "oid-cm-end-" + suffix);
|
||||
String clerkToken = wxTokenService.createWxUserToken(clerkId);
|
||||
clerkUserInfoService.updateTokenById(clerkId, clerkToken);
|
||||
|
||||
when(ossFileService.upload(any(), eq(ApiTestDataSeeder.DEFAULT_TENANT_ID), anyString()))
|
||||
.thenReturn(
|
||||
"https://oss.mock/apitest/media-a.png",
|
||||
"https://oss.mock/apitest/media-b.png");
|
||||
|
||||
String mediaIdA = uploadTinyPng("media-a.png", clerkToken);
|
||||
String mediaIdB = uploadTinyPng("media-b.png", clerkToken);
|
||||
|
||||
String payload = "{\"usage\":\"profile\",\"mediaIds\":[\"" + mediaIdB + "\",\"" + mediaIdA + "\",\"" + mediaIdB
|
||||
+ "\"]}";
|
||||
mockMvc.perform(put("/wx/clerk/media/order")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CLERK_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + clerkToken)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(payload))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200));
|
||||
|
||||
List<PlayClerkMediaAssetEntity> assets = clerkMediaAssetService.lambdaQuery()
|
||||
.eq(PlayClerkMediaAssetEntity::getClerkId, clerkId)
|
||||
.eq(PlayClerkMediaAssetEntity::getUsage, "profile")
|
||||
.eq(PlayClerkMediaAssetEntity::getDeleted, false)
|
||||
.in(PlayClerkMediaAssetEntity::getMediaId, List.of(mediaIdA, mediaIdB))
|
||||
.list();
|
||||
if (assets.size() != 2) {
|
||||
throw new AssertionError("Expected exactly 2 active assets after distinct ordering");
|
||||
}
|
||||
PlayClerkMediaAssetEntity assetA = assets.stream().filter(a -> mediaIdA.equals(a.getMediaId())).findFirst()
|
||||
.orElseThrow();
|
||||
PlayClerkMediaAssetEntity assetB = assets.stream().filter(a -> mediaIdB.equals(a.getMediaId())).findFirst()
|
||||
.orElseThrow();
|
||||
if (!Integer.valueOf(1).equals(assetA.getOrderIndex())) {
|
||||
throw new AssertionError("Expected orderIndex=1 for media-a");
|
||||
}
|
||||
if (!Integer.valueOf(0).equals(assetB.getOrderIndex())) {
|
||||
throw new AssertionError("Expected orderIndex=0 for media-b");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void deleteMarksAssetRejectedAndKeepsDeletedFalse__covers_MED_006() throws Exception {
|
||||
String suffix = Long.toString(System.nanoTime(), 36);
|
||||
String clerkId = ensureActiveClerk("cm-del-" + suffix, "oid-cm-del-" + suffix);
|
||||
String clerkToken = wxTokenService.createWxUserToken(clerkId);
|
||||
clerkUserInfoService.updateTokenById(clerkId, clerkToken);
|
||||
|
||||
when(ossFileService.upload(any(), eq(ApiTestDataSeeder.DEFAULT_TENANT_ID), anyString()))
|
||||
.thenReturn("https://oss.mock/apitest/media-delete.png");
|
||||
|
||||
String mediaId = uploadTinyPng("media-delete.png", clerkToken);
|
||||
|
||||
PlayClerkMediaAssetEntity beforeDelete = clerkMediaAssetService.lambdaQuery()
|
||||
.eq(PlayClerkMediaAssetEntity::getClerkId, clerkId)
|
||||
.eq(PlayClerkMediaAssetEntity::getMediaId, mediaId)
|
||||
.one();
|
||||
if (beforeDelete == null) {
|
||||
throw new AssertionError("Expected clerk asset to exist before delete");
|
||||
}
|
||||
if (beforeDelete.getDeleted() == null) {
|
||||
beforeDelete.setDeleted(false);
|
||||
clerkMediaAssetService.updateById(beforeDelete);
|
||||
}
|
||||
|
||||
mockMvc.perform(delete("/wx/clerk/media/" + mediaId)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CLERK_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + clerkToken))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200));
|
||||
|
||||
List<PlayClerkMediaAssetEntity> assetsAfterDelete = clerkMediaAssetService.lambdaQuery()
|
||||
.eq(PlayClerkMediaAssetEntity::getClerkId, clerkId)
|
||||
.eq(PlayClerkMediaAssetEntity::getMediaId, mediaId)
|
||||
.list();
|
||||
if (assetsAfterDelete.isEmpty()) {
|
||||
throw new AssertionError("Expected clerk asset to exist");
|
||||
}
|
||||
boolean anyRejected = assetsAfterDelete.stream()
|
||||
.anyMatch(item -> ClerkMediaReviewState.REJECTED.getCode().equals(item.getReviewState()));
|
||||
if (!anyRejected) {
|
||||
throw new AssertionError("Expected asset.reviewState=rejected; got: "
|
||||
+ assetsAfterDelete.stream()
|
||||
.map(item -> item.getId() + "(deleted=" + item.getDeleted() + ", state="
|
||||
+ item.getReviewState() + ")")
|
||||
.collect(java.util.stream.Collectors.toList()));
|
||||
}
|
||||
boolean anyDeleted = assetsAfterDelete.stream().anyMatch(item -> Boolean.TRUE.equals(item.getDeleted()));
|
||||
if (anyDeleted) {
|
||||
throw new AssertionError("Expected asset.deleted to remain false; got: "
|
||||
+ assetsAfterDelete.stream()
|
||||
.map(item -> item.getId() + "(deleted=" + item.getDeleted() + ", state="
|
||||
+ item.getReviewState() + ")")
|
||||
.collect(java.util.stream.Collectors.toList()));
|
||||
}
|
||||
|
||||
PlayMediaEntity media = mediaService.getById(mediaId);
|
||||
if (media == null) {
|
||||
throw new AssertionError("Expected media to exist");
|
||||
}
|
||||
if (!MediaStatus.REJECTED.getCode().equals(media.getStatus())) {
|
||||
throw new AssertionError("Expected media.status=rejected after delete");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void listDraftReturnsOnlyDraftPendingRejected__covers_MED_007__covers_MED_008() throws Exception {
|
||||
String suffix = Long.toString(System.nanoTime(), 36);
|
||||
String clerkId = ensureActiveClerk("cm-lst-" + suffix, "oid-cm-lst-" + suffix);
|
||||
String clerkToken = wxTokenService.createWxUserToken(clerkId);
|
||||
clerkUserInfoService.updateTokenById(clerkId, clerkToken);
|
||||
|
||||
when(ossFileService.upload(any(), eq(ApiTestDataSeeder.DEFAULT_TENANT_ID), anyString()))
|
||||
.thenReturn(
|
||||
"https://oss.mock/apitest/media-draft.png",
|
||||
"https://oss.mock/apitest/media-approved.png");
|
||||
|
||||
String draftMediaId = uploadTinyPng("media-draft.png", clerkToken);
|
||||
String approvedMediaId = uploadTinyPng("media-approved.png", clerkToken);
|
||||
|
||||
List<PlayClerkMediaAssetEntity> assets = clerkMediaAssetService.lambdaQuery()
|
||||
.eq(PlayClerkMediaAssetEntity::getClerkId, clerkId)
|
||||
.eq(PlayClerkMediaAssetEntity::getUsage, "profile")
|
||||
.eq(PlayClerkMediaAssetEntity::getDeleted, false)
|
||||
.in(PlayClerkMediaAssetEntity::getMediaId, List.of(draftMediaId, approvedMediaId))
|
||||
.list();
|
||||
PlayClerkMediaAssetEntity draftAsset = assets.stream().filter(a -> draftMediaId.equals(a.getMediaId()))
|
||||
.findFirst().orElseThrow();
|
||||
PlayClerkMediaAssetEntity approvedAsset = assets.stream().filter(a -> approvedMediaId.equals(a.getMediaId()))
|
||||
.findFirst().orElseThrow();
|
||||
|
||||
draftAsset.setReviewState(ClerkMediaReviewState.DRAFT.getCode());
|
||||
approvedAsset.setReviewState(ClerkMediaReviewState.APPROVED.getCode());
|
||||
clerkMediaAssetService.updateBatchById(List.of(draftAsset, approvedAsset));
|
||||
|
||||
mockMvc.perform(get("/wx/clerk/media/list")
|
||||
.param("usage", "profile")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CLERK_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + clerkToken))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200))
|
||||
.andExpect(jsonPath("$.data[*].mediaId").value(containsInAnyOrder(draftMediaId)));
|
||||
|
||||
mockMvc.perform(get("/wx/clerk/media/approved")
|
||||
.param("usage", "profile")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CLERK_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + clerkToken))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200))
|
||||
.andExpect(jsonPath("$.data[*].mediaId").value(containsInAnyOrder(approvedMediaId)));
|
||||
}
|
||||
|
||||
private String ensureActiveClerk(String clerkId, String openId) {
|
||||
PlayClerkUserInfoEntity existing = clerkUserInfoService.getById(clerkId);
|
||||
if (existing == null) {
|
||||
PlayClerkUserInfoEntity entity = new PlayClerkUserInfoEntity();
|
||||
entity.setId(clerkId);
|
||||
entity.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
entity.setOpenid(openId);
|
||||
entity.setNickname("API Test Clerk Media");
|
||||
entity.setAvatar("https://example.com/avatar.png");
|
||||
entity.setSysUserId("");
|
||||
entity.setOnboardingState("1");
|
||||
entity.setListingState("1");
|
||||
entity.setClerkState("1");
|
||||
entity.setOnlineState("1");
|
||||
clerkUserInfoService.save(entity);
|
||||
return clerkId;
|
||||
}
|
||||
PlayClerkUserInfoEntity patch = new PlayClerkUserInfoEntity();
|
||||
patch.setId(clerkId);
|
||||
patch.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
patch.setOpenid(openId);
|
||||
patch.setDeleted(Boolean.FALSE);
|
||||
patch.setSysUserId("");
|
||||
patch.setOnboardingState("1");
|
||||
patch.setListingState("1");
|
||||
patch.setClerkState("1");
|
||||
patch.setOnlineState("1");
|
||||
clerkUserInfoService.updateById(patch);
|
||||
return clerkId;
|
||||
}
|
||||
|
||||
private String uploadTinyPng(String filename, String clerkToken) throws Exception {
|
||||
BufferedImage image = new BufferedImage(2, 2, BufferedImage.TYPE_INT_RGB);
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
ImageIO.write(image, "png", baos);
|
||||
MockMultipartFile file = new MockMultipartFile("file", filename, "image/png", baos.toByteArray());
|
||||
|
||||
MvcResult result = mockMvc.perform(multipart("/wx/clerk/media/upload")
|
||||
.file(file)
|
||||
.param("usage", "profile")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CLERK_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + clerkToken)
|
||||
.contentType(MediaType.MULTIPART_FORM_DATA))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200))
|
||||
.andReturn();
|
||||
return OBJECT_MAPPER.readTree(result.getResponse().getContentAsString()).path("data").path("mediaId").asText();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
package com.starry.admin.api;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.doNothing;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
import com.starry.admin.common.apitest.ApiTestDataSeeder;
|
||||
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;
|
||||
import com.starry.admin.modules.clerk.service.IPlayClerkUserInfoService;
|
||||
import com.starry.admin.modules.order.service.impl.PlayOrderInfoServiceImpl;
|
||||
import com.starry.admin.modules.personnel.module.entity.PlayPersonnelAdminInfoEntity;
|
||||
import com.starry.admin.modules.personnel.module.entity.PlayPersonnelGroupInfoEntity;
|
||||
import com.starry.admin.modules.personnel.service.IPlayPersonnelAdminInfoService;
|
||||
import com.starry.admin.modules.personnel.service.IPlayPersonnelGroupInfoService;
|
||||
import com.starry.admin.modules.weichat.service.WxTokenService;
|
||||
import com.starry.admin.utils.SecurityUtils;
|
||||
import com.starry.common.constant.Constants;
|
||||
import com.starry.common.context.CustomSecurityContextHolder;
|
||||
import com.starry.common.utils.IdUtils;
|
||||
import java.time.LocalDateTime;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.http.MediaType;
|
||||
|
||||
class WxClerkOrderCompleteApiTest extends AbstractApiTest {
|
||||
|
||||
@Autowired
|
||||
private WxTokenService wxTokenService;
|
||||
|
||||
@Autowired
|
||||
private IPlayClerkUserInfoService clerkUserInfoService;
|
||||
|
||||
@Autowired
|
||||
private IPlayPersonnelAdminInfoService personnelAdminInfoService;
|
||||
|
||||
@Autowired
|
||||
private IPlayPersonnelGroupInfoService personnelGroupInfoService;
|
||||
|
||||
@MockBean
|
||||
private PlayOrderInfoServiceImpl orderInfoService;
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
CustomSecurityContextHolder.remove();
|
||||
}
|
||||
|
||||
@Test
|
||||
void completeOrderRejectsWhenSysUserIdMissing__covers_CLK_017() throws Exception {
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
|
||||
String clerkId = "clerk-complete-nosys-" + IdUtils.getUuid().substring(0, 8);
|
||||
ensureClerk(clerkId, "openid-" + clerkId, "");
|
||||
String token = ensureClerkToken(clerkId);
|
||||
|
||||
String payload = "{\"orderId\":\"order-any\",\"remark\":\"\"}";
|
||||
mockMvc.perform(post("/wx/clerk/order/complete")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CLERK_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + token)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(payload))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(500))
|
||||
.andExpect(jsonPath("$.message").value("账号未绑定系统用户,无法完成订单"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void completeOrderChoosesAdminOperatorType__covers_CLK_018() throws Exception {
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
|
||||
String sysUserId = "sys-admin-" + IdUtils.getUuid().substring(0, 8);
|
||||
PlayPersonnelAdminInfoEntity admin = new PlayPersonnelAdminInfoEntity();
|
||||
admin.setId("admin-" + IdUtils.getUuid().substring(0, 8));
|
||||
admin.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
admin.setSysUserId(sysUserId);
|
||||
admin.setAdminName("admin");
|
||||
admin.setAddTime(LocalDateTime.now());
|
||||
personnelAdminInfoService.save(admin);
|
||||
|
||||
String clerkId = "clerk-complete-admin-" + IdUtils.getUuid().substring(0, 8);
|
||||
ensureClerk(clerkId, "openid-" + clerkId, sysUserId);
|
||||
String token = ensureClerkToken(clerkId);
|
||||
|
||||
doNothing().when(orderInfoService).completeOrderByManagement(
|
||||
org.mockito.ArgumentMatchers.anyString(),
|
||||
org.mockito.ArgumentMatchers.anyString(),
|
||||
org.mockito.ArgumentMatchers.anyString(),
|
||||
org.mockito.ArgumentMatchers.any());
|
||||
|
||||
String orderId = "order-admin-" + IdUtils.getUuid().substring(0, 8);
|
||||
String payload = "{\"orderId\":\"" + orderId + "\",\"remark\":\"admin\"}";
|
||||
mockMvc.perform(post("/wx/clerk/order/complete")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CLERK_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + token)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(payload))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200))
|
||||
.andExpect(jsonPath("$.data").value("操作成功"));
|
||||
|
||||
ArgumentCaptor<String> typeCaptor = ArgumentCaptor.forClass(String.class);
|
||||
ArgumentCaptor<String> operatorCaptor = ArgumentCaptor.forClass(String.class);
|
||||
ArgumentCaptor<String> orderCaptor = ArgumentCaptor.forClass(String.class);
|
||||
verify(orderInfoService).completeOrderByManagement(
|
||||
typeCaptor.capture(),
|
||||
operatorCaptor.capture(),
|
||||
orderCaptor.capture(),
|
||||
org.mockito.ArgumentMatchers.any());
|
||||
assertThat(typeCaptor.getValue()).isEqualTo("2");
|
||||
assertThat(operatorCaptor.getValue()).isEqualTo(sysUserId);
|
||||
assertThat(orderCaptor.getValue()).isEqualTo(orderId);
|
||||
}
|
||||
|
||||
@Test
|
||||
void completeOrderChoosesGroupLeaderOperatorType__covers_CLK_018() throws Exception {
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
|
||||
String sysUserId = "sys-leader-" + IdUtils.getUuid().substring(0, 8);
|
||||
PlayPersonnelGroupInfoEntity group = new PlayPersonnelGroupInfoEntity();
|
||||
group.setId("group-" + IdUtils.getUuid().substring(0, 8));
|
||||
group.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
group.setSysUserId(sysUserId);
|
||||
group.setGroupName("group");
|
||||
group.setLeaderName("leader");
|
||||
group.setAddTime(LocalDateTime.now());
|
||||
personnelGroupInfoService.save(group);
|
||||
|
||||
String clerkId = "clerk-complete-leader-" + IdUtils.getUuid().substring(0, 8);
|
||||
ensureClerk(clerkId, "openid-" + clerkId, sysUserId);
|
||||
String token = ensureClerkToken(clerkId);
|
||||
|
||||
doNothing().when(orderInfoService).completeOrderByManagement(
|
||||
org.mockito.ArgumentMatchers.anyString(),
|
||||
org.mockito.ArgumentMatchers.anyString(),
|
||||
org.mockito.ArgumentMatchers.anyString(),
|
||||
org.mockito.ArgumentMatchers.any());
|
||||
|
||||
String orderId = "order-leader-" + IdUtils.getUuid().substring(0, 8);
|
||||
String payload = "{\"orderId\":\"" + orderId + "\",\"remark\":\"leader\"}";
|
||||
mockMvc.perform(post("/wx/clerk/order/complete")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CLERK_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + token)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(payload))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200))
|
||||
.andExpect(jsonPath("$.data").value("操作成功"));
|
||||
|
||||
ArgumentCaptor<String> typeCaptor = ArgumentCaptor.forClass(String.class);
|
||||
ArgumentCaptor<String> operatorCaptor = ArgumentCaptor.forClass(String.class);
|
||||
verify(orderInfoService).completeOrderByManagement(
|
||||
typeCaptor.capture(),
|
||||
operatorCaptor.capture(),
|
||||
org.mockito.ArgumentMatchers.eq(orderId),
|
||||
org.mockito.ArgumentMatchers.any());
|
||||
assertThat(typeCaptor.getValue()).isEqualTo("3");
|
||||
assertThat(operatorCaptor.getValue()).isEqualTo(clerkId);
|
||||
}
|
||||
|
||||
private String ensureClerkToken(String clerkId) {
|
||||
String token = wxTokenService.createWxUserToken(clerkId);
|
||||
clerkUserInfoService.updateTokenById(clerkId, token);
|
||||
return token;
|
||||
}
|
||||
|
||||
private void ensureClerk(String clerkId, String openid, String sysUserId) {
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
PlayClerkUserInfoEntity entity = new PlayClerkUserInfoEntity();
|
||||
entity.setId(clerkId);
|
||||
entity.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
entity.setOpenid(openid);
|
||||
entity.setNickname("API Clerk " + clerkId);
|
||||
entity.setSysUserId(sysUserId);
|
||||
entity.setOnboardingState("1");
|
||||
entity.setListingState("1");
|
||||
entity.setClerkState("1");
|
||||
entity.setOnlineState("1");
|
||||
if (clerkUserInfoService.getById(clerkId) == null) {
|
||||
clerkUserInfoService.save(entity);
|
||||
} else {
|
||||
entity.setDeleted(Boolean.FALSE);
|
||||
clerkUserInfoService.updateById(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
package com.starry.admin.api;
|
||||
|
||||
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.starry.admin.common.apitest.ApiTestDataSeeder;
|
||||
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;
|
||||
import com.starry.admin.modules.clerk.module.entity.PlayClerkWagesInfoEntity;
|
||||
import com.starry.admin.modules.clerk.service.IPlayClerkUserInfoService;
|
||||
import com.starry.admin.modules.clerk.service.IPlayClerkWagesDetailsInfoService;
|
||||
import com.starry.admin.modules.clerk.service.IPlayClerkWagesInfoService;
|
||||
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
|
||||
import com.starry.admin.modules.order.service.IPlayOrderInfoService;
|
||||
import com.starry.admin.modules.weichat.service.WxTokenService;
|
||||
import com.starry.admin.utils.SecurityUtils;
|
||||
import com.starry.common.constant.Constants;
|
||||
import com.starry.common.utils.IdUtils;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.MediaType;
|
||||
|
||||
class WxClerkWagesControllerApiTest extends AbstractApiTest {
|
||||
|
||||
@Autowired
|
||||
private WxTokenService wxTokenService;
|
||||
|
||||
@Autowired
|
||||
private IPlayClerkUserInfoService clerkUserInfoService;
|
||||
|
||||
@Autowired
|
||||
private IPlayOrderInfoService orderInfoService;
|
||||
|
||||
@Autowired
|
||||
private IPlayClerkWagesInfoService wagesInfoService;
|
||||
|
||||
@Autowired
|
||||
private IPlayClerkWagesDetailsInfoService wagesDetailsInfoService;
|
||||
|
||||
private final java.util.List<String> orderIdsToCleanup = new java.util.ArrayList<>();
|
||||
private final java.util.List<String> wagesIdsToCleanup = new java.util.ArrayList<>();
|
||||
private String clerkToken;
|
||||
private String clerkId;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
String suffix = Long.toString(System.nanoTime(), 36);
|
||||
clerkId = "apitest-wage-clerk-" + suffix;
|
||||
String openId = "openid-" + clerkId;
|
||||
clerkToken = wxTokenService.createWxUserToken(clerkId);
|
||||
ensureActiveClerk(clerkId, openId, clerkToken);
|
||||
|
||||
wagesDetailsInfoService.lambdaUpdate()
|
||||
.eq(com.starry.admin.modules.clerk.module.entity.PlayClerkWagesDetailsInfoEntity::getClerkId,
|
||||
clerkId)
|
||||
.remove();
|
||||
wagesInfoService.lambdaUpdate()
|
||||
.eq(PlayClerkWagesInfoEntity::getClerkId, clerkId)
|
||||
.remove();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
if (!orderIdsToCleanup.isEmpty()) {
|
||||
orderInfoService.removeByIds(orderIdsToCleanup);
|
||||
orderIdsToCleanup.clear();
|
||||
}
|
||||
if (!wagesIdsToCleanup.isEmpty()) {
|
||||
wagesInfoService.removeByIds(wagesIdsToCleanup);
|
||||
wagesIdsToCleanup.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void queryUnsettledWagesSumsOrdersForClerk__covers_WAGE_001() throws Exception {
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
String orderA = insertUnsettledOrder(new BigDecimal("10.00"), new BigDecimal("3.00"));
|
||||
String orderB = insertUnsettledOrder(new BigDecimal("20.50"), new BigDecimal("6.50"));
|
||||
orderIdsToCleanup.add(orderA);
|
||||
orderIdsToCleanup.add(orderB);
|
||||
|
||||
mockMvc.perform(get("/wx/wages/clerk/queryUnsettledWages")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CLERK_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + clerkToken))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200))
|
||||
.andExpect(jsonPath("$.data.orderNumber").value(2))
|
||||
.andExpect(jsonPath("$.data.orderMoney").value(30.50))
|
||||
.andExpect(jsonPath("$.data.estimatedRevenue").value(9.50));
|
||||
}
|
||||
|
||||
@Test
|
||||
void queryCurrentPeriodWagesReturnsZerosWhenNoRow__covers_WAGE_002() throws Exception {
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
|
||||
mockMvc.perform(get("/wx/wages/clerk/queryCurrentPeriodWages")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CLERK_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + clerkToken))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200))
|
||||
.andExpect(jsonPath("$.data.totalMoney").value(0))
|
||||
.andExpect(jsonPath("$.data.orderWages.orderNumber").value(0))
|
||||
.andExpect(jsonPath("$.data.startCountDate").value(LocalDate.now().toString()))
|
||||
.andExpect(jsonPath("$.data.endCountDate").value(LocalDate.now().toString()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void queryHistoricalWagesReturnsHardcodedPageMeta__covers_WAGE_003() throws Exception {
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
insertHistoricalWages("apitest-wage-his-1", new BigDecimal("12.34"));
|
||||
insertHistoricalWages("apitest-wage-his-2", new BigDecimal("56.78"));
|
||||
|
||||
mockMvc.perform(post("/wx/wages/clerk/queryHistoricalWages")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CLERK_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + clerkToken)
|
||||
.contentType(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200))
|
||||
.andExpect(jsonPath("$.total").value(5))
|
||||
.andExpect(jsonPath("$.pageInfo.pageSize").value(10))
|
||||
.andExpect(jsonPath("$.pageInfo.totalPage").value(1))
|
||||
.andExpect(jsonPath("$.data").isArray())
|
||||
.andExpect(jsonPath("$.data.length()").value(2));
|
||||
}
|
||||
|
||||
private String insertUnsettledOrder(BigDecimal finalAmount, BigDecimal estimatedRevenue) {
|
||||
PlayOrderInfoEntity entity = new PlayOrderInfoEntity();
|
||||
String id = "apitest-wage-" + IdUtils.getUuid();
|
||||
entity.setId(id);
|
||||
entity.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
entity.setAcceptBy(clerkId);
|
||||
entity.setOrderSettlementState("0");
|
||||
entity.setFinalAmount(finalAmount);
|
||||
entity.setEstimatedRevenue(estimatedRevenue);
|
||||
entity.setDeleted(false);
|
||||
orderInfoService.save(entity);
|
||||
return id;
|
||||
}
|
||||
|
||||
private void insertHistoricalWages(String idPrefix, BigDecimal finalAmount) {
|
||||
PlayClerkWagesInfoEntity entity = new PlayClerkWagesInfoEntity();
|
||||
String id = idPrefix + "-" + IdUtils.getUuid();
|
||||
entity.setId(id);
|
||||
entity.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
entity.setClerkId(clerkId);
|
||||
entity.setHistoricalStatistics("1");
|
||||
entity.setStartCountDate(LocalDate.now().minusDays(7));
|
||||
entity.setEndCountDate(LocalDate.now().minusDays(1));
|
||||
entity.setSettlementDate(LocalDate.now().minusDays(1));
|
||||
entity.setOrderNumber(1);
|
||||
entity.setFinalAmount(finalAmount);
|
||||
entity.setEstimatedRevenue(finalAmount);
|
||||
entity.setCreatedTime(java.sql.Timestamp.valueOf(LocalDateTime.now()));
|
||||
entity.setUpdatedTime(java.sql.Timestamp.valueOf(LocalDateTime.now()));
|
||||
entity.setDeleted(false);
|
||||
wagesInfoService.save(entity);
|
||||
wagesIdsToCleanup.add(id);
|
||||
}
|
||||
|
||||
private void ensureActiveClerk(String clerkId, String openId, String token) {
|
||||
PlayClerkUserInfoEntity existing = clerkUserInfoService.getById(clerkId);
|
||||
if (existing == null) {
|
||||
PlayClerkUserInfoEntity entity = new PlayClerkUserInfoEntity();
|
||||
entity.setId(clerkId);
|
||||
entity.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
entity.setOpenid(openId);
|
||||
entity.setNickname("API Test Wages Clerk");
|
||||
entity.setAvatar("https://example.com/avatar.png");
|
||||
entity.setSysUserId("");
|
||||
entity.setOnboardingState("1");
|
||||
entity.setListingState("1");
|
||||
entity.setClerkState("1");
|
||||
entity.setOnlineState("1");
|
||||
entity.setToken(token);
|
||||
clerkUserInfoService.save(entity);
|
||||
return;
|
||||
}
|
||||
PlayClerkUserInfoEntity patch = new PlayClerkUserInfoEntity();
|
||||
patch.setId(clerkId);
|
||||
patch.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
patch.setOpenid(openId);
|
||||
patch.setSysUserId("");
|
||||
patch.setOnboardingState("1");
|
||||
patch.setListingState("1");
|
||||
patch.setClerkState("1");
|
||||
patch.setOnlineState("1");
|
||||
patch.setDeleted(Boolean.FALSE);
|
||||
patch.setToken(token);
|
||||
clerkUserInfoService.updateById(patch);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package com.starry.admin.api;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart;
|
||||
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.common.apitest.ApiTestDataSeeder;
|
||||
import com.starry.admin.common.oss.service.IOssFileService;
|
||||
import com.starry.common.context.CustomSecurityContextHolder;
|
||||
import com.starry.common.utils.IdUtils;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.mock.web.MockMultipartFile;
|
||||
import org.springframework.test.web.servlet.MvcResult;
|
||||
|
||||
class WxCommonControllerApiTest extends AbstractApiTest {
|
||||
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
@MockBean
|
||||
private IOssFileService ossFileService;
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
CustomSecurityContextHolder.remove();
|
||||
}
|
||||
|
||||
@Test
|
||||
void areaTreeReturnsTreeSchema__covers_COM_001() throws Exception {
|
||||
mockMvc.perform(get("/wx/common/area/tree")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200))
|
||||
.andExpect(jsonPath("$.data").exists());
|
||||
}
|
||||
|
||||
@Test
|
||||
void settingInfoReturnsGlobalRowRegardlessOfTenant__covers_COM_002() throws Exception {
|
||||
MvcResult first = mockMvc.perform(get("/wx/common/setting/info")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, "tenant-a-" + IdUtils.getUuid().substring(0, 6)))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200))
|
||||
.andReturn();
|
||||
JsonNode firstRoot = objectMapper.readTree(first.getResponse().getContentAsString(StandardCharsets.UTF_8));
|
||||
String firstId = firstRoot.path("data").path("id").asText();
|
||||
assertThat(firstId).isNotBlank();
|
||||
|
||||
MvcResult second = mockMvc.perform(get("/wx/common/setting/info")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, "tenant-b-" + IdUtils.getUuid().substring(0, 6)))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200))
|
||||
.andReturn();
|
||||
JsonNode secondRoot = objectMapper.readTree(second.getResponse().getContentAsString(StandardCharsets.UTF_8));
|
||||
String secondId = secondRoot.path("data").path("id").asText();
|
||||
assertThat(secondId).isNotEqualTo(firstId);
|
||||
}
|
||||
|
||||
@Test
|
||||
void fileUploadReturnsOssUrl__covers_COM_003() throws Exception {
|
||||
String url = "https://oss.mock/apitest/common-upload.png";
|
||||
when(ossFileService.upload(any(), eq(ApiTestDataSeeder.DEFAULT_TENANT_ID), anyString()))
|
||||
.thenReturn(url);
|
||||
|
||||
MockMultipartFile file = new MockMultipartFile(
|
||||
"file",
|
||||
"common-upload.png",
|
||||
MediaType.IMAGE_PNG_VALUE,
|
||||
new byte[] {0x01, 0x02, 0x03});
|
||||
|
||||
mockMvc.perform(multipart("/wx/common/file/upload")
|
||||
.file(file)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.contentType(MediaType.MULTIPART_FORM_DATA))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200))
|
||||
.andExpect(jsonPath("$.data").value(url));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package com.starry.admin.api;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.when;
|
||||
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.starry.admin.common.exception.CustomException;
|
||||
import com.starry.admin.modules.weichat.service.WxAccessTokenService;
|
||||
import com.starry.admin.modules.weichat.utils.WxFileUtils;
|
||||
import com.starry.admin.utils.SecurityUtils;
|
||||
import com.starry.common.redis.RedisCache;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.nio.file.Files;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.MockedStatic;
|
||||
import org.mockito.Mockito;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.http.MediaType;
|
||||
|
||||
class WxCommonControllerAudioUploadApiTest extends AbstractApiTest {
|
||||
|
||||
@MockBean
|
||||
private com.starry.admin.common.oss.service.IOssFileService ossFileService;
|
||||
|
||||
@MockBean
|
||||
private WxAccessTokenService wxAccessTokenService;
|
||||
|
||||
@MockBean
|
||||
private RedisCache redisCache;
|
||||
|
||||
@Test
|
||||
void audioUploadRejectsBlankMediaId__covers_COM_004() throws Exception {
|
||||
SecurityUtils.setTenantId("tenant-apitest");
|
||||
|
||||
mockMvc.perform(get("/wx/common/audio/upload")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.param("mediaId", ""))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(500));
|
||||
}
|
||||
|
||||
@Test
|
||||
void audioUploadDownloadsConvertsAndUploadsMp3__covers_COM_005() throws Exception {
|
||||
SecurityUtils.setTenantId("tenant-apitest");
|
||||
when(wxAccessTokenService.getAccessToken()).thenReturn("access-token");
|
||||
when(ossFileService.upload(any(), eq("tenant-apitest"), anyString())).thenReturn("https://oss.example/audio.mp3");
|
||||
|
||||
try (MockedStatic<WxFileUtils> mocked = Mockito.mockStatic(WxFileUtils.class)) {
|
||||
mocked.when(() -> WxFileUtils.getTemporaryMaterial(eq("access-token"), eq("media-1")))
|
||||
.thenReturn(new ByteArrayInputStream("amr-bytes".getBytes()));
|
||||
mocked.when(() -> WxFileUtils.audioConvert2Mp3(any(File.class), any(File.class)))
|
||||
.thenAnswer(invocation -> {
|
||||
File target = invocation.getArgument(1);
|
||||
Files.write(target.toPath(), "mp3-bytes".getBytes());
|
||||
return null;
|
||||
});
|
||||
|
||||
mockMvc.perform(get("/wx/common/audio/upload")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.param("mediaId", "media-1")
|
||||
.accept(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200))
|
||||
.andExpect(jsonPath("$.data").value("https://oss.example/audio.mp3"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -102,7 +102,7 @@ class WxCouponControllerApiTest extends AbstractApiTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void obtainCouponRejectsNonWhitelistCustomer() throws Exception {
|
||||
void obtainCouponReturnsNonEligibilityReasonWhenNonWhitelisted__covers_CP_002() throws Exception {
|
||||
ensureTenantContext();
|
||||
String couponId = newCouponId("whitelist");
|
||||
PlayCouponInfoEntity coupon = createBaseCoupon(couponId);
|
||||
@@ -123,7 +123,7 @@ class WxCouponControllerApiTest extends AbstractApiTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void queryAllSkipsOfflineCouponsAndMarksObtainedOnes() throws Exception {
|
||||
void queryAllSkipsOfflineCouponsAndMarksObtainedOnes__covers_CP_004() throws Exception {
|
||||
ensureTenantContext();
|
||||
PlayCouponInfoEntity onlineCoupon = createBaseCoupon(newCouponId("online"));
|
||||
onlineCoupon.setCustomWhitelist(List.of(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID));
|
||||
@@ -164,7 +164,7 @@ class WxCouponControllerApiTest extends AbstractApiTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void queryByOrderFlagsOnlyEligibleCouponsAsAvailable() throws Exception {
|
||||
void queryByOrderFlagsOnlyEligibleCouponsAsAvailable__covers_CP_006() throws Exception {
|
||||
ensureTenantContext();
|
||||
PlayCouponInfoEntity eligible = createBaseCoupon(newCouponId("eligible"));
|
||||
eligible.setUseMinAmount(MINIMUM_USAGE_AMOUNT);
|
||||
@@ -200,23 +200,30 @@ class WxCouponControllerApiTest extends AbstractApiTest {
|
||||
.andExpect(jsonPath("$.code").value(200))
|
||||
.andReturn();
|
||||
|
||||
ArrayNode data = (ArrayNode) mapper.readTree(result.getResponse().getContentAsString()).path("data");
|
||||
ArrayNode data = (ArrayNode) mapper.readTree(result.getResponse().getContentAsString(java.nio.charset.StandardCharsets.UTF_8))
|
||||
.path("data");
|
||||
assertThat(data).isNotNull();
|
||||
assertThat(data.size()).isGreaterThanOrEqualTo(2);
|
||||
boolean foundEligibleDetail = false;
|
||||
boolean foundIneligibleDetail = false;
|
||||
for (JsonNode node : data) {
|
||||
if (eligible.getId().equals(node.path("couponId").asText())) {
|
||||
if (eligibleDetail.getId().equals(node.path("id").asText())) {
|
||||
foundEligibleDetail = true;
|
||||
assertThat(node.path("available").asText()).isEqualTo("1");
|
||||
assertThat(node.path("reasonForUnavailableUse").asText("")).isEmpty();
|
||||
}
|
||||
if (ineligible.getId().equals(node.path("couponId").asText())) {
|
||||
if (ineligibleDetail.getId().equals(node.path("id").asText())) {
|
||||
foundIneligibleDetail = true;
|
||||
assertThat(node.path("available").asText()).isEqualTo("0");
|
||||
assertThat(node.path("reasonForUnavailableUse").asText()).isEqualTo("订单类型不符合");
|
||||
}
|
||||
}
|
||||
assertThat(foundEligibleDetail).isTrue();
|
||||
assertThat(foundIneligibleDetail).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void obtainCouponSucceedsWhenEligible() throws Exception {
|
||||
void obtainCouponSucceedsWhenEligible__covers_CP_003() throws Exception {
|
||||
ensureTenantContext();
|
||||
PlayCouponInfoEntity coupon = createBaseCoupon(newCouponId("success"));
|
||||
couponInfoService.save(coupon);
|
||||
@@ -244,7 +251,7 @@ class WxCouponControllerApiTest extends AbstractApiTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void obtainCouponHonorsPerUserLimit() throws Exception {
|
||||
void obtainCouponHonorsPerUserLimit__covers_CP_002() throws Exception {
|
||||
ensureTenantContext();
|
||||
PlayCouponInfoEntity coupon = createBaseCoupon(newCouponId("limit"));
|
||||
coupon.setClerkObtainedMaxQuantity(1);
|
||||
@@ -279,6 +286,121 @@ class WxCouponControllerApiTest extends AbstractApiTest {
|
||||
.andExpect(jsonPath("$.data.msg").value("优惠券已达到领取上限"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void obtainCouponRejectsEmptyId__covers_CP_001() throws Exception {
|
||||
ensureTenantContext();
|
||||
|
||||
mockMvc.perform(get("/wx/coupon/custom/obtainCoupon")
|
||||
.param("id", "")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CUSTOM_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + customerToken))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(500))
|
||||
.andExpect(jsonPath("$.message").value("请求参数异常,优惠券ID不能为"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void queryAllHidesWhitelistCouponFromNonWhitelistCustomer__covers_CP_004() throws Exception {
|
||||
ensureTenantContext();
|
||||
|
||||
String couponId = newCouponId("qall-wl");
|
||||
PlayCouponInfoEntity coupon = createBaseCoupon(couponId);
|
||||
coupon.setClaimConditionType(CouponClaimConditionType.WHITELIST.code());
|
||||
coupon.setCustomWhitelist(List.of("other-customer"));
|
||||
couponInfoService.save(coupon);
|
||||
couponIds.add(coupon.getId());
|
||||
|
||||
MvcResult result = mockMvc.perform(post("/wx/coupon/custom/queryAll")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CUSTOM_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + customerToken)
|
||||
.contentType(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200))
|
||||
.andReturn();
|
||||
|
||||
JsonNode data = mapper.readTree(result.getResponse().getContentAsString(java.nio.charset.StandardCharsets.UTF_8))
|
||||
.path("data");
|
||||
assertThat(data).isNotNull();
|
||||
assertThat(data.isArray()).isTrue();
|
||||
for (JsonNode node : data) {
|
||||
assertThat(node.path("id").asText()).isNotEqualTo(coupon.getId());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void queryByOrderRejectsWhenClerkIdAndLevelIdBothEmpty__covers_CP_005() throws Exception {
|
||||
ensureTenantContext();
|
||||
|
||||
ObjectNode payload = mapper.createObjectNode();
|
||||
payload.put("commodityId", ApiTestDataSeeder.DEFAULT_COMMODITY_ID);
|
||||
payload.put("levelId", "");
|
||||
payload.put("clerkId", "");
|
||||
payload.put("placeType", OrderConstant.PlaceType.RANDOM.getCode());
|
||||
payload.put("commodityQuantity", 1);
|
||||
|
||||
mockMvc.perform(post("/wx/coupon/custom/queryByOrder")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CUSTOM_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + customerToken)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(payload.toString()))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(500))
|
||||
.andExpect(jsonPath("$.message").value("请求参数异常,店员ID不能为空,等级ID不能为空"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void queryByOrderSwallowsBrokenCouponAndReturnsRemaining__covers_CP_007() throws Exception {
|
||||
ensureTenantContext();
|
||||
PlayCouponInfoEntity eligible = createBaseCoupon(newCouponId("swv"));
|
||||
eligible.setUseMinAmount(BigDecimal.ZERO);
|
||||
couponInfoService.save(eligible);
|
||||
couponIds.add(eligible.getId());
|
||||
|
||||
PlayCouponDetailsEntity eligibleDetail = createCouponDetail(eligible.getId(), CouponUseState.UNUSED);
|
||||
couponDetailsService.save(eligibleDetail);
|
||||
couponDetailIds.add(eligibleDetail.getId());
|
||||
|
||||
PlayCouponDetailsEntity brokenDetail = createCouponDetail(newCouponId("missing"), CouponUseState.UNUSED);
|
||||
couponDetailsService.save(brokenDetail);
|
||||
couponDetailIds.add(brokenDetail.getId());
|
||||
|
||||
ObjectNode payload = mapper.createObjectNode();
|
||||
payload.put("commodityId", ApiTestDataSeeder.DEFAULT_COMMODITY_ID);
|
||||
payload.put("levelId", ApiTestDataSeeder.DEFAULT_CLERK_LEVEL_ID);
|
||||
payload.put("clerkId", "");
|
||||
payload.put("placeType", OrderConstant.PlaceType.RANDOM.getCode());
|
||||
payload.put("commodityQuantity", 1);
|
||||
|
||||
MvcResult result = mockMvc.perform(post("/wx/coupon/custom/queryByOrder")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CUSTOM_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + customerToken)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(payload.toString()))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200))
|
||||
.andReturn();
|
||||
|
||||
JsonNode data = mapper.readTree(result.getResponse().getContentAsString()).path("data");
|
||||
assertThat(data.isArray()).isTrue();
|
||||
boolean foundEligibleDetail = false;
|
||||
boolean foundBrokenDetail = false;
|
||||
for (JsonNode node : data) {
|
||||
String returnedId = node.path("id").asText();
|
||||
if (eligibleDetail.getId().equals(returnedId)) {
|
||||
foundEligibleDetail = true;
|
||||
}
|
||||
if (brokenDetail.getId().equals(returnedId)) {
|
||||
foundBrokenDetail = true;
|
||||
}
|
||||
}
|
||||
assertThat(foundEligibleDetail).isTrue();
|
||||
assertThat(foundBrokenDetail).isFalse();
|
||||
}
|
||||
|
||||
private PlayCouponInfoEntity createBaseCoupon(String id) {
|
||||
PlayCouponInfoEntity coupon = new PlayCouponInfoEntity();
|
||||
coupon.setId(id);
|
||||
|
||||
@@ -0,0 +1,277 @@
|
||||
package com.starry.admin.api;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.starry.admin.common.apitest.ApiTestDataSeeder;
|
||||
import com.starry.admin.modules.custom.entity.PlayCustomFollowInfoEntity;
|
||||
import com.starry.admin.modules.custom.module.entity.PlayCustomLeaveMsgEntity;
|
||||
import com.starry.admin.modules.custom.module.entity.PlayCustomUserInfoEntity;
|
||||
import com.starry.admin.modules.custom.service.IPlayCustomFollowInfoService;
|
||||
import com.starry.admin.modules.custom.service.IPlayCustomLeaveMsgService;
|
||||
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.weichat.service.WxTokenService;
|
||||
import com.starry.admin.utils.SecurityUtils;
|
||||
import com.starry.common.constant.Constants;
|
||||
import com.starry.common.context.CustomSecurityContextHolder;
|
||||
import com.starry.common.utils.IdUtils;
|
||||
import java.math.BigDecimal;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.web.servlet.MvcResult;
|
||||
|
||||
class WxCustomControllerMiscApiTest extends AbstractApiTest {
|
||||
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
@Autowired
|
||||
private WxTokenService wxTokenService;
|
||||
|
||||
@Autowired
|
||||
private IPlayCustomUserInfoService customUserInfoService;
|
||||
|
||||
@Autowired
|
||||
private IPlayCustomLeaveMsgService leaveMsgService;
|
||||
|
||||
@Autowired
|
||||
private IPlayCustomFollowInfoService followInfoService;
|
||||
|
||||
@Autowired
|
||||
private IPlayOrderInfoService orderInfoService;
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
CustomSecurityContextHolder.remove();
|
||||
}
|
||||
|
||||
@Test
|
||||
void queryClerkDetailedByIdWorksWithoutLogin__covers_CUS_001() throws Exception {
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
|
||||
mockMvc.perform(get("/wx/custom/queryClerkDetailedById")
|
||||
.param("id", ApiTestDataSeeder.DEFAULT_CLERK_ID)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200))
|
||||
.andExpect(jsonPath("$.data").exists());
|
||||
}
|
||||
|
||||
@Test
|
||||
void queryByIdReturnsClerkStateWhenOpenidMatchesClerk__covers_CUS_002() throws Exception {
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
String customerId = "customer-openid-match-" + IdUtils.getUuid().substring(0, 8);
|
||||
String token = ensureCustomerWithOpenid(customerId, ApiTestDataSeeder.DEFAULT_CLERK_OPEN_ID);
|
||||
|
||||
mockMvc.perform(get("/wx/custom/queryById")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CUSTOM_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + token))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200))
|
||||
.andExpect(jsonPath("$.data.clerkState").value("1"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateHideLevelStateIgnoresClientIdAndUsesSessionId__covers_CUS_003() throws Exception {
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
String sessionCustomerId = "customer-hide-session-" + IdUtils.getUuid().substring(0, 8);
|
||||
String otherCustomerId = "customer-hide-other-" + IdUtils.getUuid().substring(0, 8);
|
||||
String sessionToken = ensureCustomerWithOpenid(sessionCustomerId, "openid-" + sessionCustomerId);
|
||||
ensureCustomerWithOpenid(otherCustomerId, "openid-" + otherCustomerId);
|
||||
|
||||
customUserInfoService.lambdaUpdate()
|
||||
.eq(PlayCustomUserInfoEntity::getId, sessionCustomerId)
|
||||
.set(PlayCustomUserInfoEntity::getHideLevelState, "0")
|
||||
.update();
|
||||
customUserInfoService.lambdaUpdate()
|
||||
.eq(PlayCustomUserInfoEntity::getId, otherCustomerId)
|
||||
.set(PlayCustomUserInfoEntity::getHideLevelState, "0")
|
||||
.update();
|
||||
|
||||
String payload = "{\"id\":\"" + otherCustomerId + "\",\"hideLevelState\":\"1\"}";
|
||||
mockMvc.perform(post("/wx/custom/updateHideLevelState")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CUSTOM_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + sessionToken)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(payload))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200))
|
||||
.andExpect(jsonPath("$.data").value("成功"));
|
||||
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
PlayCustomUserInfoEntity sessionAfter = customUserInfoService.getById(sessionCustomerId);
|
||||
PlayCustomUserInfoEntity otherAfter = customUserInfoService.getById(otherCustomerId);
|
||||
assertThat(sessionAfter.getHideLevelState()).isEqualTo("1");
|
||||
assertThat(otherAfter.getHideLevelState()).isEqualTo("0");
|
||||
}
|
||||
|
||||
@Test
|
||||
void leaveAddCreatesRowAndReturnsPinnedMessage__covers_CUS_015() throws Exception {
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
String customerId = "customer-leave-" + IdUtils.getUuid().substring(0, 8);
|
||||
String token = ensureCustomerWithOpenid(customerId, "openid-" + customerId);
|
||||
|
||||
String content = "leave-content-" + LocalDateTime.now();
|
||||
String payload = "{\"content\":\"" + content + "\",\"images\":[],\"remark\":\"\"}";
|
||||
|
||||
mockMvc.perform(post("/wx/custom/leave/add")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CUSTOM_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + token)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(payload))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200))
|
||||
.andExpect(jsonPath("$.data").value("取消成功"));
|
||||
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
PlayCustomLeaveMsgEntity latest = leaveMsgService.lambdaQuery()
|
||||
.eq(PlayCustomLeaveMsgEntity::getCustomId, customerId)
|
||||
.orderByDesc(PlayCustomLeaveMsgEntity::getMsgTime)
|
||||
.last("limit 1")
|
||||
.one();
|
||||
assertThat(latest).isNotNull();
|
||||
assertThat(latest.getContent()).isEqualTo(content);
|
||||
assertThat(latest.getImages()).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void leaveQueryPermissionReturnsSchemaPinned__covers_CUS_016() throws Exception {
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
String customerId = "customer-leave-perm-" + IdUtils.getUuid().substring(0, 8);
|
||||
String token = ensureCustomerWithOpenid(customerId, "openid-" + customerId);
|
||||
|
||||
mockMvc.perform(get("/wx/custom/leave/queryPermission")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CUSTOM_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + token))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200))
|
||||
.andExpect(jsonPath("$.data.permission").value(true))
|
||||
.andExpect(jsonPath("$.data.msg").value(""));
|
||||
}
|
||||
|
||||
@Test
|
||||
void followStateUpdateIsIdempotentAndCorrect__covers_CUS_017() throws Exception {
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
String customerId = "customer-follow-" + IdUtils.getUuid().substring(0, 8);
|
||||
String token = ensureCustomerWithOpenid(customerId, "openid-" + customerId);
|
||||
|
||||
String payload = "{\"clerkId\":\"" + ApiTestDataSeeder.DEFAULT_CLERK_ID + "\",\"followState\":\"1\"}";
|
||||
for (int i = 0; i < 2; i++) {
|
||||
mockMvc.perform(post("/wx/custom/followState/update")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CUSTOM_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + token)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(payload))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200))
|
||||
.andExpect(jsonPath("$.data").value("修改成功"));
|
||||
}
|
||||
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
long rowCount = followInfoService.lambdaQuery()
|
||||
.eq(PlayCustomFollowInfoEntity::getCustomId, customerId)
|
||||
.eq(PlayCustomFollowInfoEntity::getClerkId, ApiTestDataSeeder.DEFAULT_CLERK_ID)
|
||||
.count();
|
||||
assertThat(rowCount).isEqualTo(1);
|
||||
PlayCustomFollowInfoEntity row = followInfoService.lambdaQuery()
|
||||
.eq(PlayCustomFollowInfoEntity::getCustomId, customerId)
|
||||
.eq(PlayCustomFollowInfoEntity::getClerkId, ApiTestDataSeeder.DEFAULT_CLERK_ID)
|
||||
.one();
|
||||
assertThat(row.getFollowState()).isEqualTo("1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void followQueryByPageReturnsPagingSchema__covers_CUS_018() throws Exception {
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
String customerId = "customer-follow-page-" + IdUtils.getUuid().substring(0, 8);
|
||||
String token = ensureCustomerWithOpenid(customerId, "openid-" + customerId);
|
||||
|
||||
followInfoService.updateFollowState(customerId, ApiTestDataSeeder.DEFAULT_CLERK_ID, "1");
|
||||
|
||||
String payload = "{\"pageNum\":1,\"pageSize\":10,\"followState\":\"1\"}";
|
||||
MvcResult result = mockMvc.perform(post("/wx/custom/follow/queryByPage")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CUSTOM_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + token)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(payload))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200))
|
||||
.andReturn();
|
||||
|
||||
JsonNode root = objectMapper.readTree(result.getResponse().getContentAsString(StandardCharsets.UTF_8));
|
||||
assertThat(root.path("data").isArray()).isTrue();
|
||||
assertThat(root.path("pageInfo").isObject()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void complaintAddRejectsNonPurchaser__covers_CUS_014() throws Exception {
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
String purchaserId = "cmpown-" + IdUtils.getUuid().substring(0, 8);
|
||||
ensureCustomerWithOpenid(purchaserId, "openid-" + purchaserId);
|
||||
|
||||
String otherCustomerId = "cmpoth-" + IdUtils.getUuid().substring(0, 8);
|
||||
String otherToken = ensureCustomerWithOpenid(otherCustomerId, "openid-" + otherCustomerId);
|
||||
|
||||
String orderId = "order-complaint-" + IdUtils.getUuid().substring(0, 8);
|
||||
PlayOrderInfoEntity order = new PlayOrderInfoEntity();
|
||||
order.setId(orderId);
|
||||
order.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
order.setOrderStatus(OrderConstant.OrderStatus.PENDING.getCode());
|
||||
order.setOrderType(OrderConstant.OrderType.NORMAL.getCode());
|
||||
order.setPlaceType(OrderConstant.PlaceType.RANDOM.getCode());
|
||||
order.setPurchaserBy(purchaserId);
|
||||
order.setWeiChatCode("wx-" + purchaserId);
|
||||
orderInfoService.save(order);
|
||||
|
||||
String payload = "{\"orderId\":\"" + orderId + "\",\"wxChatCode\":\"wx-complain\",\"complaintCon\":\"bad\","
|
||||
+ "\"images\":[]}";
|
||||
|
||||
mockMvc.perform(post("/wx/custom/order/complaint/add")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CUSTOM_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + otherToken)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(payload))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(500))
|
||||
.andExpect(jsonPath("$.message").value("只有下单人才能投诉"));
|
||||
}
|
||||
|
||||
private String ensureCustomerWithOpenid(String id, String openid) {
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
PlayCustomUserInfoEntity entity = new PlayCustomUserInfoEntity();
|
||||
entity.setId(id);
|
||||
entity.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
entity.setOpenid(openid);
|
||||
entity.setNickname("api-" + id);
|
||||
entity.setAccountBalance(new BigDecimal("0.00"));
|
||||
entity.setAccountState("1");
|
||||
entity.setSubscribeState("1");
|
||||
entity.setPurchaseState("1");
|
||||
entity.setMobilePhoneState("1");
|
||||
customUserInfoService.save(entity);
|
||||
|
||||
String token = wxTokenService.createWxUserToken(id);
|
||||
customUserInfoService.updateTokenById(id, token);
|
||||
return token;
|
||||
}
|
||||
}
|
||||
@@ -79,7 +79,7 @@ class WxCustomGiftOrderApiTest extends WxCustomOrderApiTestSupport {
|
||||
@Test
|
||||
// 测试用例:用户余额充足且携带有效登录态时,请求 /wx/custom/order/gift 下单指定礼物,
|
||||
// 期望生成已完成的礼物奖励订单、产生对应收益记录,同时校验用户/陪玩师礼物计数与账户余额随订单金额同步更新。
|
||||
void giftOrderCreatesCompletedRewardAndUpdatesGiftCounters() throws Exception {
|
||||
void giftOrderCreatesCompletedRewardAndUpdatesGiftCounters__covers_CUS_005() throws Exception {
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
try {
|
||||
resetCustomerBalance();
|
||||
|
||||
@@ -9,6 +9,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import com.starry.admin.common.apitest.ApiTestDataSeeder;
|
||||
import com.starry.admin.modules.custom.module.entity.PlayCustomUserInfoEntity;
|
||||
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.IPlayOrderEvaluateInfoService;
|
||||
@@ -17,6 +18,7 @@ import com.starry.admin.utils.SecurityUtils;
|
||||
import com.starry.common.constant.Constants;
|
||||
import com.starry.common.context.CustomSecurityContextHolder;
|
||||
import com.starry.common.utils.IdUtils;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
@@ -101,6 +103,60 @@ class WxCustomOrderEvaluationApiTest extends WxCustomOrderApiTestSupport {
|
||||
assertThat(data.path("evaluateCon").asText()).endsWith(evaluationSuffix);
|
||||
}
|
||||
|
||||
@Test
|
||||
void evaluateAddRejectsNonPurchaser__covers_CUS_012() throws Exception {
|
||||
ensureTenantContext();
|
||||
String remark = "evaluate-nonpurchaser-" + LocalDateTime.now();
|
||||
String orderId = createRandomOrder(remark);
|
||||
|
||||
String otherCustomerId = "customer-eval-other-" + IdUtils.getUuid().substring(0, 8);
|
||||
PlayCustomUserInfoEntity other = new PlayCustomUserInfoEntity();
|
||||
other.setId(otherCustomerId);
|
||||
other.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
other.setOpenid("openid-" + otherCustomerId);
|
||||
other.setNickname("other");
|
||||
other.setAccountBalance(new BigDecimal("0.00"));
|
||||
other.setAccountState("1");
|
||||
other.setSubscribeState("1");
|
||||
other.setPurchaseState("1");
|
||||
other.setMobilePhoneState("1");
|
||||
customUserInfoService.save(other);
|
||||
String otherToken = wxTokenService.createWxUserToken(otherCustomerId);
|
||||
customUserInfoService.updateTokenById(otherCustomerId, otherToken);
|
||||
|
||||
ObjectNode payload = objectMapper.createObjectNode();
|
||||
payload.put("orderId", orderId);
|
||||
payload.put("anonymous", "1");
|
||||
payload.put("evaluateLevel", 5);
|
||||
payload.put("evaluateCon", "bad");
|
||||
|
||||
mockMvc.perform(post("/wx/custom/order/evaluate/add")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CUSTOM_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + otherToken)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(payload.toString()))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(500))
|
||||
.andExpect(jsonPath("$.message").value("只有下单人才能评价"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void queryEvaluationRejectsWhenNotEvaluated__covers_CUS_013() throws Exception {
|
||||
ensureTenantContext();
|
||||
String remark = "evaluate-missing-" + LocalDateTime.now();
|
||||
String orderId = createRandomOrder(remark);
|
||||
|
||||
mockMvc.perform(get("/wx/custom/order/evaluate/queryByOrderId")
|
||||
.param("id", orderId)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CUSTOM_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + customerToken))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(500))
|
||||
.andExpect(jsonPath("$.message").value("当前订单未评价"));
|
||||
}
|
||||
|
||||
private String createRandomOrder(String remark) throws Exception {
|
||||
ensureTenantContext();
|
||||
|
||||
|
||||
@@ -258,7 +258,7 @@ class WxCustomOrderQueryApiTest extends WxCustomOrderApiTestSupport {
|
||||
}
|
||||
|
||||
@Test
|
||||
void queryByPageReturnsOnlyOrdersBelongingToCurrentCustomer() throws Exception {
|
||||
void queryByPageReturnsOnlyOrdersBelongingToCurrentCustomer__covers_CUS_009() throws Exception {
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
try {
|
||||
String token = ensureCustomerToken();
|
||||
|
||||
@@ -18,6 +18,8 @@ import com.starry.admin.modules.clerk.module.entity.PlayClerkLevelInfoEntity;
|
||||
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;
|
||||
import com.starry.admin.modules.order.module.constant.OrderConstant;
|
||||
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
|
||||
import com.starry.admin.modules.order.module.entity.PlayOrderRefundInfoEntity;
|
||||
import com.starry.admin.modules.order.service.IPlayOrderRefundInfoService;
|
||||
import com.starry.admin.modules.personnel.module.entity.PlayPersonnelAdminInfoEntity;
|
||||
import com.starry.admin.modules.personnel.service.IPlayPersonnelAdminInfoService;
|
||||
import com.starry.admin.modules.shop.module.constant.CouponUseState;
|
||||
@@ -71,8 +73,11 @@ class WxCustomRandomOrderApiTest extends WxCustomOrderApiTestSupport {
|
||||
@org.springframework.beans.factory.annotation.Autowired
|
||||
private IPlayPersonnelAdminInfoService playPersonnelAdminInfoService;
|
||||
|
||||
@org.springframework.beans.factory.annotation.Autowired
|
||||
private IPlayOrderRefundInfoService orderRefundInfoService;
|
||||
|
||||
@Test
|
||||
void randomOrderFailsWhenBalanceInsufficient() throws Exception {
|
||||
void randomOrderFailsWhenBalanceInsufficient__covers_CUS_008() throws Exception {
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
String remark = "API random insufficient " + IdUtils.getUuid();
|
||||
try {
|
||||
@@ -120,9 +125,10 @@ class WxCustomRandomOrderApiTest extends WxCustomOrderApiTestSupport {
|
||||
@Test
|
||||
// 测试用例:客户带随机下单请求命中默认等级与服务,接口应返回成功文案,
|
||||
// 并新增一条处于待接单状态的随机订单,金额、商品信息与 remark 与提交参数保持一致。
|
||||
void randomOrderCreatesPendingOrder() throws Exception {
|
||||
void randomOrderCreatesPendingOrder__covers_CUS_007() throws Exception {
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
try {
|
||||
reset(wxCustomMpService, overdueOrderHandlerTask);
|
||||
resetCustomerBalance();
|
||||
String rawToken = wxTokenService.createWxUserToken(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID);
|
||||
customUserInfoService.updateTokenById(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID, rawToken);
|
||||
@@ -175,6 +181,104 @@ class WxCustomRandomOrderApiTest extends WxCustomOrderApiTestSupport {
|
||||
Assertions.assertThat(latest.getOrderStatus()).isEqualTo(OrderConstant.OrderStatus.PENDING.getCode());
|
||||
Assertions.assertThat(latest.getCommodityId()).isEqualTo(ApiTestDataSeeder.DEFAULT_COMMODITY_ID);
|
||||
Assertions.assertThat(latest.getOrderMoney()).isNotNull();
|
||||
|
||||
verify(wxCustomMpService).sendCreateOrderMessageBatch(
|
||||
argThat(list -> list != null && list.stream()
|
||||
.anyMatch(item -> ApiTestDataSeeder.DEFAULT_CLERK_OPEN_ID.equals(item.getOpenid()))),
|
||||
eq(latest.getOrderNo()),
|
||||
org.mockito.ArgumentMatchers.anyString(),
|
||||
eq(latest.getCommodityName()),
|
||||
eq(latest.getId()),
|
||||
eq(latest.getPlaceType()),
|
||||
eq(latest.getRewardType()));
|
||||
verify(overdueOrderHandlerTask).enqueue(eq(latest.getId() + "_" + ApiTestDataSeeder.DEFAULT_TENANT_ID));
|
||||
} finally {
|
||||
CustomSecurityContextHolder.remove();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void endOrderTransitionsFromInProgressToCompleted__covers_CUS_010() throws Exception {
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
try {
|
||||
resetCustomerBalance();
|
||||
String customerToken = wxTokenService.createWxUserToken(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID);
|
||||
customUserInfoService.updateTokenById(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID, customerToken);
|
||||
String clerkToken = wxTokenService.createWxUserToken(ApiTestDataSeeder.DEFAULT_CLERK_ID);
|
||||
clerkUserInfoService.updateTokenById(ApiTestDataSeeder.DEFAULT_CLERK_ID, clerkToken);
|
||||
|
||||
String remark = "API random end " + IdUtils.getUuid();
|
||||
String orderId = placeRandomOrder(remark, customerToken);
|
||||
|
||||
ensureTenantContext();
|
||||
mockMvc.perform(get("/wx/clerk/order/accept")
|
||||
.param("id", orderId)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CLERK_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + clerkToken))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200));
|
||||
|
||||
ensureTenantContext();
|
||||
mockMvc.perform(get("/wx/clerk/order/start")
|
||||
.param("id", orderId)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CLERK_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + clerkToken))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200));
|
||||
|
||||
ensureTenantContext();
|
||||
mockMvc.perform(get("/wx/custom/order/end")
|
||||
.param("id", orderId)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CUSTOM_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + customerToken))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200))
|
||||
.andExpect(jsonPath("$.data").value("成功"));
|
||||
|
||||
ensureTenantContext();
|
||||
PlayOrderInfoEntity updated = playOrderInfoService.selectOrderInfoById(orderId);
|
||||
Assertions.assertThat(updated.getOrderStatus()).isEqualTo(OrderConstant.OrderStatus.COMPLETED.getCode());
|
||||
} finally {
|
||||
CustomSecurityContextHolder.remove();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void cancellationCreatesRefundRecordAndIgnoresImages__covers_CUS_011() throws Exception {
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
try {
|
||||
resetCustomerBalance();
|
||||
String customerToken = wxTokenService.createWxUserToken(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID);
|
||||
customUserInfoService.updateTokenById(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID, customerToken);
|
||||
|
||||
String remark = "API random cancel record " + IdUtils.getUuid();
|
||||
String orderId = placeRandomOrder(remark, customerToken);
|
||||
|
||||
String cancelPayload = "{\"orderId\":\"" + orderId + "\",\"refundReason\":\"测试取消\","
|
||||
+ "\"images\":[\"https://img.example/a.png\",\"https://img.example/b.png\"]}";
|
||||
mockMvc.perform(post("/wx/custom/order/cancellation")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CUSTOM_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + customerToken)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(cancelPayload))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200))
|
||||
.andExpect(jsonPath("$.data").value("取消成功"));
|
||||
|
||||
ensureTenantContext();
|
||||
PlayOrderInfoEntity cancelled = playOrderInfoService.selectOrderInfoById(orderId);
|
||||
Assertions.assertThat(cancelled.getOrderStatus()).isEqualTo(OrderConstant.OrderStatus.CANCELLED.getCode());
|
||||
Assertions.assertThat(cancelled.getRefundReason()).isNull();
|
||||
|
||||
ensureTenantContext();
|
||||
PlayOrderRefundInfoEntity refund = orderRefundInfoService.selectPlayOrderRefundInfoByOrderId(orderId);
|
||||
Assertions.assertThat(refund).isNotNull();
|
||||
Assertions.assertThat(refund.getRefundReason()).isEqualTo("测试取消");
|
||||
Assertions.assertThat(refund.getImages()).isNull();
|
||||
} finally {
|
||||
CustomSecurityContextHolder.remove();
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ class WxCustomRewardOrderApiTest extends WxCustomOrderApiTestSupport {
|
||||
@Test
|
||||
// 测试用例:客户指定打赏金额下单时,应即时扣减账户余额、生成已完成的打赏订单并同步收益记录,
|
||||
// 同时校验订单归属陪玩师正确且金额与输入一致,确保余额打赏流程闭环。
|
||||
void rewardOrderConsumesBalanceAndGeneratesEarnings() throws Exception {
|
||||
void rewardOrderConsumesBalanceAndGeneratesEarnings__covers_CUS_004() throws Exception {
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
try {
|
||||
resetCustomerBalance();
|
||||
|
||||
@@ -358,7 +358,7 @@ class WxCustomSpecifiedOrderApiTest extends WxCustomOrderApiTestSupport {
|
||||
@Test
|
||||
// 测试用例:客户携带指定陪玩师和服务下单时,接口需返回成功,并生成待支付状态的指定订单,
|
||||
// 验证订单金额与种子服务价格一致、陪玩师被正确指派,同时触发微信创建订单通知。
|
||||
void specifiedOrderCreatesPendingOrder() throws Exception {
|
||||
void specifiedOrderCreatesPendingOrder__covers_CUS_006() throws Exception {
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
try {
|
||||
resetCustomerBalance();
|
||||
|
||||
@@ -0,0 +1,290 @@
|
||||
package com.starry.admin.api;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.when;
|
||||
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.starry.admin.common.apitest.ApiTestDataSeeder;
|
||||
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;
|
||||
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.weichat.service.WxTokenService;
|
||||
import com.starry.admin.utils.SecurityUtils;
|
||||
import com.starry.common.constant.Constants;
|
||||
import com.starry.common.redis.RedisCache;
|
||||
import me.chanjar.weixin.common.bean.WxJsapiSignature;
|
||||
import me.chanjar.weixin.common.bean.WxOAuth2UserInfo;
|
||||
import me.chanjar.weixin.common.bean.oauth2.WxOAuth2AccessToken;
|
||||
import me.chanjar.weixin.common.service.WxOAuth2Service;
|
||||
import me.chanjar.weixin.mp.api.WxMpService;
|
||||
import me.chanjar.weixin.mp.bean.result.WxMpUser;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.MediaType;
|
||||
|
||||
class WxOauthControllerApiTest extends AbstractApiTest {
|
||||
|
||||
@Autowired
|
||||
private WxTokenService wxTokenService;
|
||||
|
||||
@Autowired
|
||||
private IPlayCustomUserInfoService customUserInfoService;
|
||||
|
||||
@Autowired
|
||||
private IPlayClerkUserInfoService clerkUserInfoService;
|
||||
|
||||
@Autowired
|
||||
private WxMpService wxMpService;
|
||||
|
||||
@Autowired
|
||||
private RedisCache redisCache;
|
||||
|
||||
@Test
|
||||
void getConfigAddressUsesDefaultWhenUrlMissing__covers_OAUTH_001() throws Exception {
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
when(wxMpService.createJsapiSignature(anyString())).thenReturn(new WxJsapiSignature());
|
||||
|
||||
mockMvc.perform(post("/wx/oauth2/getConfigAddress")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("{\"url\":\"\"}"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getConfigAddressUsesProvidedUrlWhenPresent__covers_OAUTH_002() throws Exception {
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
when(wxMpService.createJsapiSignature(anyString())).thenReturn(new WxJsapiSignature());
|
||||
|
||||
mockMvc.perform(post("/wx/oauth2/getConfigAddress")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("{\"url\":\"https://example.com/custom\"}"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getClerkLoginAddressBuildsAuthorizationUrl__covers_OAUTH_003() throws Exception {
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
when(wxMpService.getOAuth2Service().buildAuthorizationUrl(anyString(), anyString(), anyString()))
|
||||
.thenReturn("https://wx.example/auth?scope=snsapi_userinfo");
|
||||
|
||||
mockMvc.perform(post("/wx/oauth2/getClerkLoginAddress")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("{\"url\":\"https://example.com/callback\"}"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200))
|
||||
.andExpect(jsonPath("$.data").value("https://wx.example/auth?scope=snsapi_userinfo"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getCustomLoginAddressBuildsAuthorizationUrl__covers_OAUTH_004() throws Exception {
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
when(wxMpService.getOAuth2Service().buildAuthorizationUrl(anyString(), anyString(), anyString()))
|
||||
.thenReturn("https://wx.example/auth?scope=snsapi_userinfo");
|
||||
|
||||
mockMvc.perform(post("/wx/oauth2/getCustomLoginAddress")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("{\"url\":\"https://example.com/callback\"}"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200))
|
||||
.andExpect(jsonPath("$.data").value("https://wx.example/auth?scope=snsapi_userinfo"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void customLoginPersistsTokenAndReturnsPayload__covers_OAUTH_005() throws Exception {
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
|
||||
WxOAuth2AccessToken token = new WxOAuth2AccessToken();
|
||||
token.setOpenId(ApiTestDataSeeder.DEFAULT_CUSTOMER_OPEN_ID);
|
||||
WxOAuth2Service oAuth2Service = wxMpService.getOAuth2Service();
|
||||
Mockito.doReturn(token).when(oAuth2Service).getAccessToken(anyString());
|
||||
|
||||
WxOAuth2UserInfo userInfo = new WxOAuth2UserInfo();
|
||||
userInfo.setOpenid(ApiTestDataSeeder.DEFAULT_CUSTOMER_OPEN_ID);
|
||||
userInfo.setNickname("API Test Customer");
|
||||
userInfo.setHeadImgUrl("https://example.com/avatar.png");
|
||||
Mockito.doReturn(userInfo).when(oAuth2Service).getUserInfo(eq(token), eq(null));
|
||||
|
||||
mockMvc.perform(post("/wx/oauth2/custom/login")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("{\"code\":\"apitest-code\"}"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200))
|
||||
.andExpect(jsonPath("$.data.tokenValue").isString())
|
||||
.andExpect(jsonPath("$.data.tokenName").value(Constants.CUSTOM_USER_LOGIN_TOKEN));
|
||||
|
||||
PlayCustomUserInfoEntity customer = customUserInfoService.selectById(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID);
|
||||
if (customer == null || customer.getToken() == null || customer.getToken().isEmpty()) {
|
||||
throw new AssertionError("Expected customer token to be persisted after login");
|
||||
}
|
||||
Object cachedTenantId = redisCache.getCacheObject("TENANT_INFO:" + ApiTestDataSeeder.DEFAULT_CUSTOMER_ID);
|
||||
if (!ApiTestDataSeeder.DEFAULT_TENANT_ID.equals(cachedTenantId)) {
|
||||
throw new AssertionError("Expected Redis TENANT_INFO to be cached after login");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void customLoginReturnsUnauthorizedWhenWxOauthFails__covers_OAUTH_006() throws Exception {
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
WxOAuth2Service oAuth2Service = wxMpService.getOAuth2Service();
|
||||
Mockito.doThrow(new RuntimeException("wx-fail")).when(oAuth2Service).getAccessToken(eq("fail-code"));
|
||||
|
||||
mockMvc.perform(post("/wx/oauth2/custom/login")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("{\"code\":\"fail-code\"}"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(401));
|
||||
}
|
||||
|
||||
@Test
|
||||
void customLogoutInvalidatesToken__covers_OAUTH_008() throws Exception {
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
String token = wxTokenService.createWxUserToken(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID);
|
||||
customUserInfoService.updateTokenById(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID, token);
|
||||
|
||||
mockMvc.perform(get("/wx/oauth2/custom/logout")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CUSTOM_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + token))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200));
|
||||
|
||||
PlayCustomUserInfoEntity customer = customUserInfoService.selectById(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID);
|
||||
if (customer == null) {
|
||||
throw new AssertionError("Customer missing");
|
||||
}
|
||||
if (!"empty".equals(customer.getToken())) {
|
||||
throw new AssertionError("Expected token to be invalidated to 'empty'");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void clerkLoginPersistsTokenAndCachesTenant__covers_OAUTH_007() throws Exception {
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
|
||||
String clerkId = "clerk-oauth-apitest";
|
||||
String clerkOpenId = "openid-clerk-oauth-apitest";
|
||||
|
||||
PlayClerkUserInfoEntity existing = clerkUserInfoService.getById(clerkId);
|
||||
if (existing == null) {
|
||||
PlayClerkUserInfoEntity entity = new PlayClerkUserInfoEntity();
|
||||
entity.setId(clerkId);
|
||||
entity.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
entity.setOpenid(clerkOpenId);
|
||||
entity.setNickname("API Test Clerk OAuth");
|
||||
entity.setAvatar("https://example.com/avatar.png");
|
||||
entity.setSysUserId("");
|
||||
entity.setOnboardingState("1");
|
||||
entity.setListingState("1");
|
||||
entity.setClerkState("1");
|
||||
entity.setOnlineState("1");
|
||||
clerkUserInfoService.save(entity);
|
||||
} else {
|
||||
PlayClerkUserInfoEntity patch = new PlayClerkUserInfoEntity();
|
||||
patch.setId(clerkId);
|
||||
patch.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
patch.setOpenid(clerkOpenId);
|
||||
patch.setAvatar("https://example.com/avatar.png");
|
||||
patch.setSysUserId("");
|
||||
patch.setOnboardingState("1");
|
||||
patch.setListingState("1");
|
||||
patch.setClerkState("1");
|
||||
patch.setOnlineState("1");
|
||||
patch.setDeleted(Boolean.FALSE);
|
||||
clerkUserInfoService.updateById(patch);
|
||||
}
|
||||
|
||||
WxOAuth2AccessToken token = new WxOAuth2AccessToken();
|
||||
token.setOpenId(clerkOpenId);
|
||||
WxOAuth2Service oAuth2Service = wxMpService.getOAuth2Service();
|
||||
Mockito.doReturn(token).when(oAuth2Service).getAccessToken(anyString());
|
||||
|
||||
WxOAuth2UserInfo userInfo = new WxOAuth2UserInfo();
|
||||
userInfo.setOpenid(clerkOpenId);
|
||||
userInfo.setNickname("API Test Clerk");
|
||||
userInfo.setHeadImgUrl("https://example.com/avatar.png");
|
||||
Mockito.doReturn(userInfo).when(oAuth2Service).getUserInfo(eq(token), eq(null));
|
||||
|
||||
mockMvc.perform(post("/wx/oauth2/clerk/login")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("{\"code\":\"apitest-code\"}"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200))
|
||||
.andExpect(jsonPath("$.data.tokenValue").isString())
|
||||
.andExpect(jsonPath("$.data.tokenName").value(Constants.CLERK_USER_LOGIN_TOKEN))
|
||||
.andExpect(jsonPath("$.data.pcData.token").value(""))
|
||||
.andExpect(jsonPath("$.data.pcData.role").value(""));
|
||||
|
||||
PlayClerkUserInfoEntity clerk = clerkUserInfoService.selectById(clerkId);
|
||||
if (clerk == null || clerk.getToken() == null || clerk.getToken().isEmpty() || "empty".equals(clerk.getToken())) {
|
||||
throw new AssertionError("Expected clerk token to be persisted after login");
|
||||
}
|
||||
Object cachedTenantId = redisCache.getCacheObject("TENANT_INFO:" + clerkId);
|
||||
if (!ApiTestDataSeeder.DEFAULT_TENANT_ID.equals(cachedTenantId)) {
|
||||
throw new AssertionError("Expected clerk TENANT_INFO to be cached after login");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void clerkLogoutInvalidatesToken__covers_OAUTH_007() throws Exception {
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
String token = wxTokenService.createWxUserToken(ApiTestDataSeeder.DEFAULT_CLERK_ID);
|
||||
clerkUserInfoService.updateTokenById(ApiTestDataSeeder.DEFAULT_CLERK_ID, token);
|
||||
|
||||
mockMvc.perform(get("/wx/oauth2/clerk/logout")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CLERK_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + token))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200));
|
||||
|
||||
PlayClerkUserInfoEntity clerk = clerkUserInfoService.selectById(ApiTestDataSeeder.DEFAULT_CLERK_ID);
|
||||
if (clerk == null) {
|
||||
throw new AssertionError("Clerk missing");
|
||||
}
|
||||
if (!"empty".equals(clerk.getToken())) {
|
||||
throw new AssertionError("Expected clerk token to be invalidated to 'empty'");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void checkSubscribeReturnsBoolean__covers_OAUTH_011() throws Exception {
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
String token = wxTokenService.createWxUserToken(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID);
|
||||
customUserInfoService.updateTokenById(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID, token);
|
||||
|
||||
WxMpUser wxMpUser = new WxMpUser();
|
||||
wxMpUser.setSubscribe(true);
|
||||
when(wxMpService.getUserService().userInfo(ApiTestDataSeeder.DEFAULT_CUSTOMER_OPEN_ID)).thenReturn(wxMpUser);
|
||||
|
||||
mockMvc.perform(get("/wx/oauth2/checkSubscribe")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CUSTOM_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + token))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200))
|
||||
.andExpect(jsonPath("$.data").value(true));
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import com.starry.admin.common.apitest.ApiTestDataSeeder;
|
||||
import com.starry.admin.common.exception.ServiceException;
|
||||
import com.starry.admin.common.task.OverdueOrderHandlerTask;
|
||||
import com.starry.admin.modules.custom.module.entity.PlayCustomUserInfoEntity;
|
||||
import com.starry.admin.modules.order.module.constant.OrderConstant;
|
||||
@@ -125,7 +126,7 @@ class WxOrderInfoControllerApiTest extends WxCustomOrderApiTestSupport {
|
||||
}
|
||||
|
||||
@Test
|
||||
void duplicateContinuationRequestIsRejected() throws Exception {
|
||||
void continueRejectsSecondContinuationRequest__covers_ORD_002() throws Exception {
|
||||
String marker = "continue-" + LocalDateTime.now().toString();
|
||||
String orderId = createRandomOrder(marker);
|
||||
|
||||
@@ -168,7 +169,36 @@ class WxOrderInfoControllerApiTest extends WxCustomOrderApiTestSupport {
|
||||
}
|
||||
|
||||
@Test
|
||||
void randomOrderAcceptedByAnotherClerkHidesSensitiveFields() throws Exception {
|
||||
void continueRejectsNonOwnerClerk__covers_ORD_001() throws Exception {
|
||||
String marker = "continue-non-owner-" + LocalDateTime.now();
|
||||
String orderId = createRandomOrder(marker);
|
||||
|
||||
ensureTenantContext();
|
||||
playOrderInfoService.lambdaUpdate()
|
||||
.eq(PlayOrderInfoEntity::getId, orderId)
|
||||
.set(PlayOrderInfoEntity::getAcceptBy, OTHER_CLERK_ID)
|
||||
.set(PlayOrderInfoEntity::getOrderStatus, OrderConstant.OrderStatus.ACCEPTED.getCode())
|
||||
.update();
|
||||
|
||||
ArrayNode images = mapper.createArrayNode().add("https://example.com/proof.png");
|
||||
ObjectNode payload = mapper.createObjectNode()
|
||||
.put("orderId", orderId)
|
||||
.put("remark", "加场申请")
|
||||
.set("images", images);
|
||||
|
||||
mockMvc.perform(post("/wx/order/clerk/continue")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CLERK_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + clerkToken)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(payload.toString()))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(500))
|
||||
.andExpect(jsonPath("$.message").value("非本人订单;无法续单"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void randomOrderAcceptedByAnotherClerkHidesSensitiveFields__covers_ORD_003() throws Exception {
|
||||
String marker = PRIVACY_MARKER_PREFIX + "accepted-" + LocalDateTime.now();
|
||||
String orderId = createRandomOrder(marker);
|
||||
|
||||
@@ -197,6 +227,87 @@ class WxOrderInfoControllerApiTest extends WxCustomOrderApiTestSupport {
|
||||
assertThat(data.path("customAvatar").asText()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateReviewStateRejectsWhenAlreadyProcessed__covers_ORD_004() throws Exception {
|
||||
ensureTenantContext();
|
||||
PlayOrderContinueInfoEntity entity = new PlayOrderContinueInfoEntity();
|
||||
entity.setId("cont-processed-" + java.util.UUID.randomUUID().toString().substring(0, 8));
|
||||
entity.setOrderId("order-" + java.util.UUID.randomUUID().toString().substring(0, 8));
|
||||
entity.setOrderNo("ORDER-" + java.util.UUID.randomUUID().toString().substring(0, 6));
|
||||
entity.setCustomId(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID);
|
||||
entity.setClerkId(ApiTestDataSeeder.DEFAULT_CLERK_ID);
|
||||
entity.setPlaceType(OrderConstant.PlaceType.RANDOM.getCode());
|
||||
entity.setOrderMoney(ApiTestDataSeeder.DEFAULT_COMMODITY_PRICE);
|
||||
entity.setFinalAmount(ApiTestDataSeeder.DEFAULT_COMMODITY_PRICE);
|
||||
entity.setContinueMsg("processed");
|
||||
entity.setReviewedRequired("1");
|
||||
entity.setReviewedState("1");
|
||||
entity.setContinueTime(LocalDateTime.now().minusMinutes(1));
|
||||
orderContinueInfoService.create(entity);
|
||||
|
||||
String payload = "{\"id\":\"" + entity.getId() + "\",\"reviewState\":\"1\",\"remark\":\"ok\"}";
|
||||
mockMvc.perform(post("/wx/order/custom/updateReviewState")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CUSTOM_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + customerToken)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(payload))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(500))
|
||||
.andExpect(jsonPath("$.message").value("续单已处理"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void continueListByPageForcesCustomIdToSession__covers_ORD_005() throws Exception {
|
||||
ensureTenantContext();
|
||||
String ownId = "cont-own-" + java.util.UUID.randomUUID().toString().substring(0, 8);
|
||||
PlayOrderContinueInfoEntity own = new PlayOrderContinueInfoEntity();
|
||||
own.setId(ownId);
|
||||
own.setOrderId("order-own-" + java.util.UUID.randomUUID().toString().substring(0, 8));
|
||||
own.setOrderNo("OWN-" + java.util.UUID.randomUUID().toString().substring(0, 6));
|
||||
own.setCustomId(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID);
|
||||
own.setClerkId(ApiTestDataSeeder.DEFAULT_CLERK_ID);
|
||||
own.setPlaceType(OrderConstant.PlaceType.RANDOM.getCode());
|
||||
own.setOrderMoney(ApiTestDataSeeder.DEFAULT_COMMODITY_PRICE);
|
||||
own.setFinalAmount(ApiTestDataSeeder.DEFAULT_COMMODITY_PRICE);
|
||||
own.setReviewedRequired("1");
|
||||
own.setReviewedState("0");
|
||||
own.setContinueTime(LocalDateTime.now().minusMinutes(1));
|
||||
orderContinueInfoService.create(own);
|
||||
|
||||
String otherId = "cont-other-" + java.util.UUID.randomUUID().toString().substring(0, 8);
|
||||
PlayOrderContinueInfoEntity other = new PlayOrderContinueInfoEntity();
|
||||
other.setId(otherId);
|
||||
other.setOrderId("order-other-" + java.util.UUID.randomUUID().toString().substring(0, 8));
|
||||
other.setOrderNo("OTHER-" + java.util.UUID.randomUUID().toString().substring(0, 6));
|
||||
other.setCustomId("other-customer");
|
||||
other.setClerkId(ApiTestDataSeeder.DEFAULT_CLERK_ID);
|
||||
other.setPlaceType(OrderConstant.PlaceType.RANDOM.getCode());
|
||||
other.setOrderMoney(ApiTestDataSeeder.DEFAULT_COMMODITY_PRICE);
|
||||
other.setFinalAmount(ApiTestDataSeeder.DEFAULT_COMMODITY_PRICE);
|
||||
other.setReviewedRequired("1");
|
||||
other.setReviewedState("0");
|
||||
other.setContinueTime(LocalDateTime.now().minusMinutes(1));
|
||||
orderContinueInfoService.create(other);
|
||||
|
||||
String payload = "{\"pageNum\":1,\"pageSize\":20,\"customId\":\"other-customer\"}";
|
||||
MvcResult result = mockMvc.perform(post("/wx/order/custom/continueListByPage")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CUSTOM_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + customerToken)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(payload))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200))
|
||||
.andReturn();
|
||||
|
||||
JsonNode records = mapper.readTree(result.getResponse().getContentAsString()).path("data");
|
||||
assertThat(records.isArray()).isTrue();
|
||||
assertThat(records)
|
||||
.anyMatch(node -> ownId.equals(node.path("id").asText()))
|
||||
.noneMatch(node -> otherId.equals(node.path("id").asText()));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("randomOrderMaskingCases")
|
||||
void selectRandomOrderByIdAppliesPrivacyRules(OrderStatus status,
|
||||
@@ -334,7 +445,7 @@ class WxOrderInfoControllerApiTest extends WxCustomOrderApiTestSupport {
|
||||
}
|
||||
|
||||
@Test
|
||||
void queryByIdForRandomOrderAcceptedByAnotherClerkHidesCustomerInfoAndOrderStatus() throws Exception {
|
||||
void queryByIdForRandomOrderAcceptedByAnotherClerkHidesCustomerInfoAndOrderStatus__covers_CLK_013() throws Exception {
|
||||
String marker = "random-other-clerk-" + LocalDateTime.now();
|
||||
String orderId = createRandomOrder(marker);
|
||||
|
||||
@@ -366,6 +477,103 @@ class WxOrderInfoControllerApiTest extends WxCustomOrderApiTestSupport {
|
||||
assertThat(data.path("customAvatar").asText()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void queryByIdForCancelledOrderClearsWeiChatCode__covers_CLK_014() throws Exception {
|
||||
String marker = "cancelled-weiChatCode-" + LocalDateTime.now();
|
||||
String orderId = createRandomOrder(marker);
|
||||
|
||||
ensureTenantContext();
|
||||
playOrderInfoService.lambdaUpdate()
|
||||
.eq(PlayOrderInfoEntity::getId, orderId)
|
||||
.set(PlayOrderInfoEntity::getPlaceType, OrderConstant.PlaceType.SPECIFIED.getCode())
|
||||
.set(PlayOrderInfoEntity::getAcceptBy, ApiTestDataSeeder.DEFAULT_CLERK_ID)
|
||||
.set(PlayOrderInfoEntity::getOrderStatus, OrderConstant.OrderStatus.CANCELLED.getCode())
|
||||
.update();
|
||||
|
||||
MvcResult result = mockMvc.perform(get("/wx/clerk/order/queryById")
|
||||
.param("id", orderId)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CLERK_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + clerkToken))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200))
|
||||
.andReturn();
|
||||
|
||||
JsonNode data = mapper.readTree(result.getResponse().getContentAsString()).path("data");
|
||||
assertThat(data.path("orderStatus").asText()).isEqualTo(OrderConstant.OrderStatus.CANCELLED.getCode());
|
||||
assertThat(data.path("weiChatCode").asText()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void acceptOrderRequiresSubscribe__covers_CLK_015() throws Exception {
|
||||
String orderId = createRandomOrder("accept-subscribe-" + LocalDateTime.now());
|
||||
|
||||
Mockito.doThrow(new ServiceException("请先关注公众号然后再来使用系统~"))
|
||||
.when(wxCustomMpService)
|
||||
.checkSubscribeThrowsExp(Mockito.anyString(), Mockito.anyString());
|
||||
|
||||
mockMvc.perform(get("/wx/clerk/order/accept")
|
||||
.param("id", orderId)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CLERK_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + clerkToken))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(500))
|
||||
.andExpect(jsonPath("$.message").value("请先关注公众号然后再来使用系统~"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void startOrderTransitionsState__covers_CLK_016() throws Exception {
|
||||
String orderId = createRandomOrder("start-" + LocalDateTime.now());
|
||||
|
||||
ensureTenantContext();
|
||||
playOrderInfoService.lambdaUpdate()
|
||||
.eq(PlayOrderInfoEntity::getId, orderId)
|
||||
.set(PlayOrderInfoEntity::getAcceptBy, ApiTestDataSeeder.DEFAULT_CLERK_ID)
|
||||
.set(PlayOrderInfoEntity::getOrderStatus, OrderConstant.OrderStatus.ACCEPTED.getCode())
|
||||
.update();
|
||||
|
||||
mockMvc.perform(get("/wx/clerk/order/start")
|
||||
.param("id", orderId)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CLERK_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + clerkToken))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200))
|
||||
.andExpect(jsonPath("$.data").value("成功"));
|
||||
|
||||
ensureTenantContext();
|
||||
PlayOrderInfoEntity updated = playOrderInfoService.selectOrderInfoById(orderId);
|
||||
assertThat(updated.getOrderStatus()).isEqualTo(OrderConstant.OrderStatus.IN_PROGRESS.getCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
void clerkCancellationUpdatesOrderState__covers_CLK_019() throws Exception {
|
||||
String orderId = createRandomOrder("clerk-cancel-" + LocalDateTime.now());
|
||||
|
||||
ensureTenantContext();
|
||||
playOrderInfoService.lambdaUpdate()
|
||||
.eq(PlayOrderInfoEntity::getId, orderId)
|
||||
.set(PlayOrderInfoEntity::getAcceptBy, ApiTestDataSeeder.DEFAULT_CLERK_ID)
|
||||
.set(PlayOrderInfoEntity::getOrderStatus, OrderConstant.OrderStatus.ACCEPTED.getCode())
|
||||
.update();
|
||||
|
||||
String payload = "{\"orderId\":\"" + orderId + "\",\"refundReason\":\"clerk cancel\",\"images\":[\"https://img.example/1.png\"]}";
|
||||
mockMvc.perform(post("/wx/clerk/order/cancellation")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CLERK_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + clerkToken)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(payload))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200))
|
||||
.andExpect(jsonPath("$.data").value("成功"));
|
||||
|
||||
ensureTenantContext();
|
||||
PlayOrderInfoEntity cancelled = playOrderInfoService.selectOrderInfoById(orderId);
|
||||
assertThat(cancelled.getOrderStatus()).isEqualTo(OrderConstant.OrderStatus.CANCELLED.getCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
void queryByIdForNonRandomOrderAcceptedByAnotherClerkDoesNotMaskStatus() throws Exception {
|
||||
String marker = "specified-other-clerk-" + LocalDateTime.now();
|
||||
|
||||
@@ -0,0 +1,494 @@
|
||||
package com.starry.admin.api;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
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.github.binarywang.wxpay.bean.profitsharing.ProfitSharingResult;
|
||||
import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest;
|
||||
import com.github.binarywang.wxpay.bean.result.WxPayUnifiedOrderResult;
|
||||
import com.github.binarywang.wxpay.service.WxPayService;
|
||||
import com.starry.admin.common.apitest.ApiTestDataSeeder;
|
||||
import com.starry.admin.common.exception.ServiceException;
|
||||
import com.starry.admin.modules.custom.module.entity.PlayCustomLevelInfoEntity;
|
||||
import com.starry.admin.modules.custom.module.entity.PlayCustomUserInfoEntity;
|
||||
import com.starry.admin.modules.custom.service.IPlayCustomLevelInfoService;
|
||||
import com.starry.admin.modules.custom.service.IPlayCustomUserInfoService;
|
||||
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
|
||||
import com.starry.admin.modules.order.service.IPlayOrderInfoService;
|
||||
import com.starry.admin.modules.system.module.entity.SysTenantEntity;
|
||||
import com.starry.admin.modules.system.service.impl.SysTenantServiceImpl;
|
||||
import com.starry.admin.modules.weichat.service.WxCustomMpService;
|
||||
import com.starry.admin.modules.weichat.service.WxTokenService;
|
||||
import com.starry.admin.utils.SecurityUtils;
|
||||
import com.starry.common.constant.Constants;
|
||||
import java.math.BigDecimal;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.web.servlet.MvcResult;
|
||||
|
||||
class WxPayControllerApiTest extends AbstractApiTest {
|
||||
|
||||
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
|
||||
|
||||
@Autowired
|
||||
private WxTokenService wxTokenService;
|
||||
|
||||
@Autowired
|
||||
private IPlayCustomUserInfoService customUserInfoService;
|
||||
|
||||
@Autowired
|
||||
private IPlayCustomLevelInfoService customLevelInfoService;
|
||||
|
||||
@Autowired
|
||||
private IPlayOrderInfoService orderInfoService;
|
||||
|
||||
@Autowired
|
||||
private SysTenantServiceImpl tenantService;
|
||||
|
||||
@MockBean
|
||||
private WxCustomMpService wxCustomMpService;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getCustomPaymentAmountRejectsEmptyMoney__covers_PAY_001() throws Exception {
|
||||
String token = wxTokenService.createWxUserToken(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID);
|
||||
customUserInfoService.updateTokenById(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID, token);
|
||||
|
||||
mockMvc.perform(get("/wx/pay/custom/getCustomPaymentAmount")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CUSTOM_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + token)
|
||||
.param("money", ""))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(500));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getCustomPaymentAmountReturnsDiscountedAmount__covers_PAY_002() throws Exception {
|
||||
String levelId = "lvl-custom-apitest";
|
||||
PlayCustomLevelInfoEntity level = customLevelInfoService.getById(levelId);
|
||||
if (level == null) {
|
||||
level = new PlayCustomLevelInfoEntity();
|
||||
level.setId(levelId);
|
||||
level.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
level.setName("API Test Level");
|
||||
level.setLevel(1);
|
||||
level.setConsumptionAmount("0");
|
||||
level.setDiscount(80);
|
||||
customLevelInfoService.save(level);
|
||||
} else {
|
||||
level.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
level.setDiscount(80);
|
||||
customLevelInfoService.updateById(level);
|
||||
}
|
||||
customUserInfoService.lambdaUpdate()
|
||||
.set(PlayCustomUserInfoEntity::getLevelId, levelId)
|
||||
.eq(PlayCustomUserInfoEntity::getId, ApiTestDataSeeder.DEFAULT_CUSTOMER_ID)
|
||||
.update();
|
||||
|
||||
String token = wxTokenService.createWxUserToken(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID);
|
||||
customUserInfoService.updateTokenById(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID, token);
|
||||
|
||||
MvcResult result = mockMvc.perform(get("/wx/pay/custom/getCustomPaymentAmount")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CUSTOM_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + token)
|
||||
.param("money", "10.00"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200))
|
||||
.andReturn();
|
||||
|
||||
JsonNode root = OBJECT_MAPPER.readTree(result.getResponse().getContentAsString());
|
||||
BigDecimal actual = root.get("data").decimalValue();
|
||||
BigDecimal expected = new BigDecimal("0.80").multiply(new BigDecimal("10.00"));
|
||||
if (actual.compareTo(expected) != 0) {
|
||||
throw new AssertionError("Expected discounted amount=" + expected + " but got " + actual);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void createOrderCreatesRechargeOrderAndReturnsPayParams__covers_PAY_004() throws Exception {
|
||||
String token = wxTokenService.createWxUserToken(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID);
|
||||
customUserInfoService.updateTokenById(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID, token);
|
||||
|
||||
WxPayService wxPayService = org.mockito.Mockito.mock(WxPayService.class, org.mockito.Mockito.RETURNS_DEEP_STUBS);
|
||||
WxPayUnifiedOrderResult result = new WxPayUnifiedOrderResult();
|
||||
result.setPrepayId("prepay-123");
|
||||
when(wxPayService.unifiedOrder(any())).thenReturn(result);
|
||||
when(wxCustomMpService.getWxPay()).thenReturn(wxPayService);
|
||||
org.mockito.Mockito.doNothing().when(wxCustomMpService).checkSubscribeThrowsExp(anyString(), anyString());
|
||||
when(wxPayService.getConfig().getAppId()).thenReturn("wx-app");
|
||||
when(wxPayService.getConfig().getMchKey()).thenReturn("mch-key");
|
||||
when(wxPayService.getConfig().getNotifyUrl()).thenReturn("https://example.com/notify");
|
||||
|
||||
mockMvc.perform(get("/wx/pay/custom/createOrder")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CUSTOM_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + token)
|
||||
.param("money", "12.34"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200))
|
||||
.andExpect(jsonPath("$.data.appId").value("wx-app"))
|
||||
.andExpect(jsonPath("$.data.package").value(org.hamcrest.Matchers.startsWith("prepay_id=")))
|
||||
.andExpect(jsonPath("$.data.signType").value("MD5"))
|
||||
.andExpect(jsonPath("$.data.paySign").isString());
|
||||
|
||||
ArgumentCaptor<WxPayUnifiedOrderRequest> requestCaptor = ArgumentCaptor.forClass(WxPayUnifiedOrderRequest.class);
|
||||
verify(wxPayService).unifiedOrder(requestCaptor.capture());
|
||||
String outTradeNo = requestCaptor.getValue().getOutTradeNo();
|
||||
|
||||
PlayOrderInfoEntity order = orderInfoService.queryByOrderNo(outTradeNo);
|
||||
assertThat(order).as("recharge order should be created, outTradeNo=%s", outTradeNo).isNotNull();
|
||||
assertThat(order.getPurchaserBy()).isEqualTo(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID);
|
||||
assertThat(order.getOrderType()).isEqualTo("0");
|
||||
assertThat(order.getPayState()).as("pay_state should be pending before callback").isEqualTo("0");
|
||||
}
|
||||
|
||||
@Test
|
||||
void createOrderRejectsWhenUserNotSubscribed__covers_PAY_005() throws Exception {
|
||||
String token = wxTokenService.createWxUserToken(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID);
|
||||
customUserInfoService.updateTokenById(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID, token);
|
||||
|
||||
org.mockito.Mockito.doThrow(new ServiceException("请先关注公众号然后再来使用系统~"))
|
||||
.when(wxCustomMpService)
|
||||
.checkSubscribeThrowsExp(anyString(), anyString());
|
||||
|
||||
mockMvc.perform(get("/wx/pay/custom/createOrder")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CUSTOM_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + token)
|
||||
.param("money", "10.00"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(500))
|
||||
.andExpect(jsonPath("$.message").value("请先关注公众号然后再来使用系统~"));
|
||||
|
||||
verify(wxCustomMpService, never()).getWxPay();
|
||||
}
|
||||
|
||||
@Test
|
||||
void wxPayCallbackUpdatesPayStateAndBalanceAndIsIdempotent__covers_PAY_009__covers_PAY_010() throws Exception {
|
||||
PlayCustomUserInfoEntity customer = customUserInfoService.selectById(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID);
|
||||
if (customer == null) {
|
||||
throw new AssertionError("Missing seeded customer");
|
||||
}
|
||||
BigDecimal before = customer.getAccountBalance();
|
||||
|
||||
// Create a recharge order.
|
||||
String orderNo = orderInfoService.getOrderNo();
|
||||
orderInfoService.createRechargeOrder(orderNo, new BigDecimal("10.00"), new BigDecimal("10.00"),
|
||||
ApiTestDataSeeder.DEFAULT_CUSTOMER_ID);
|
||||
|
||||
WxPayService wxPayService = org.mockito.Mockito.mock(WxPayService.class, org.mockito.Mockito.RETURNS_DEEP_STUBS);
|
||||
when(wxCustomMpService.getWxPay()).thenReturn(wxPayService);
|
||||
when(wxPayService.getProfitSharingService().profitSharing(any())).thenReturn(null);
|
||||
org.mockito.Mockito.doNothing().when(wxCustomMpService).sendBalanceMessage(any());
|
||||
|
||||
String xml = "<xml>"
|
||||
+ "<out_trade_no>" + orderNo + "</out_trade_no>"
|
||||
+ "<attach>" + ApiTestDataSeeder.DEFAULT_TENANT_ID + "</attach>"
|
||||
+ "<transaction_id>tx-1</transaction_id>"
|
||||
+ "</xml>";
|
||||
|
||||
mockMvc.perform(post("/wx/pay/jsCallback")
|
||||
.contentType(MediaType.TEXT_XML)
|
||||
.content(xml))
|
||||
.andExpect(status().isOk());
|
||||
|
||||
PlayOrderInfoEntity order = orderInfoService.queryByOrderNo(orderNo);
|
||||
if (order == null) {
|
||||
throw new AssertionError("Order not found");
|
||||
}
|
||||
if (!"1".equals(order.getPayState())) {
|
||||
throw new AssertionError("Expected pay_state=1 after callback");
|
||||
}
|
||||
|
||||
PlayCustomUserInfoEntity afterFirst = customUserInfoService.selectById(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID);
|
||||
if (afterFirst == null) {
|
||||
throw new AssertionError("Missing customer after callback");
|
||||
}
|
||||
if (afterFirst.getAccountBalance().compareTo(before) <= 0) {
|
||||
throw new AssertionError("Expected account balance increased after callback");
|
||||
}
|
||||
|
||||
// replay same callback should not double add
|
||||
mockMvc.perform(post("/wx/pay/jsCallback")
|
||||
.contentType(MediaType.TEXT_XML)
|
||||
.content(xml))
|
||||
.andExpect(status().isOk());
|
||||
|
||||
PlayCustomUserInfoEntity afterSecond = customUserInfoService.selectById(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID);
|
||||
if (afterSecond == null) {
|
||||
throw new AssertionError("Missing customer after replay");
|
||||
}
|
||||
if (afterSecond.getAccountBalance().compareTo(afterFirst.getAccountBalance()) != 0) {
|
||||
throw new AssertionError("Expected callback replay to be idempotent for balance");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void wxPayCallbackSkipsProfitSharingWhenComputedAmountIsZero__covers_PAY_012() throws Exception {
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
SysTenantEntity tenant = tenantService.selectSysTenantByTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
if (tenant == null) {
|
||||
throw new AssertionError("Missing seeded tenant");
|
||||
}
|
||||
Integer originalRate = tenant.getProfitsharingRate();
|
||||
try {
|
||||
tenantService.lambdaUpdate()
|
||||
.eq(SysTenantEntity::getTenantId, ApiTestDataSeeder.DEFAULT_TENANT_ID)
|
||||
.set(SysTenantEntity::getProfitsharingRate, 1)
|
||||
.update();
|
||||
|
||||
String orderNo = orderInfoService.getOrderNo();
|
||||
// 1 cent recharge with 1% profit sharing => amount=0 (int truncation)
|
||||
orderInfoService.createRechargeOrder(orderNo, new BigDecimal("0.01"), new BigDecimal("0.01"),
|
||||
ApiTestDataSeeder.DEFAULT_CUSTOMER_ID);
|
||||
|
||||
WxPayService wxPayService = org.mockito.Mockito.mock(WxPayService.class, org.mockito.Mockito.RETURNS_DEEP_STUBS);
|
||||
when(wxCustomMpService.getWxPay()).thenReturn(wxPayService);
|
||||
org.mockito.Mockito.doNothing().when(wxCustomMpService).sendBalanceMessage(any());
|
||||
|
||||
String xml = "<xml>"
|
||||
+ "<out_trade_no>" + orderNo + "</out_trade_no>"
|
||||
+ "<attach>" + ApiTestDataSeeder.DEFAULT_TENANT_ID + "</attach>"
|
||||
+ "<transaction_id>tx-ps-0</transaction_id>"
|
||||
+ "</xml>";
|
||||
|
||||
mockMvc.perform(post("/wx/pay/jsCallback")
|
||||
.contentType(MediaType.TEXT_XML)
|
||||
.content(xml))
|
||||
.andExpect(status().isOk());
|
||||
|
||||
verify(wxPayService.getProfitSharingService(), never()).profitSharing(any());
|
||||
|
||||
PlayOrderInfoEntity order = orderInfoService.queryByOrderNo(orderNo);
|
||||
if (order == null) {
|
||||
throw new AssertionError("Order not found");
|
||||
}
|
||||
if (order.getProfitSharingAmount() != null) {
|
||||
throw new AssertionError("Expected profit_sharing_amount to remain null when amount=0");
|
||||
}
|
||||
} finally {
|
||||
tenantService.lambdaUpdate()
|
||||
.eq(SysTenantEntity::getTenantId, ApiTestDataSeeder.DEFAULT_TENANT_ID)
|
||||
.set(SysTenantEntity::getProfitsharingRate, originalRate == null ? 0 : originalRate)
|
||||
.update();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void wxPayCallbackWritesProfitSharingAmountWhenRatePositive__covers_PAY_013() throws Exception {
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
SysTenantEntity tenant = tenantService.selectSysTenantByTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
if (tenant == null) {
|
||||
throw new AssertionError("Missing seeded tenant");
|
||||
}
|
||||
Integer originalRate = tenant.getProfitsharingRate();
|
||||
try {
|
||||
tenantService.lambdaUpdate()
|
||||
.eq(SysTenantEntity::getTenantId, ApiTestDataSeeder.DEFAULT_TENANT_ID)
|
||||
.set(SysTenantEntity::getProfitsharingRate, 1)
|
||||
.update();
|
||||
|
||||
String orderNo = orderInfoService.getOrderNo();
|
||||
orderInfoService.createRechargeOrder(orderNo, new BigDecimal("1.00"), new BigDecimal("1.00"),
|
||||
ApiTestDataSeeder.DEFAULT_CUSTOMER_ID);
|
||||
|
||||
WxPayService wxPayService = org.mockito.Mockito.mock(WxPayService.class, org.mockito.Mockito.RETURNS_DEEP_STUBS);
|
||||
when(wxCustomMpService.getWxPay()).thenReturn(wxPayService);
|
||||
when(wxPayService.getProfitSharingService().profitSharing(any())).thenReturn(new ProfitSharingResult());
|
||||
org.mockito.Mockito.doNothing().when(wxCustomMpService).sendBalanceMessage(any());
|
||||
|
||||
String xml = "<xml>"
|
||||
+ "<out_trade_no>" + orderNo + "</out_trade_no>"
|
||||
+ "<attach>" + ApiTestDataSeeder.DEFAULT_TENANT_ID + "</attach>"
|
||||
+ "<transaction_id>tx-ps-1</transaction_id>"
|
||||
+ "</xml>";
|
||||
|
||||
mockMvc.perform(post("/wx/pay/jsCallback")
|
||||
.contentType(MediaType.TEXT_XML)
|
||||
.content(xml))
|
||||
.andExpect(status().isOk());
|
||||
|
||||
verify(wxPayService.getProfitSharingService()).profitSharing(any());
|
||||
|
||||
PlayOrderInfoEntity order = orderInfoService.queryByOrderNo(orderNo);
|
||||
if (order == null) {
|
||||
throw new AssertionError("Order not found");
|
||||
}
|
||||
if (order.getProfitSharingAmount() == null) {
|
||||
throw new AssertionError("Expected profit_sharing_amount to be set");
|
||||
}
|
||||
if (order.getProfitSharingAmount().compareTo(new BigDecimal("0.01")) != 0) {
|
||||
throw new AssertionError("Expected profit_sharing_amount=0.01, got: " + order.getProfitSharingAmount());
|
||||
}
|
||||
} finally {
|
||||
tenantService.lambdaUpdate()
|
||||
.eq(SysTenantEntity::getTenantId, ApiTestDataSeeder.DEFAULT_TENANT_ID)
|
||||
.set(SysTenantEntity::getProfitsharingRate, originalRate == null ? 0 : originalRate)
|
||||
.update();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void wxPayCallbackWithInvalidXmlStillReturnsSuccessAndNoStateChange__covers_PAY_006() throws Exception {
|
||||
String xml = "<xml><bad></xml>";
|
||||
|
||||
mockMvc.perform(post("/wx/pay/jsCallback")
|
||||
.contentType(MediaType.TEXT_XML)
|
||||
.content(xml))
|
||||
.andExpect(status().isOk());
|
||||
}
|
||||
|
||||
@Test
|
||||
void wxPayCallbackUnknownOrderDoesNotChangeState__covers_PAY_007() throws Exception {
|
||||
String orderNo = "unknown-order-no-apitest";
|
||||
String xml = "<xml>"
|
||||
+ "<out_trade_no>" + orderNo + "</out_trade_no>"
|
||||
+ "<attach>" + ApiTestDataSeeder.DEFAULT_TENANT_ID + "</attach>"
|
||||
+ "<transaction_id>tx-unknown</transaction_id>"
|
||||
+ "</xml>";
|
||||
|
||||
mockMvc.perform(post("/wx/pay/jsCallback")
|
||||
.contentType(MediaType.TEXT_XML)
|
||||
.content(xml))
|
||||
.andExpect(status().isOk());
|
||||
|
||||
PlayOrderInfoEntity order = orderInfoService.queryByOrderNo(orderNo);
|
||||
if (order != null) {
|
||||
throw new AssertionError("Did not expect an order to be created for unknown out_trade_no");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void wxPayCallbackDoesNotReprocessAlreadyPaidRechargeOrder__covers_PAY_008() throws Exception {
|
||||
PlayCustomUserInfoEntity customer = customUserInfoService.selectById(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID);
|
||||
if (customer == null) {
|
||||
throw new AssertionError("Missing seeded customer");
|
||||
}
|
||||
BigDecimal before = customer.getAccountBalance();
|
||||
|
||||
String orderNo = orderInfoService.getOrderNo();
|
||||
orderInfoService.createRechargeOrder(orderNo, new BigDecimal("10.00"), new BigDecimal("10.00"),
|
||||
ApiTestDataSeeder.DEFAULT_CUSTOMER_ID);
|
||||
PlayOrderInfoEntity order = orderInfoService.queryByOrderNo(orderNo);
|
||||
if (order == null) {
|
||||
throw new AssertionError("Expected order to exist");
|
||||
}
|
||||
order.setPayState("1");
|
||||
orderInfoService.updateById(order);
|
||||
|
||||
String xml = "<xml>"
|
||||
+ "<out_trade_no>" + orderNo + "</out_trade_no>"
|
||||
+ "<attach>" + ApiTestDataSeeder.DEFAULT_TENANT_ID + "</attach>"
|
||||
+ "<transaction_id>tx-repeat</transaction_id>"
|
||||
+ "</xml>";
|
||||
|
||||
mockMvc.perform(post("/wx/pay/jsCallback")
|
||||
.contentType(MediaType.TEXT_XML)
|
||||
.content(xml))
|
||||
.andExpect(status().isOk());
|
||||
|
||||
PlayCustomUserInfoEntity after = customUserInfoService.selectById(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID);
|
||||
if (after == null) {
|
||||
throw new AssertionError("Missing customer after callback");
|
||||
}
|
||||
if (after.getAccountBalance().compareTo(before) != 0) {
|
||||
throw new AssertionError("Expected no balance change for already paid order");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void wxPayCallbackDoesNotProcessNonRechargeOrderType__covers_PAY_008() throws Exception {
|
||||
String orderNo = orderInfoService.getOrderNo();
|
||||
orderInfoService.createRechargeOrder(orderNo, new BigDecimal("10.00"), new BigDecimal("10.00"),
|
||||
ApiTestDataSeeder.DEFAULT_CUSTOMER_ID);
|
||||
|
||||
PlayOrderInfoEntity order = orderInfoService.queryByOrderNo(orderNo);
|
||||
if (order == null) {
|
||||
throw new AssertionError("Expected order to exist");
|
||||
}
|
||||
order.setOrderType("1");
|
||||
order.setPayState("0");
|
||||
orderInfoService.updateById(order);
|
||||
|
||||
String xml = "<xml>"
|
||||
+ "<out_trade_no>" + orderNo + "</out_trade_no>"
|
||||
+ "<attach>" + ApiTestDataSeeder.DEFAULT_TENANT_ID + "</attach>"
|
||||
+ "<transaction_id>tx-nonrecharge</transaction_id>"
|
||||
+ "</xml>";
|
||||
|
||||
mockMvc.perform(post("/wx/pay/jsCallback")
|
||||
.contentType(MediaType.TEXT_XML)
|
||||
.content(xml))
|
||||
.andExpect(status().isOk());
|
||||
|
||||
PlayOrderInfoEntity after = orderInfoService.queryByOrderNo(orderNo);
|
||||
if (after == null) {
|
||||
throw new AssertionError("Order missing after callback");
|
||||
}
|
||||
if (!"0".equals(after.getPayState())) {
|
||||
throw new AssertionError("Expected pay_state unchanged for non-recharge order");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void wxPayCallbackMissingAttachDoesNotUpdateOrder__covers_PAY_011() throws Exception {
|
||||
String orderNo = orderInfoService.getOrderNo();
|
||||
orderInfoService.createRechargeOrder(orderNo, new BigDecimal("10.00"), new BigDecimal("10.00"),
|
||||
ApiTestDataSeeder.DEFAULT_CUSTOMER_ID);
|
||||
|
||||
String xml = "<xml>"
|
||||
+ "<out_trade_no>" + orderNo + "</out_trade_no>"
|
||||
+ "<transaction_id>tx-missing-attach</transaction_id>"
|
||||
+ "</xml>";
|
||||
|
||||
mockMvc.perform(post("/wx/pay/jsCallback")
|
||||
.contentType(MediaType.TEXT_XML)
|
||||
.content(xml))
|
||||
.andExpect(status().isOk());
|
||||
|
||||
PlayOrderInfoEntity after = orderInfoService.queryByOrderNo(orderNo);
|
||||
if (after == null) {
|
||||
throw new AssertionError("Order missing after callback");
|
||||
}
|
||||
if (!"0".equals(after.getPayState())) {
|
||||
throw new AssertionError("Expected pay_state unchanged when attach is missing");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void createOrderRejectsMoneyBelowOne__covers_PAY_003() throws Exception {
|
||||
String token = wxTokenService.createWxUserToken(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID);
|
||||
customUserInfoService.updateTokenById(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID, token);
|
||||
|
||||
mockMvc.perform(get("/wx/pay/custom/createOrder")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CUSTOM_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + token)
|
||||
.param("money", "0.99"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(500));
|
||||
|
||||
verify(wxCustomMpService, never()).getWxPay();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
package com.starry.admin.api;
|
||||
|
||||
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.starry.admin.common.apitest.ApiTestDataSeeder;
|
||||
import com.starry.admin.modules.clerk.service.IPlayClerkUserInfoService;
|
||||
import com.starry.admin.modules.shop.module.entity.PlayShopArticleInfoEntity;
|
||||
import com.starry.admin.modules.shop.module.entity.PlayShopCarouselInfoEntity;
|
||||
import com.starry.admin.modules.shop.service.IPlayShopArticleInfoService;
|
||||
import com.starry.admin.modules.shop.service.IPlayShopCarouselInfoService;
|
||||
import com.starry.admin.modules.weichat.service.WxTokenService;
|
||||
import com.starry.admin.utils.SecurityUtils;
|
||||
import com.starry.common.constant.Constants;
|
||||
import com.starry.common.utils.IdUtils;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.MediaType;
|
||||
|
||||
class WxShopControllerApiTest extends AbstractApiTest {
|
||||
|
||||
@Autowired
|
||||
private IPlayShopCarouselInfoService carouselInfoService;
|
||||
|
||||
@Autowired
|
||||
private IPlayShopArticleInfoService articleInfoService;
|
||||
|
||||
@Autowired
|
||||
private WxTokenService wxTokenService;
|
||||
|
||||
@Autowired
|
||||
private IPlayClerkUserInfoService clerkUserInfoService;
|
||||
|
||||
private final java.util.List<String> carouselIdsToCleanup = new java.util.ArrayList<>();
|
||||
private final java.util.List<String> articleIdsToCleanup = new java.util.ArrayList<>();
|
||||
private String clerkToken;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
clerkToken = wxTokenService.createWxUserToken(ApiTestDataSeeder.DEFAULT_CLERK_ID);
|
||||
clerkUserInfoService.updateTokenById(ApiTestDataSeeder.DEFAULT_CLERK_ID, clerkToken);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
if (!carouselIdsToCleanup.isEmpty()) {
|
||||
carouselInfoService.removeByIds(carouselIdsToCleanup);
|
||||
carouselIdsToCleanup.clear();
|
||||
}
|
||||
if (!articleIdsToCleanup.isEmpty()) {
|
||||
articleInfoService.removeByIds(articleIdsToCleanup);
|
||||
articleIdsToCleanup.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void getShopHomeCarouseInfoReturnsEnabledIndexZeroOnly__covers_SHOP_001() throws Exception {
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
String urlA = "https://example.com/carousel-a.png";
|
||||
String urlB = "https://example.com/carousel-b.png";
|
||||
insertCarousel("1", "0", urlA);
|
||||
insertCarousel("1", "0", urlB);
|
||||
insertCarousel("0", "0", "https://example.com/carousel-disabled.png");
|
||||
insertCarousel("1", "1", "https://example.com/carousel-nonhome.png");
|
||||
|
||||
mockMvc.perform(get("/wx/shop/custom/getShopHomeCarouseInfo")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200))
|
||||
.andExpect(jsonPath("$.data.length()").value(2))
|
||||
.andExpect(jsonPath("$.data[*].carouselUrl").value(org.hamcrest.Matchers.hasItems(urlA, urlB)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void readShopArticleInfoIncrementsVisitsNumber__covers_SHOP_002() throws Exception {
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
String articleId = insertShopArticle(0);
|
||||
|
||||
mockMvc.perform(get("/wx/shop/clerk/readShopArticleInfo")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CLERK_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + clerkToken)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("{\"id\":\"" + articleId + "\"}"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200));
|
||||
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
PlayShopArticleInfoEntity stored = articleInfoService.selectById(articleId);
|
||||
if (stored == null) {
|
||||
throw new AssertionError("Expected shop article to exist");
|
||||
}
|
||||
if (stored.getVisitsNumber() == null || stored.getVisitsNumber() != 1) {
|
||||
throw new AssertionError("Expected visitsNumber=1, got " + stored.getVisitsNumber());
|
||||
}
|
||||
}
|
||||
|
||||
private String insertCarousel(String enableState, String carouselIndex, String url) {
|
||||
PlayShopCarouselInfoEntity entity = new PlayShopCarouselInfoEntity();
|
||||
String id = "apitest-carousel-" + IdUtils.getUuid();
|
||||
entity.setId(id);
|
||||
entity.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
entity.setCarouselIndex(carouselIndex);
|
||||
entity.setEnableState(enableState);
|
||||
entity.setCarouselUrl(url);
|
||||
entity.setDeleted(false);
|
||||
carouselInfoService.save(entity);
|
||||
carouselIdsToCleanup.add(id);
|
||||
return id;
|
||||
}
|
||||
|
||||
private String insertShopArticle(int visitsNumber) {
|
||||
PlayShopArticleInfoEntity entity = new PlayShopArticleInfoEntity();
|
||||
String id = "apitest-article-" + IdUtils.getUuid();
|
||||
entity.setId(id);
|
||||
entity.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
entity.setArticleType("apitest");
|
||||
entity.setArticleTitle("API Test Shop Article");
|
||||
entity.setArticleContent("content");
|
||||
entity.setVisitsNumber(visitsNumber);
|
||||
entity.setDeleted(false);
|
||||
Date now = Date.from(LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant());
|
||||
entity.setCreatedTime(now);
|
||||
entity.setUpdatedTime(now);
|
||||
articleInfoService.save(entity);
|
||||
articleIdsToCleanup.add(id);
|
||||
return id;
|
||||
}
|
||||
}
|
||||
@@ -121,7 +121,7 @@ class WxWithdrawControllerApiTest extends AbstractApiTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void balanceEndpointAggregatesAvailableAndPendingEarnings() throws Exception {
|
||||
void balanceEndpointAggregatesAvailableAndPendingEarnings__covers_WD_001() throws Exception {
|
||||
ensureTenantContext();
|
||||
LocalDateTime now = LocalDateTime.now().withNano(0);
|
||||
String availableId = insertEarningsLine(
|
||||
@@ -154,7 +154,7 @@ class WxWithdrawControllerApiTest extends AbstractApiTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWithdrawRejectsNonPositiveAmount() throws Exception {
|
||||
void createWithdrawRejectsNonPositiveAmount__covers_WD_003() throws Exception {
|
||||
ensureTenantContext();
|
||||
mockMvc.perform(post("/wx/withdraw/requests")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
@@ -168,7 +168,7 @@ class WxWithdrawControllerApiTest extends AbstractApiTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWithdrawLocksEligibleEarningsLines() throws Exception {
|
||||
void createWithdrawLocksEligibleEarningsLines__covers_WD_004() throws Exception {
|
||||
ensureTenantContext();
|
||||
BigDecimal amount = new BigDecimal("80.00");
|
||||
String firstLine = insertEarningsLine(
|
||||
@@ -217,7 +217,7 @@ class WxWithdrawControllerApiTest extends AbstractApiTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWithdrawHandlesMixedPositiveAndNegativeLines() throws Exception {
|
||||
void createWithdrawHandlesMixedPositiveAndNegativeLines__covers_WD_004() throws Exception {
|
||||
ensureTenantContext();
|
||||
LocalDateTime base = LocalDateTime.now().minusHours(4);
|
||||
BigDecimal[] amounts = {
|
||||
@@ -263,7 +263,7 @@ class WxWithdrawControllerApiTest extends AbstractApiTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void earningsEndpointFiltersByStatus() throws Exception {
|
||||
void earningsEndpointFiltersByStatus__covers_WD_002() throws Exception {
|
||||
ensureTenantContext();
|
||||
String availableId = insertEarningsLine(
|
||||
"earning-available",
|
||||
@@ -300,7 +300,7 @@ class WxWithdrawControllerApiTest extends AbstractApiTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void concurrentWithdrawRequestsCompeteForSameEarningsLines() throws Exception {
|
||||
void concurrentWithdrawRequestsCompeteForSameEarningsLines__covers_WD_004() throws Exception {
|
||||
ensureTenantContext();
|
||||
String firstLine = insertEarningsLine(
|
||||
"concurrent-one",
|
||||
@@ -345,6 +345,76 @@ class WxWithdrawControllerApiTest extends AbstractApiTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void earningsEndpointSupportsMultipleDateFormats__covers_WD_002() throws Exception {
|
||||
ensureTenantContext();
|
||||
LocalDateTime base = LocalDateTime.now().withNano(0).withHour(10).withMinute(0).withSecond(0);
|
||||
|
||||
String early = insertEarningsLineWithCreatedTime(
|
||||
"time-early",
|
||||
new BigDecimal("10.00"),
|
||||
EarningsStatus.AVAILABLE,
|
||||
base.minusDays(2));
|
||||
String middle = insertEarningsLineWithCreatedTime(
|
||||
"time-middle",
|
||||
new BigDecimal("20.00"),
|
||||
EarningsStatus.AVAILABLE,
|
||||
base.minusDays(1));
|
||||
String late = insertEarningsLineWithCreatedTime(
|
||||
"time-late",
|
||||
new BigDecimal("30.00"),
|
||||
EarningsStatus.AVAILABLE,
|
||||
base);
|
||||
earningsToCleanup.add(early);
|
||||
earningsToCleanup.add(middle);
|
||||
earningsToCleanup.add(late);
|
||||
|
||||
String beginIso = base.minusDays(1).format(java.time.format.DateTimeFormatter.ISO_DATE_TIME);
|
||||
String end12Hour = base.plusDays(1).format(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss"));
|
||||
|
||||
mockMvc.perform(get("/wx/withdraw/earnings")
|
||||
.param("pageNum", "1")
|
||||
.param("pageSize", "10")
|
||||
.param("beginTime", beginIso)
|
||||
.param("endTime", end12Hour)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CLERK_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + clerkToken))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200))
|
||||
.andExpect(jsonPath("$.data").isArray())
|
||||
.andExpect(jsonPath("$.data[*].id").value(org.hamcrest.Matchers.hasItems(middle, late)))
|
||||
.andExpect(jsonPath("$.data[*].id").value(org.hamcrest.Matchers.not(org.hamcrest.Matchers.hasItem(early))));
|
||||
}
|
||||
|
||||
@Test
|
||||
void requestLogsRejectsWhenNotOwner__covers_WD_005() throws Exception {
|
||||
ensureTenantContext();
|
||||
WithdrawalRequestEntity req = new WithdrawalRequestEntity();
|
||||
String requestId = "apitest-withdraw-" + IdUtils.getUuid();
|
||||
req.setId(requestId);
|
||||
req.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
req.setClerkId(ApiTestDataSeeder.DEFAULT_CLERK_ID);
|
||||
req.setAmount(new BigDecimal("10.00"));
|
||||
req.setFee(BigDecimal.ZERO);
|
||||
req.setNetAmount(new BigDecimal("10.00"));
|
||||
req.setStatus("pending");
|
||||
withdrawalService.save(req);
|
||||
withdrawalsToCleanup.add(requestId);
|
||||
|
||||
String otherClerkId = "apitest-clerk-other-" + IdUtils.getUuid().substring(0, 6);
|
||||
String otherToken = wxTokenService.createWxUserToken(otherClerkId);
|
||||
ensureActiveClerk(otherClerkId, "openid-" + otherClerkId, otherToken);
|
||||
|
||||
mockMvc.perform(get("/wx/withdraw/requests/" + requestId + "/logs")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CLERK_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + otherToken))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(500))
|
||||
.andExpect(jsonPath("$.message").value("无权查看"));
|
||||
}
|
||||
|
||||
private String insertEarningsLine(
|
||||
String suffix, BigDecimal amount, EarningsStatus status, LocalDateTime unlockAt) {
|
||||
return insertEarningsLine(suffix, amount, status, unlockAt, EarningsType.ORDER);
|
||||
@@ -376,6 +446,72 @@ class WxWithdrawControllerApiTest extends AbstractApiTest {
|
||||
return id;
|
||||
}
|
||||
|
||||
private String insertEarningsLineWithCreatedTime(
|
||||
String suffix,
|
||||
BigDecimal amount,
|
||||
EarningsStatus status,
|
||||
LocalDateTime createdAt) {
|
||||
EarningsLineEntity entity = new EarningsLineEntity();
|
||||
String id = "earn-" + suffix + "-" + IdUtils.getUuid();
|
||||
entity.setId(id);
|
||||
entity.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
entity.setClerkId(ApiTestDataSeeder.DEFAULT_CLERK_ID);
|
||||
String rawOrderId = ORDER_ID_PREFIX + IdUtils.getUuid();
|
||||
entity.setOrderId(rawOrderId.length() <= 32 ? rawOrderId : rawOrderId.substring(0, 32));
|
||||
entity.setAmount(amount);
|
||||
entity.setStatus(status.getCode());
|
||||
entity.setUnlockTime(createdAt.plusHours(1));
|
||||
Date stamp = toDate(createdAt);
|
||||
entity.setCreatedBy(ApiTestDataSeeder.DEFAULT_ADMIN_USER_ID);
|
||||
entity.setCreatedTime(toDate(LocalDateTime.now()));
|
||||
entity.setUpdatedBy(ApiTestDataSeeder.DEFAULT_ADMIN_USER_ID);
|
||||
entity.setUpdatedTime(toDate(LocalDateTime.now()));
|
||||
earningsService.save(entity);
|
||||
earningsService.lambdaUpdate()
|
||||
.set(EarningsLineEntity::getCreatedTime, stamp)
|
||||
.set(EarningsLineEntity::getUpdatedTime, stamp)
|
||||
.eq(EarningsLineEntity::getId, id)
|
||||
.update();
|
||||
return id;
|
||||
}
|
||||
|
||||
private void ensureActiveClerk(String clerkId, String openId, String token) {
|
||||
ensureTenantContext();
|
||||
com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity existing = clerkUserInfoService.getById(clerkId);
|
||||
if (existing == null) {
|
||||
com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity entity =
|
||||
new com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity();
|
||||
entity.setId(clerkId);
|
||||
entity.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
entity.setOpenid(openId);
|
||||
entity.setNickname("API Test Clerk");
|
||||
entity.setAvatar("https://example.com/avatar.png");
|
||||
entity.setSysUserId("");
|
||||
entity.setOnboardingState("1");
|
||||
entity.setListingState("1");
|
||||
entity.setClerkState("1");
|
||||
entity.setOnlineState("1");
|
||||
entity.setToken(token);
|
||||
clerkUserInfoService.save(entity);
|
||||
return;
|
||||
}
|
||||
com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity patch =
|
||||
new com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity();
|
||||
patch.setId(clerkId);
|
||||
patch.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
patch.setOpenid(openId);
|
||||
patch.setNickname(existing.getNickname());
|
||||
patch.setAvatar(existing.getAvatar());
|
||||
patch.setSysUserId("");
|
||||
patch.setOnboardingState("1");
|
||||
patch.setListingState("1");
|
||||
patch.setClerkState("1");
|
||||
patch.setOnlineState("1");
|
||||
patch.setDeleted(Boolean.FALSE);
|
||||
patch.setToken(token);
|
||||
clerkUserInfoService.updateById(patch);
|
||||
}
|
||||
|
||||
private void ensureTenantContext() {
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
package com.starry.admin.api;
|
||||
|
||||
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.jayway.jsonpath.JsonPath;
|
||||
import com.starry.admin.common.apitest.ApiTestDataSeeder;
|
||||
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;
|
||||
import com.starry.admin.modules.clerk.service.IPlayClerkUserInfoService;
|
||||
import com.starry.admin.modules.weichat.service.WxTokenService;
|
||||
import com.starry.admin.modules.withdraw.entity.ClerkPayeeProfileEntity;
|
||||
import com.starry.admin.modules.withdraw.service.IClerkPayeeProfileService;
|
||||
import com.starry.admin.utils.SecurityUtils;
|
||||
import com.starry.common.constant.Constants;
|
||||
import com.starry.common.utils.IdUtils;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.web.servlet.MvcResult;
|
||||
|
||||
class WxWithdrawPayeeControllerApiTest extends AbstractApiTest {
|
||||
|
||||
@Autowired
|
||||
private WxTokenService wxTokenService;
|
||||
|
||||
@Autowired
|
||||
private IPlayClerkUserInfoService clerkUserInfoService;
|
||||
|
||||
@Autowired
|
||||
private IClerkPayeeProfileService payeeProfileService;
|
||||
|
||||
private String clerkId;
|
||||
private String clerkToken;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
|
||||
clerkId = "clerk-payee-" + IdUtils.getUuid();
|
||||
String clerkOpenId = "openid-payee-" + IdUtils.getUuid();
|
||||
clerkToken = wxTokenService.createWxUserToken(clerkId);
|
||||
String phone = "139" + String.format("%08d", java.util.concurrent.ThreadLocalRandom.current().nextInt(0, 100000000));
|
||||
|
||||
PlayClerkUserInfoEntity clerk = new PlayClerkUserInfoEntity();
|
||||
clerk.setId(clerkId);
|
||||
clerk.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
clerk.setSysUserId(ApiTestDataSeeder.DEFAULT_ADMIN_USER_ID);
|
||||
clerk.setOpenid(clerkOpenId);
|
||||
clerk.setNickname("payee-test");
|
||||
clerk.setGroupId(ApiTestDataSeeder.DEFAULT_GROUP_ID);
|
||||
clerk.setLevelId(ApiTestDataSeeder.DEFAULT_CLERK_LEVEL_ID);
|
||||
clerk.setFixingLevel("1");
|
||||
clerk.setSex("2");
|
||||
clerk.setPhone(phone);
|
||||
clerk.setWeiChatCode("payee-test-" + IdUtils.getUuid());
|
||||
clerk.setAvatar("https://example.com/avatar.png");
|
||||
clerk.setAccountBalance(java.math.BigDecimal.ZERO);
|
||||
clerk.setOnboardingState("1");
|
||||
clerk.setListingState("1");
|
||||
clerk.setDisplayState("1");
|
||||
clerk.setOnlineState("1");
|
||||
clerk.setRandomOrderState("1");
|
||||
clerk.setClerkState("1");
|
||||
clerk.setEntryTime(java.time.LocalDateTime.now());
|
||||
clerk.setToken(clerkToken);
|
||||
clerkUserInfoService.save(clerk);
|
||||
|
||||
payeeProfileService.lambdaUpdate()
|
||||
.eq(ClerkPayeeProfileEntity::getTenantId, ApiTestDataSeeder.DEFAULT_TENANT_ID)
|
||||
.eq(ClerkPayeeProfileEntity::getClerkId, clerkId)
|
||||
.remove();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
|
||||
payeeProfileService.lambdaUpdate()
|
||||
.eq(ClerkPayeeProfileEntity::getTenantId, ApiTestDataSeeder.DEFAULT_TENANT_ID)
|
||||
.eq(ClerkPayeeProfileEntity::getClerkId, clerkId)
|
||||
.remove();
|
||||
|
||||
clerkUserInfoService.lambdaUpdate()
|
||||
.eq(PlayClerkUserInfoEntity::getId, clerkId)
|
||||
.remove();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getProfileReturnsNullWhenAbsent__covers_PAYEE_001() throws Exception {
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
|
||||
mockMvc.perform(get("/wx/withdraw/payee")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CLERK_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + clerkToken))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200))
|
||||
.andExpect(jsonPath("$.data").isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void upsertRejectsMissingQrCodeUrl__covers_PAYEE_002() throws Exception {
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
|
||||
mockMvc.perform(post("/wx/withdraw/payee")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CLERK_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + clerkToken)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("{\"qrCodeUrl\":\"\"}"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(500))
|
||||
.andExpect(jsonPath("$.message").value("请上传支付宝收款二维码"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void upsertDefaultsChannelAndDisplayNameAndClearsConfirmation__covers_PAYEE_003() throws Exception {
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
PlayClerkUserInfoEntity clerk = clerkUserInfoService.selectById(clerkId);
|
||||
String expectedDisplayName = clerk != null ? clerk.getNickname() : null;
|
||||
|
||||
MvcResult result = mockMvc.perform(post("/wx/withdraw/payee")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CLERK_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + clerkToken)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("{\"channel\":\"\",\"qrCodeUrl\":\"https://example.com/payee.png\",\"displayName\":\"\"}"))
|
||||
.andExpect(status().isOk())
|
||||
.andReturn();
|
||||
|
||||
String responseBody = result.getResponse().getContentAsString();
|
||||
Integer businessCode = JsonPath.read(responseBody, "$.code");
|
||||
Assertions.assertEquals(200, businessCode, "Unexpected response: " + responseBody);
|
||||
Assertions.assertEquals("ALIPAY_QR", JsonPath.read(responseBody, "$.data.channel"));
|
||||
Assertions.assertEquals("https://example.com/payee.png", JsonPath.read(responseBody, "$.data.qrCodeUrl"));
|
||||
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
ClerkPayeeProfileEntity stored = payeeProfileService.getByClerk(
|
||||
ApiTestDataSeeder.DEFAULT_TENANT_ID, clerkId);
|
||||
if (stored == null) {
|
||||
throw new AssertionError("Expected payee profile to be stored");
|
||||
}
|
||||
if (!"ALIPAY_QR".equals(stored.getChannel())) {
|
||||
throw new AssertionError("Expected default channel ALIPAY_QR");
|
||||
}
|
||||
if (expectedDisplayName != null && !expectedDisplayName.equals(stored.getDisplayName())) {
|
||||
throw new AssertionError("Expected displayName default to clerk nickname");
|
||||
}
|
||||
if (stored.getLastConfirmedAt() != null) {
|
||||
throw new AssertionError("Expected lastConfirmedAt to be cleared after upsert");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void confirmRejectsWhenNoQrCodeUploaded__covers_PAYEE_004() throws Exception {
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
|
||||
mockMvc.perform(post("/wx/withdraw/payee/confirm")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CLERK_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + clerkToken)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("{\"trace\":\"" + IdUtils.getUuid() + "\"}"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(500))
|
||||
.andExpect(jsonPath("$.message").value("请先上传支付宝收款二维码"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
package com.starry.admin.common.security.filter;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import com.starry.admin.common.component.JwtToken;
|
||||
import com.starry.admin.common.exception.CustomException;
|
||||
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;
|
||||
import com.starry.admin.modules.clerk.service.impl.PlayClerkUserInfoServiceImpl;
|
||||
import com.starry.admin.modules.custom.module.entity.PlayCustomUserInfoEntity;
|
||||
import com.starry.admin.modules.custom.service.impl.PlayCustomUserInfoServiceImpl;
|
||||
import com.starry.admin.modules.system.module.entity.SysTenantEntity;
|
||||
import com.starry.admin.modules.system.service.ISysTenantService;
|
||||
import com.starry.admin.modules.system.service.SysUserService;
|
||||
import com.starry.admin.modules.weichat.service.WxTokenService;
|
||||
import com.starry.common.redis.RedisCache;
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.web.servlet.HandlerExceptionResolver;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class JwtAuthenticationTokenFilterTest {
|
||||
|
||||
@Mock
|
||||
private WxTokenService tokenService;
|
||||
|
||||
@Mock
|
||||
private JwtToken jwtToken;
|
||||
|
||||
@Mock
|
||||
private HandlerExceptionResolver resolver;
|
||||
|
||||
@Mock
|
||||
private PlayCustomUserInfoServiceImpl customUserInfoService;
|
||||
|
||||
@Mock
|
||||
private PlayClerkUserInfoServiceImpl clerkUserInfoService;
|
||||
|
||||
@Mock
|
||||
private ISysTenantService sysTenantService;
|
||||
|
||||
@Mock
|
||||
private RedisCache redisCache;
|
||||
|
||||
@Mock
|
||||
private SysUserService userService;
|
||||
|
||||
@InjectMocks
|
||||
private JwtAuthenticationTokenFilter filter;
|
||||
|
||||
@Test
|
||||
void wxPayCallbackBypassesTenantKeyAndAuth__covers_GW_003() throws ServletException, IOException {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("POST", "/wx/pay/jsCallback");
|
||||
request.setServletPath("/wx/pay/jsCallback");
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
|
||||
FilterChain chain = (req, resp) -> response.addHeader("X-Chain", "called");
|
||||
|
||||
filter.doFilter(request, response, chain);
|
||||
|
||||
assertThat(response.getHeader("X-Chain")).isEqualTo("called");
|
||||
verify(resolver, never()).resolveException(any(), any(), any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void wxRouteMissingTenantKeyTriggersResolverAndStopsChain__covers_GW_001__covers_GW_002() throws ServletException, IOException {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/wx/custom/queryById");
|
||||
request.setServletPath("/wx/custom/queryById");
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
|
||||
FilterChain chain = (req, resp) -> response.addHeader("X-Chain", "called");
|
||||
|
||||
filter.doFilter(request, response, chain);
|
||||
|
||||
assertThat(response.getHeader("X-Chain")).isNull();
|
||||
verify(resolver).resolveException(eq(request), eq(response), eq(null), any(CustomException.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void noLoginWxRouteInvalidTenantTriggersResolver__covers_GW_004() throws ServletException, IOException {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/wx/common/area/tree");
|
||||
request.setServletPath("/wx/common/area/tree");
|
||||
request.addHeader("tenantkey", "unknown-tenant");
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
|
||||
FilterChain chain = (req, resp) -> response.addHeader("X-Chain", "called");
|
||||
|
||||
when(sysTenantService.selectByTenantKey("unknown-tenant")).thenReturn(null);
|
||||
|
||||
filter.doFilter(request, response, chain);
|
||||
|
||||
assertThat(response.getHeader("X-Chain")).isNull();
|
||||
verify(resolver).resolveException(eq(request), eq(response), eq(null), any(CustomException.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void noLoginWxRouteTenantLookupThrowsReturns401Json__covers_GW_004() throws ServletException, IOException {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/wx/common/area/tree");
|
||||
request.setServletPath("/wx/common/area/tree");
|
||||
request.addHeader("tenantkey", "tenantkey-1");
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
|
||||
FilterChain chain = (req, resp) -> response.addHeader("X-Chain", "called");
|
||||
|
||||
when(sysTenantService.selectByTenantKey("tenantkey-1")).thenThrow(new RuntimeException("boom"));
|
||||
|
||||
filter.doFilter(request, response, chain);
|
||||
|
||||
assertThat(response.getHeader("X-Chain")).isNull();
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
assertThat(response.getContentAsString()).contains("\"code\":401");
|
||||
}
|
||||
|
||||
@Test
|
||||
void loginRequiredWxRouteTenantLookupThrowsReturns401Json__covers_GW_005__covers_GW_006() throws ServletException, IOException {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/wx/custom/queryById");
|
||||
request.setServletPath("/wx/custom/queryById");
|
||||
request.addHeader("tenantkey", "tenantkey-1");
|
||||
request.addHeader(com.starry.common.constant.Constants.CUSTOM_USER_LOGIN_TOKEN, "Bearer bad-token");
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
|
||||
FilterChain chain = (req, resp) -> response.addHeader("X-Chain", "called");
|
||||
|
||||
when(tokenService.getWxUserIdByToken(any())).thenThrow(new RuntimeException("bad token"));
|
||||
|
||||
filter.doFilter(request, response, chain);
|
||||
|
||||
assertThat(response.getHeader("X-Chain")).isNull();
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
assertThat(response.getContentAsString()).contains("\"code\":401");
|
||||
}
|
||||
|
||||
@Test
|
||||
void wxRouteWithValidTenantPassesChainAndSetsTenantContext__covers_GW_004() throws ServletException, IOException {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/wx/common/area/tree");
|
||||
request.setServletPath("/wx/common/area/tree");
|
||||
request.addHeader("tenantkey", "tenantkey-1");
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
|
||||
FilterChain chain = (req, resp) -> response.addHeader("X-Chain", "called");
|
||||
|
||||
SysTenantEntity tenant = new SysTenantEntity();
|
||||
tenant.setTenantId("tenant-1");
|
||||
tenant.setTenantTime(new Date(System.currentTimeMillis() + 3600_000));
|
||||
when(sysTenantService.selectByTenantKey("tenantkey-1")).thenReturn(tenant);
|
||||
when(sysTenantService.selectSysTenantByTenantId("tenant-1")).thenReturn(tenant);
|
||||
|
||||
filter.doFilter(request, response, chain);
|
||||
|
||||
assertThat(response.getHeader("X-Chain")).isEqualTo("called");
|
||||
verify(resolver, never()).resolveException(any(), any(), any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void getTenantIdUsesClerkTokenWhenProvided__covers_GW_005() {
|
||||
when(tokenService.getWxUserIdByToken(any())).thenReturn("user-1");
|
||||
when(redisCache.getCacheObject("TENANT_INFO:user-1")).thenReturn("tenant-from-redis");
|
||||
|
||||
PlayClerkUserInfoEntity clerk = new PlayClerkUserInfoEntity();
|
||||
clerk.setTenantId("tenant-clerk");
|
||||
when(clerkUserInfoService.selectById("user-1")).thenReturn(clerk);
|
||||
|
||||
String tenantId = filter.getTenantId("Bearer clerk", "Bearer custom", "tenantkey-1");
|
||||
assertThat(tenantId).isEqualTo("tenant-clerk");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getTenantIdUsesCustomTokenWhenNoClerkToken__covers_GW_006() {
|
||||
when(tokenService.getWxUserIdByToken(any())).thenReturn("user-2");
|
||||
when(redisCache.getCacheObject("TENANT_INFO:user-2")).thenReturn("tenant-from-redis");
|
||||
|
||||
PlayCustomUserInfoEntity custom = new PlayCustomUserInfoEntity();
|
||||
custom.setTenantId("tenant-custom");
|
||||
when(customUserInfoService.selectById("user-2")).thenReturn(custom);
|
||||
|
||||
String tenantId = filter.getTenantId(null, "Bearer custom", "tenantkey-1");
|
||||
assertThat(tenantId).isEqualTo("tenant-custom");
|
||||
}
|
||||
}
|
||||
@@ -32,7 +32,9 @@ 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.MockMvc;
|
||||
import org.springframework.test.web.servlet.MvcResult;
|
||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
||||
|
||||
class WxPkApiTest extends AbstractApiTest {
|
||||
|
||||
@@ -91,6 +93,12 @@ class WxPkApiTest extends AbstractApiTest {
|
||||
@Resource
|
||||
private IPlayOrderInfoService orderInfoService;
|
||||
|
||||
@Resource
|
||||
private com.starry.admin.modules.weichat.controller.WxPkController wxPkController;
|
||||
|
||||
@Resource
|
||||
private GlobalExceptionHandler globalExceptionHandler;
|
||||
|
||||
@BeforeEach
|
||||
void clearRedis() {
|
||||
if (stringRedisTemplate.getConnectionFactory() == null) {
|
||||
@@ -100,7 +108,7 @@ class WxPkApiTest extends AbstractApiTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void clerkLiveShouldRejectWhenClerkIdMissing() throws Exception {
|
||||
void clerkLiveShouldRejectWhenClerkIdMissing__covers_PK_001() throws Exception {
|
||||
mockMvc.perform(get("/wx/pk/clerk/live")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
@@ -110,7 +118,7 @@ class WxPkApiTest extends AbstractApiTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void clerkLiveShouldReturnInactiveWhenNoActivePk() throws Exception {
|
||||
void clerkLiveShouldReturnInactiveWhenNoActivePk__covers_PK_002() throws Exception {
|
||||
String clerkId = newClerkId();
|
||||
mockMvc.perform(get("/wx/pk/clerk/live")
|
||||
.param(PARAM_CLERK_ID, clerkId)
|
||||
@@ -151,7 +159,7 @@ class WxPkApiTest extends AbstractApiTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void clerkLiveShouldReturnInactiveWhenToBeStarted() throws Exception {
|
||||
void clerkLiveShouldReturnInactiveWhenToBeStarted__covers_PK_003() throws Exception {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
String clerkId = newClerkId();
|
||||
PlayClerkPkEntity pk = buildPk(newPkId(), clerkId, newClerkId(),
|
||||
@@ -170,7 +178,20 @@ class WxPkApiTest extends AbstractApiTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void upcomingShouldReturnInactiveWhenRedisEmpty() throws Exception {
|
||||
void upcomingShouldRejectWhenTenantMissing__covers_PK_004() throws Exception {
|
||||
com.starry.common.context.CustomSecurityContextHolder.remove();
|
||||
MockMvc standalone = MockMvcBuilders.standaloneSetup(wxPkController)
|
||||
.setControllerAdvice(globalExceptionHandler)
|
||||
.build();
|
||||
|
||||
standalone.perform(get("/wx/pk/upcoming"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.FAILED.getCode()))
|
||||
.andExpect(jsonPath("$.message").value("租户信息缺失"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void upcomingShouldReturnInactiveWhenRedisEmpty__covers_PK_005() throws Exception {
|
||||
mockMvc.perform(get("/wx/pk/upcoming")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
@@ -179,7 +200,7 @@ class WxPkApiTest extends AbstractApiTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void upcomingShouldReturnUpcomingWhenRedisHasPk() throws Exception {
|
||||
void upcomingShouldReturnUpcomingWhenRedisHasPk__covers_PK_005() throws Exception {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
String pkId = newPkId();
|
||||
PlayClerkPkEntity pk = buildPk(pkId, newClerkId(), newClerkId(),
|
||||
@@ -358,7 +379,7 @@ class WxPkApiTest extends AbstractApiTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void clerkScheduleShouldRejectWhenClerkIdMissing() throws Exception {
|
||||
void clerkScheduleShouldRejectWhenClerkIdMissing__covers_PK_006() throws Exception {
|
||||
mockMvc.perform(get("/wx/pk/clerk/schedule")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
@@ -409,7 +430,7 @@ class WxPkApiTest extends AbstractApiTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void clerkScheduleShouldApplyLimitDefaultWhenMissing() throws Exception {
|
||||
void clerkScheduleShouldApplyLimitDefaultWhenMissing__covers_PK_006() throws Exception {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
String clerkId = newClerkId();
|
||||
SecurityUtils.setTenantId(DEFAULT_TENANT);
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package com.starry.admin.modules.weichat.service;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatCode;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
@@ -8,17 +10,26 @@ import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import com.starry.admin.common.exception.CustomException;
|
||||
import com.starry.admin.common.exception.ServiceException;
|
||||
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;
|
||||
import com.starry.admin.modules.clerk.module.enums.ListingStatus;
|
||||
import com.starry.admin.modules.clerk.module.enums.OnboardingStatus;
|
||||
import com.starry.admin.modules.custom.module.entity.PlayCustomUserInfoEntity;
|
||||
import com.starry.admin.modules.order.module.constant.OrderConstant;
|
||||
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
|
||||
import com.starry.admin.modules.system.module.entity.SysTenantEntity;
|
||||
import com.starry.admin.modules.system.service.impl.SysTenantServiceImpl;
|
||||
import com.starry.admin.utils.SecurityUtils;
|
||||
import com.starry.common.context.CustomSecurityContextHolder;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import me.chanjar.weixin.common.error.WxErrorException;
|
||||
import me.chanjar.weixin.mp.api.WxMpService;
|
||||
import me.chanjar.weixin.mp.api.WxMpTemplateMsgService;
|
||||
import me.chanjar.weixin.mp.api.WxMpUserService;
|
||||
import me.chanjar.weixin.mp.bean.result.WxMpUser;
|
||||
import me.chanjar.weixin.mp.bean.template.WxMpTemplateData;
|
||||
import me.chanjar.weixin.mp.bean.template.WxMpTemplateMessage;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -26,6 +37,7 @@ import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
|
||||
@@ -69,7 +81,35 @@ class WxCustomMpServiceTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void sendCreateOrderMessageUsesOrderLabelResolver() throws WxErrorException {
|
||||
void proxyWxMpServiceRejectsMissingTenantId__covers_MP_001() {
|
||||
try {
|
||||
SecurityUtils.setTenantId("");
|
||||
assertThatThrownBy(wxCustomMpService::proxyWxMpService)
|
||||
.isInstanceOf(CustomException.class)
|
||||
.hasMessage("系统错误,租户ID不能为空");
|
||||
} finally {
|
||||
CustomSecurityContextHolder.remove();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void getWxPayRejectsWhenTenantMissingMchId__covers_MP_002() {
|
||||
try {
|
||||
SecurityUtils.setTenantId(TENANT_ID);
|
||||
SysTenantEntity tenant = buildTenant();
|
||||
tenant.setMchId("");
|
||||
when(tenantService.selectSysTenantByTenantId(TENANT_ID)).thenReturn(tenant);
|
||||
|
||||
assertThatThrownBy(wxCustomMpService::getWxPay)
|
||||
.isInstanceOf(CustomException.class)
|
||||
.hasMessage("商户号不能为空,请联系平台方进行配置");
|
||||
} finally {
|
||||
CustomSecurityContextHolder.remove();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void sendCreateOrderMessageUsesOrderLabelResolver__covers_MP_003() throws WxErrorException {
|
||||
SysTenantEntity tenant = buildTenant();
|
||||
|
||||
when(tenantService.selectSysTenantByTenantId(TENANT_ID)).thenReturn(tenant);
|
||||
@@ -114,7 +154,7 @@ class WxCustomMpServiceTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void sendCreateOrderMessageBatchFiltersInactiveClerksAndForwardsLabel() throws WxErrorException {
|
||||
void sendCreateOrderMessageBatchFiltersInactiveClerksAndForwardsLabel__covers_MP_004() throws WxErrorException {
|
||||
SysTenantEntity tenant = buildTenant();
|
||||
|
||||
when(tenantService.selectSysTenantByTenantId(TENANT_ID)).thenReturn(tenant);
|
||||
@@ -170,4 +210,103 @@ class WxCustomMpServiceTest {
|
||||
.extracting(WxMpTemplateData::getValue)
|
||||
.isEqualTo("指定单");
|
||||
}
|
||||
|
||||
@Test
|
||||
void sendBalanceMessageBuildsRechargeTemplateData__covers_MP_005() throws WxErrorException {
|
||||
try {
|
||||
SecurityUtils.setTenantId(TENANT_ID);
|
||||
|
||||
SysTenantEntity tenant = buildTenant();
|
||||
tenant.setChongzhichenggongTemplateId("template-recharge-success");
|
||||
tenant.setTenantName("租户名称");
|
||||
when(tenantService.selectSysTenantByTenantId(TENANT_ID)).thenReturn(tenant);
|
||||
|
||||
when(wxMpService.switchoverTo(tenant.getAppId())).thenReturn(wxMpService);
|
||||
when(wxMpService.getTemplateMsgService()).thenReturn(templateMsgService);
|
||||
|
||||
PlayCustomUserInfoEntity customUser = new PlayCustomUserInfoEntity();
|
||||
customUser.setId("customer-1");
|
||||
customUser.setOpenid("openid-customer");
|
||||
when(customUserInfoService.selectById(customUser.getId())).thenReturn(customUser);
|
||||
|
||||
PlayOrderInfoEntity order = new PlayOrderInfoEntity();
|
||||
order.setTenantId(TENANT_ID);
|
||||
order.setPurchaserBy(customUser.getId());
|
||||
order.setOrderMoney(new BigDecimal("10.00"));
|
||||
|
||||
wxCustomMpService.sendBalanceMessage(order);
|
||||
|
||||
ArgumentCaptor<WxMpTemplateMessage> captor = ArgumentCaptor.forClass(WxMpTemplateMessage.class);
|
||||
verify(templateMsgService).sendTemplateMsg(captor.capture());
|
||||
|
||||
WxMpTemplateMessage message = captor.getValue();
|
||||
assertThat(message.getTemplateId()).isEqualTo("template-recharge-success");
|
||||
assertThat(message.getToUser()).isEqualTo("openid-customer");
|
||||
assertThat(message.getUrl()).isEqualTo("https://tenant-key.julyharbor.com/user/");
|
||||
|
||||
assertThat(message.getData())
|
||||
.filteredOn(item -> "amount2".equals(item.getName()))
|
||||
.singleElement()
|
||||
.extracting(WxMpTemplateData::getValue)
|
||||
.isEqualTo("10.00");
|
||||
assertThat(message.getData())
|
||||
.filteredOn(item -> "amount17".equals(item.getName()))
|
||||
.singleElement()
|
||||
.extracting(WxMpTemplateData::getValue)
|
||||
.isEqualTo("0");
|
||||
assertThat(message.getData())
|
||||
.filteredOn(item -> "thing10".equals(item.getName()))
|
||||
.singleElement()
|
||||
.extracting(WxMpTemplateData::getValue)
|
||||
.isEqualTo("租户名称");
|
||||
} finally {
|
||||
CustomSecurityContextHolder.remove();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void sendOrderFinishMessageOnlyTriggersForPlaceType12__covers_MP_006() throws WxErrorException {
|
||||
PlayOrderInfoEntity order = new PlayOrderInfoEntity();
|
||||
order.setPlaceType("3");
|
||||
|
||||
wxCustomMpService.sendOrderFinishMessage(order);
|
||||
|
||||
verify(templateMsgService, Mockito.never()).sendTemplateMsg(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void asyncWrappersSwallowExceptionsFromUnderlyingSend__covers_MP_007() {
|
||||
doAnswer(invocation -> {
|
||||
Runnable runnable = invocation.getArgument(0);
|
||||
runnable.run();
|
||||
return null;
|
||||
}).when(executor).execute(any(Runnable.class));
|
||||
|
||||
WxCustomMpService spy = Mockito.spy(wxCustomMpService);
|
||||
Mockito.doThrow(new RuntimeException("boom")).when(spy).sendOrderFinishMessage(any());
|
||||
|
||||
PlayOrderInfoEntity order = new PlayOrderInfoEntity();
|
||||
order.setId("order-async-1");
|
||||
|
||||
assertThatCode(() -> spy.sendOrderFinishMessageAsync(order)).doesNotThrowAnyException();
|
||||
verify(executor).execute(any(Runnable.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void checkSubscribeThrowsWhenUserNotSubscribed__covers_MP_008() throws WxErrorException {
|
||||
SysTenantEntity tenant = buildTenant();
|
||||
when(tenantService.selectSysTenantByTenantId(TENANT_ID)).thenReturn(tenant);
|
||||
when(wxMpService.switchoverTo(tenant.getAppId())).thenReturn(wxMpService);
|
||||
|
||||
WxMpUserService userService = Mockito.mock(WxMpUserService.class);
|
||||
when(wxMpService.getUserService()).thenReturn(userService);
|
||||
|
||||
WxMpUser user = new WxMpUser();
|
||||
user.setSubscribe(false);
|
||||
when(userService.userInfo("openid-1")).thenReturn(user);
|
||||
|
||||
assertThatThrownBy(() -> wxCustomMpService.checkSubscribeThrowsExp("openid-1", TENANT_ID))
|
||||
.isInstanceOf(ServiceException.class)
|
||||
.hasMessage("请先关注公众号然后再来使用系统~");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,213 @@
|
||||
package com.starry.admin.modules.weichat.service;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import cn.hutool.http.HttpRequest;
|
||||
import com.starry.admin.common.exception.ServiceException;
|
||||
import com.starry.admin.common.oss.service.IOssFileService;
|
||||
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;
|
||||
import com.starry.admin.modules.clerk.module.enums.ClerkRoleStatus;
|
||||
import com.starry.admin.modules.clerk.module.enums.ListingStatus;
|
||||
import com.starry.admin.modules.clerk.module.enums.OnboardingStatus;
|
||||
import com.starry.admin.modules.clerk.service.IPlayClerkLevelInfoService;
|
||||
import com.starry.admin.modules.clerk.service.IPlayClerkUserInfoService;
|
||||
import com.starry.admin.modules.custom.module.entity.PlayCustomUserInfoEntity;
|
||||
import com.starry.admin.modules.custom.service.IPlayCustomLevelInfoService;
|
||||
import com.starry.admin.modules.custom.service.IPlayCustomUserInfoService;
|
||||
import com.starry.admin.utils.SecurityUtils;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import me.chanjar.weixin.common.bean.WxOAuth2UserInfo;
|
||||
import me.chanjar.weixin.common.bean.oauth2.WxOAuth2AccessToken;
|
||||
import me.chanjar.weixin.mp.api.WxMpService;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockedStatic;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class WxOauthServiceTest {
|
||||
|
||||
@Mock
|
||||
private WxCustomMpService wxCustomMpService;
|
||||
|
||||
@Mock
|
||||
private IPlayCustomUserInfoService customUserInfoService;
|
||||
|
||||
@Mock
|
||||
private IPlayClerkUserInfoService clerkUserInfoService;
|
||||
|
||||
@Mock
|
||||
private IPlayClerkLevelInfoService playClerkLevelInfoService;
|
||||
|
||||
@Mock
|
||||
private IPlayCustomLevelInfoService playCustomLevelInfoService;
|
||||
|
||||
@Mock
|
||||
private IOssFileService ossFileService;
|
||||
|
||||
@InjectMocks
|
||||
private WxOauthService wxOauthService;
|
||||
|
||||
@Test
|
||||
void getWxOAuth2AccessTokenRejectsBlankCode__covers_OAUTH_009() {
|
||||
try {
|
||||
wxOauthService.getWxOAuth2AccessToken(" ");
|
||||
} catch (ServiceException ex) {
|
||||
assertThat(ex.getMessage()).isEqualTo("不能为空");
|
||||
return;
|
||||
}
|
||||
throw new AssertionError("Expected ServiceException");
|
||||
}
|
||||
|
||||
@Test
|
||||
void customUserLoginCreatesNewUserAndUpdatesLastLogin__covers_OAUTH_009() throws Exception {
|
||||
WxMpService wxMpService = mock(WxMpService.class, Mockito.RETURNS_DEEP_STUBS);
|
||||
when(wxCustomMpService.proxyWxMpService()).thenReturn(wxMpService);
|
||||
|
||||
WxOAuth2AccessToken token = new WxOAuth2AccessToken();
|
||||
token.setOpenId("openid-1");
|
||||
when(wxMpService.getOAuth2Service().getAccessToken("code-1")).thenReturn(token);
|
||||
|
||||
WxOAuth2UserInfo userInfo = new WxOAuth2UserInfo();
|
||||
userInfo.setOpenid("openid-1");
|
||||
userInfo.setNickname("Nick");
|
||||
userInfo.setHeadImgUrl("https://example.com/avatar.png");
|
||||
when(wxMpService.getOAuth2Service().getUserInfo(eq(token), eq(null))).thenReturn(userInfo);
|
||||
|
||||
when(customUserInfoService.selectByOpenid("openid-1")).thenReturn(null);
|
||||
com.starry.admin.modules.custom.module.entity.PlayCustomLevelInfoEntity defaultLevel =
|
||||
new com.starry.admin.modules.custom.module.entity.PlayCustomLevelInfoEntity();
|
||||
defaultLevel.setId("lvl-1");
|
||||
when(playCustomLevelInfoService.getDefaultLevel()).thenReturn(defaultLevel);
|
||||
|
||||
String userId = wxOauthService.customUserLogin("code-1");
|
||||
assertThat(userId).isNotBlank();
|
||||
verify(customUserInfoService).saveOrUpdate(any(PlayCustomUserInfoEntity.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void clerkUserLoginCreatesNewClerkWithAvatarAndDefaultStates__covers_OAUTH_010() throws Exception {
|
||||
SecurityUtils.setTenantId("tenant-1");
|
||||
|
||||
WxMpService wxMpService = mock(WxMpService.class, Mockito.RETURNS_DEEP_STUBS);
|
||||
when(wxCustomMpService.proxyWxMpService()).thenReturn(wxMpService);
|
||||
|
||||
WxOAuth2AccessToken token = new WxOAuth2AccessToken();
|
||||
token.setOpenId("openid-clerk-1");
|
||||
when(wxMpService.getOAuth2Service().getAccessToken("code-2")).thenReturn(token);
|
||||
|
||||
WxOAuth2UserInfo userInfo = new WxOAuth2UserInfo();
|
||||
userInfo.setOpenid("openid-clerk-1");
|
||||
userInfo.setNickname("Lily");
|
||||
userInfo.setHeadImgUrl("https://example.com/avatar.png");
|
||||
when(wxMpService.getOAuth2Service().getUserInfo(eq(token), eq(null))).thenReturn(userInfo);
|
||||
|
||||
when(clerkUserInfoService.selectByOpenid("openid-clerk-1")).thenReturn(null);
|
||||
com.starry.admin.modules.clerk.module.entity.PlayClerkLevelInfoEntity defaultClerkLevel =
|
||||
new com.starry.admin.modules.clerk.module.entity.PlayClerkLevelInfoEntity();
|
||||
defaultClerkLevel.setId("lvl-clerk");
|
||||
when(playClerkLevelInfoService.getDefaultLevel()).thenReturn(defaultClerkLevel);
|
||||
when(ossFileService.upload(any(), eq("tenant-1"), eq("image"))).thenReturn("https://oss.example/avatar.jpg");
|
||||
|
||||
HttpRequest httpRequest = mock(HttpRequest.class, Mockito.RETURNS_DEEP_STUBS);
|
||||
try (MockedStatic<HttpRequest> mocked = Mockito.mockStatic(HttpRequest.class)) {
|
||||
mocked.when(() -> HttpRequest.get("https://example.com/avatar.png")).thenReturn(httpRequest);
|
||||
when(httpRequest.execute().bodyStream()).thenReturn(new ByteArrayInputStream("img".getBytes()));
|
||||
|
||||
String clerkId = wxOauthService.clerkUserLogin("code-2");
|
||||
assertThat(clerkId).isNotBlank();
|
||||
|
||||
verify(clerkUserInfoService).create(Mockito.argThat(entity -> {
|
||||
if (!"openid-clerk-1".equals(entity.getOpenid())) {
|
||||
return false;
|
||||
}
|
||||
if (!"https://oss.example/avatar.jpg".equals(entity.getAvatar())) {
|
||||
return false;
|
||||
}
|
||||
return "lvl-clerk".equals(entity.getLevelId())
|
||||
&& ClerkRoleStatus.NON_CLERK.getCode().equals(entity.getClerkState())
|
||||
&& OnboardingStatus.ACTIVE.getCode().equals(entity.getOnboardingState())
|
||||
&& ListingStatus.LISTED.getCode().equals(entity.getListingState());
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void clerkUserLoginRestoresDeletedClerk__covers_OAUTH_010() throws Exception {
|
||||
WxMpService wxMpService = mock(WxMpService.class, Mockito.RETURNS_DEEP_STUBS);
|
||||
when(wxCustomMpService.proxyWxMpService()).thenReturn(wxMpService);
|
||||
|
||||
WxOAuth2AccessToken token = new WxOAuth2AccessToken();
|
||||
token.setOpenId("openid-restore");
|
||||
when(wxMpService.getOAuth2Service().getAccessToken("code-restore")).thenReturn(token);
|
||||
|
||||
WxOAuth2UserInfo userInfo = new WxOAuth2UserInfo();
|
||||
userInfo.setOpenid("openid-restore");
|
||||
userInfo.setNickname("Restored");
|
||||
userInfo.setHeadImgUrl("https://example.com/avatar.png");
|
||||
when(wxMpService.getOAuth2Service().getUserInfo(eq(token), eq(null))).thenReturn(userInfo);
|
||||
|
||||
PlayClerkUserInfoEntity existing = new PlayClerkUserInfoEntity();
|
||||
existing.setId("clerk-restore");
|
||||
existing.setOpenid("openid-restore");
|
||||
existing.setDeleted(true);
|
||||
existing.setToken("old");
|
||||
existing.setOnlineState("1");
|
||||
existing.setOnboardingState("");
|
||||
existing.setListingState("");
|
||||
existing.setClerkState("");
|
||||
|
||||
when(clerkUserInfoService.selectByOpenid("openid-restore")).thenReturn(existing);
|
||||
|
||||
String clerkId = wxOauthService.clerkUserLogin("code-restore");
|
||||
assertThat(clerkId).isEqualTo("clerk-restore");
|
||||
assertThat(existing.getDeleted()).isFalse();
|
||||
assertThat(existing.getToken()).isEqualTo("empty");
|
||||
assertThat(existing.getOnlineState()).isEqualTo("0");
|
||||
assertThat(existing.getOnboardingState()).isEqualTo(OnboardingStatus.ACTIVE.getCode());
|
||||
assertThat(existing.getListingState()).isEqualTo(ListingStatus.LISTED.getCode());
|
||||
assertThat(existing.getClerkState()).isEqualTo(ClerkRoleStatus.NON_CLERK.getCode());
|
||||
|
||||
verify(clerkUserInfoService).update(eq(null), any());
|
||||
verify(clerkUserInfoService).ensureClerkSessionIsValid(existing);
|
||||
}
|
||||
|
||||
@Test
|
||||
void clerkUserLoginDoesNotDownloadAvatarWhenAlreadyPresent__covers_OAUTH_010() throws Exception {
|
||||
WxMpService wxMpService = mock(WxMpService.class, Mockito.RETURNS_DEEP_STUBS);
|
||||
when(wxCustomMpService.proxyWxMpService()).thenReturn(wxMpService);
|
||||
|
||||
WxOAuth2AccessToken token = new WxOAuth2AccessToken();
|
||||
token.setOpenId("openid-existing");
|
||||
when(wxMpService.getOAuth2Service().getAccessToken("code-existing")).thenReturn(token);
|
||||
|
||||
WxOAuth2UserInfo userInfo = new WxOAuth2UserInfo();
|
||||
userInfo.setOpenid("openid-existing");
|
||||
userInfo.setNickname("Exists");
|
||||
userInfo.setHeadImgUrl("https://example.com/avatar.png");
|
||||
when(wxMpService.getOAuth2Service().getUserInfo(eq(token), eq(null))).thenReturn(userInfo);
|
||||
|
||||
PlayClerkUserInfoEntity existing = new PlayClerkUserInfoEntity();
|
||||
existing.setId("clerk-existing");
|
||||
existing.setOpenid("openid-existing");
|
||||
existing.setDeleted(false);
|
||||
existing.setAvatar("https://oss.example/already.jpg");
|
||||
|
||||
when(clerkUserInfoService.selectByOpenid("openid-existing")).thenReturn(existing);
|
||||
|
||||
String clerkId = wxOauthService.clerkUserLogin("code-existing");
|
||||
assertThat(clerkId).isEqualTo("clerk-existing");
|
||||
|
||||
verify(ossFileService, never()).upload(any(), anyString(), anyString());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.starry.admin.modules.weichat.service;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import io.jsonwebtoken.ExpiredJwtException;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
class WxTokenServiceTest {
|
||||
|
||||
@Test
|
||||
void tokenRoundTripSupportsBearerPrefix__covers_TOK_001() {
|
||||
WxTokenService service = new WxTokenService();
|
||||
ReflectionTestUtils.setField(service, "secret", "apitest-secret");
|
||||
ReflectionTestUtils.setField(service, "expireTime", 60);
|
||||
|
||||
String token = service.createWxUserToken("user-1");
|
||||
assertThat(service.getWxUserIdByToken(token)).isEqualTo("user-1");
|
||||
assertThat(service.getWxUserIdByToken("Bearer " + token)).isEqualTo("user-1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void expiredTokenFailsToParse__covers_TOK_002() {
|
||||
WxTokenService service = new WxTokenService();
|
||||
ReflectionTestUtils.setField(service, "secret", "apitest-secret");
|
||||
ReflectionTestUtils.setField(service, "expireTime", -1);
|
||||
|
||||
String token = service.createWxUserToken("user-2");
|
||||
try {
|
||||
service.getWxUserIdByToken(token);
|
||||
} catch (ExpiredJwtException ex) {
|
||||
return;
|
||||
}
|
||||
throw new AssertionError("Expected ExpiredJwtException");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.starry.admin.modules.weichat.utils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
import com.starry.admin.common.exception.CustomException;
|
||||
import java.io.File;
|
||||
import java.nio.file.Files;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class WxFileUtilsTest {
|
||||
|
||||
@Test
|
||||
void audioConvert2Mp3RejectsEmptyFile__covers_COM_006() throws Exception {
|
||||
File source = Files.createTempFile("apitest-empty-audio-", ".amr").toFile();
|
||||
File target = Files.createTempFile("apitest-empty-audio-", ".mp3").toFile();
|
||||
try {
|
||||
assertThatThrownBy(() -> WxFileUtils.audioConvert2Mp3(source, target))
|
||||
.isInstanceOf(CustomException.class)
|
||||
.hasMessage("音频文件上传失败");
|
||||
} finally {
|
||||
Files.deleteIfExists(source.toPath());
|
||||
Files.deleteIfExists(target.toPath());
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user