✨ 新增小程序登录编排MpLoginSupport与登录失败语义测试

This commit is contained in:
2026-10-23 15:58:42 +08:00
parent 1fb7a777c8
commit a8e059e92e
3 changed files with 595 additions and 0 deletions
@@ -0,0 +1,246 @@
package org.springblade.auth.service;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.springblade.core.oauth2.exception.UserInvalidException;
import org.springblade.core.oauth2.service.OAuth2User;
import org.springblade.core.tool.api.R;
import org.springblade.core.tool.support.Kv;
import org.springblade.system.feign.IMpUserClient;
import org.springblade.system.pojo.dto.MpUserEnsureDTO;
import org.springblade.system.pojo.entity.MpUser;
import org.springblade.system.pojo.entity.MpUserInfo;
import org.springblade.transport.feign.IDriverClient;
import org.springblade.transport.pojo.dto.DriverEnsureProfileDTO;
import org.springblade.transport.pojo.vo.DriverEnsureProfileVO;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
* 登录编排测试。
* <p>
* 守住票 02 / 03 / 05 的三条口径:建号失败才阻断登录;建档失败放行;
* openid 换号撞键时落到手机号账号且不迁移 openid。
*/
class MpLoginSupportTest {
private static final String TENANT_ID = "000000";
private static final String PHONE = "13800000001";
private static final String NEW_PHONE = "13800000002";
private static final String OPENID = "wx-openid-1";
private IMpUserClient mpUserClient;
private IDriverClient driverClient;
private MpLoginSupport mpLoginSupport;
@BeforeEach
void setUp() {
mpUserClient = mock(IMpUserClient.class);
driverClient = mock(IDriverClient.class);
mpLoginSupport = new MpLoginSupport(mpUserClient, driverClient);
}
@Test
@DisplayName("新手机号微信登录:建号 + 建档,登录成功且无提示")
void newPhoneCreatesAccountAndProfile() {
MpUser created = mpUser(11L, PHONE, OPENID, "driver");
when(mpUserClient.userByOpenid(TENANT_ID, OPENID)).thenReturn(R.data(null));
when(mpUserClient.ensureUser(any(MpUserEnsureDTO.class))).thenReturn(R.data(created));
when(mpUserClient.userInfoById(11L)).thenReturn(R.data(userInfo(created, "driver")));
when(driverClient.ensureProfile(any(DriverEnsureProfileDTO.class))).thenReturn(R.data(profileResult()));
OAuth2User user = mpLoginSupport.ensureLoginAccount(TENANT_ID, PHONE, OPENID);
assertNotNull(user);
assertEquals("11", user.getUserId());
assertNull(user.getDetail().get(MpLoginSupport.DETAIL_LOGIN_NOTICE));
ArgumentCaptor<MpUserEnsureDTO> ensureCaptor = ArgumentCaptor.forClass(MpUserEnsureDTO.class);
verify(mpUserClient).ensureUser(ensureCaptor.capture());
assertEquals(TENANT_ID, ensureCaptor.getValue().getTenantId());
assertEquals(PHONE, ensureCaptor.getValue().getPhone());
assertEquals(OPENID, ensureCaptor.getValue().getOpenid());
ArgumentCaptor<DriverEnsureProfileDTO> profileCaptor = ArgumentCaptor.forClass(DriverEnsureProfileDTO.class);
verify(driverClient).ensureProfile(profileCaptor.capture());
assertEquals(TENANT_ID, profileCaptor.getValue().getTenantId());
assertEquals(11L, profileCaptor.getValue().getMpUserId());
assertEquals(PHONE, profileCaptor.getValue().getPhone());
}
@Test
@DisplayName("建档抛异常:登录仍然成功")
void profileFailureDoesNotBlockLogin() {
MpUser created = mpUser(12L, PHONE, OPENID, "driver");
when(mpUserClient.userByOpenid(TENANT_ID, OPENID)).thenReturn(R.data(null));
when(mpUserClient.ensureUser(any(MpUserEnsureDTO.class))).thenReturn(R.data(created));
when(mpUserClient.userInfoById(12L)).thenReturn(R.data(userInfo(created, "driver")));
when(driverClient.ensureProfile(any(DriverEnsureProfileDTO.class)))
.thenThrow(new RuntimeException("transport 不可用"));
OAuth2User user = mpLoginSupport.ensureLoginAccount(TENANT_ID, PHONE, OPENID);
assertNotNull(user);
assertEquals("12", user.getUserId());
verify(driverClient).ensureProfile(any(DriverEnsureProfileDTO.class));
}
@Test
@DisplayName("openid 绑定失败:登录仍然成功,不抛异常")
void bindOpenidFailureDoesNotBlockLogin() {
MpUser existing = mpUser(13L, PHONE, null, "driver");
when(mpUserClient.userByOpenid(TENANT_ID, OPENID)).thenReturn(R.data(null));
when(mpUserClient.ensureUser(any(MpUserEnsureDTO.class))).thenReturn(R.data(existing));
when(mpUserClient.userInfoById(13L)).thenReturn(R.data(userInfo(existing, "driver")));
when(driverClient.ensureProfile(any(DriverEnsureProfileDTO.class))).thenReturn(R.data(profileResult()));
when(mpUserClient.bindOpenId(TENANT_ID, 13L, OPENID)).thenReturn(R.fail("该微信已绑定其他小程序用户"));
OAuth2User user = mpLoginSupport.ensureLoginAccount(TENANT_ID, PHONE, OPENID);
assertNotNull(user);
assertEquals("13", user.getUserId());
verify(mpUserClient).bindOpenId(TENANT_ID, 13L, OPENID);
}
@Test
@DisplayName("打开号码来源:微信登录时给空 openid 账号补绑 openid")
void bindsOpenidForPhoneAccount() {
MpUser existing = mpUser(14L, PHONE, null, "driver");
when(mpUserClient.userByOpenid(TENANT_ID, OPENID)).thenReturn(R.data(null));
when(mpUserClient.ensureUser(any(MpUserEnsureDTO.class))).thenReturn(R.data(existing));
when(mpUserClient.userInfoById(14L)).thenReturn(R.data(userInfo(existing, "driver")));
when(driverClient.ensureProfile(any(DriverEnsureProfileDTO.class))).thenReturn(R.data(profileResult()));
when(mpUserClient.bindOpenId(TENANT_ID, 14L, OPENID)).thenReturn(R.data(Boolean.TRUE));
mpLoginSupport.ensureLoginAccount(TENANT_ID, PHONE, OPENID);
verify(mpUserClient).bindOpenId(TENANT_ID, 14L, OPENID);
}
@Test
@DisplayName("openid 命中旧账号但新手机号已被别的账号占用:落到手机号账号、不迁移 openid、有提示")
void phoneOccupiedKeepsPhoneAccount() {
MpUser openidAccount = mpUser(21L, "13900000000", OPENID, "driver");
MpUser phoneAccount = mpUser(22L, NEW_PHONE, null, "driver");
when(mpUserClient.userByOpenid(TENANT_ID, OPENID)).thenReturn(R.data(openidAccount));
when(mpUserClient.userByPhone(TENANT_ID, NEW_PHONE)).thenReturn(R.data(phoneAccount));
when(mpUserClient.userInfoById(22L)).thenReturn(R.data(userInfo(phoneAccount, "driver")));
when(driverClient.ensureProfile(any(DriverEnsureProfileDTO.class))).thenReturn(R.data(profileResult()));
OAuth2User user = mpLoginSupport.ensureLoginAccount(TENANT_ID, NEW_PHONE, OPENID);
assertNotNull(user);
assertEquals("22", user.getUserId(), "应落在手机号账号上");
verify(mpUserClient, never()).updateUser(any(MpUser.class));
verify(mpUserClient, never()).bindOpenId(anyString(), anyLong(), anyString());
assertNotNull(user.getDetail().get(MpLoginSupport.DETAIL_LOGIN_NOTICE), "应带上可读提示");
assertTrue(String.valueOf(user.getDetail().get(MpLoginSupport.DETAIL_LOGIN_NOTICE)).contains("该微信已绑定其他账号"));
}
@Test
@DisplayName("openid 命中且手机号不同但新号空闲:更新账号手机号")
void openidHitWithFreePhoneUpdatesPhone() {
MpUser openidAccount = mpUser(31L, "13900000000", OPENID, "driver");
when(mpUserClient.userByOpenid(TENANT_ID, OPENID)).thenReturn(R.data(openidAccount));
when(mpUserClient.userByPhone(TENANT_ID, NEW_PHONE)).thenReturn(R.data(null));
when(mpUserClient.updateUser(any(MpUser.class))).thenReturn(R.data(Boolean.TRUE));
when(mpUserClient.userInfoById(31L)).thenReturn(R.data(userInfo(openidAccount, "driver")));
when(driverClient.ensureProfile(any(DriverEnsureProfileDTO.class))).thenReturn(R.data(profileResult()));
OAuth2User user = mpLoginSupport.ensureLoginAccount(TENANT_ID, NEW_PHONE, OPENID);
assertEquals("31", user.getUserId());
ArgumentCaptor<MpUser> updateCaptor = ArgumentCaptor.forClass(MpUser.class);
verify(mpUserClient).updateUser(updateCaptor.capture());
assertEquals(31L, updateCaptor.getValue().getId());
assertEquals(NEW_PHONE, updateCaptor.getValue().getPhone());
verify(mpUserClient, never()).ensureUser(any(MpUserEnsureDTO.class));
}
@Test
@DisplayName("纯 admin 身份:不建司机档案")
void adminOnlyDoesNotCreateProfile() {
MpUser admin = mpUser(41L, PHONE, OPENID, "admin");
when(mpUserClient.userByOpenid(TENANT_ID, OPENID)).thenReturn(R.data(null));
when(mpUserClient.ensureUser(any(MpUserEnsureDTO.class))).thenReturn(R.data(admin));
when(mpUserClient.userInfoById(41L)).thenReturn(R.data(userInfo(admin, "admin")));
OAuth2User user = mpLoginSupport.ensureLoginAccount(TENANT_ID, PHONE, OPENID);
assertEquals("41", user.getUserId());
verify(driverClient, never()).ensureProfile(any(DriverEnsureProfileDTO.class));
}
@Test
@DisplayName("建号失败是唯一阻断登录的条件")
void accountCreationFailureBlocksLogin() {
when(mpUserClient.userByOpenid(TENANT_ID, OPENID)).thenReturn(R.data(null));
when(mpUserClient.ensureUser(any(MpUserEnsureDTO.class))).thenReturn(R.fail("创建小程序用户失败"));
UserInvalidException exception = assertThrows(UserInvalidException.class,
() -> mpLoginSupport.ensureLoginAccount(TENANT_ID, PHONE, OPENID));
assertEquals("创建小程序用户失败", exception.getMessage());
verify(driverClient, never()).ensureProfile(any(DriverEnsureProfileDTO.class));
}
@Test
@DisplayName("短信登录(openid 为空)不做 openid 相关调用")
void smsLoginSkipsOpenid() {
MpUser created = mpUser(51L, PHONE, null, "driver");
when(mpUserClient.ensureUser(any(MpUserEnsureDTO.class))).thenReturn(R.data(created));
when(mpUserClient.userInfoById(51L)).thenReturn(R.data(userInfo(created, "driver")));
when(driverClient.ensureProfile(any(DriverEnsureProfileDTO.class))).thenReturn(R.data(profileResult()));
OAuth2User user = mpLoginSupport.ensureLoginAccount(TENANT_ID, PHONE, null);
assertEquals("51", user.getUserId());
verify(mpUserClient, never()).userByOpenid(anyString(), anyString());
verify(mpUserClient, never()).bindOpenId(anyString(), anyLong(), anyString());
}
private MpUser mpUser(Long id, String phone, String openid, String identities) {
MpUser user = new MpUser();
user.setId(id);
user.setTenantId(TENANT_ID);
user.setPhone(phone);
user.setAccount(phone);
user.setOpenid(openid);
user.setIdentities(identities);
user.setStatus(1);
return user;
}
private MpUserInfo userInfo(MpUser user, String... identities) {
MpUserInfo userInfo = new MpUserInfo();
userInfo.setUser(user);
userInfo.setIdentities(List.of(identities));
userInfo.setDetail(Kv.create()
.set("type", "mp")
.set("identities", String.join(",", identities))
.set("openid", user.getOpenid() == null ? "" : user.getOpenid()));
return userInfo;
}
private DriverEnsureProfileVO profileResult() {
DriverEnsureProfileVO result = new DriverEnsureProfileVO();
result.setDriverId(9001L);
result.setCreated(true);
result.setBoundExisting(false);
return result;
}
}