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:
@@ -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));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user