test: add wechat integration test suite
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:
irving
2026-01-12 18:54:14 -05:00
parent 56239450d4
commit 985b35cd90
36 changed files with 5293 additions and 48 deletions

View File

@@ -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));
}
}