✨ 小程序登录改为只按手机号识别账号
微信 openid 改为每次登录覆盖写留痕,不再参与账号查找,删除 openid 查账号、改手机号与手机号占用提示。
This commit is contained in:
@@ -19,24 +19,23 @@ 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.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* 登录编排测试。
|
||||
* <p>
|
||||
* 保住三条不变式:建号失败才阻断登录;建档失败放行;
|
||||
* openid 换号撞键时落到手机号账号且不迁移 openid。
|
||||
* 手机号是账号唯一识别凭据:登录只按手机号找账号,openid 只做「最近一次登录用的微信」留痕;
|
||||
* 登录路径不改手机号、不按 openid 找账号。只有建号失败才阻断登录。
|
||||
*/
|
||||
class MpLoginSupportTest {
|
||||
|
||||
@@ -44,6 +43,7 @@ class MpLoginSupportTest {
|
||||
private static final String PHONE = "13800000001";
|
||||
private static final String NEW_PHONE = "13800000002";
|
||||
private static final String OPENID = "wx-openid-1";
|
||||
private static final String OTHER_OPENID = "wx-openid-2";
|
||||
|
||||
private IMpUserClient mpUserClient;
|
||||
private IDriverClient driverClient;
|
||||
@@ -57,214 +57,234 @@ class MpLoginSupportTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("新手机号微信登录:建号 + 建档,登录成功且无提示")
|
||||
void newPhoneCreatesAccountAndProfile() {
|
||||
// feign 下发时 phone 是脱敏值(MpUser.phone 标了 @Sensitive),建档必须仍用登录入参的真实号
|
||||
MpUser created = mpUser(11L, "138****0001", 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")));
|
||||
@DisplayName("手机号命中账号:登进该账号,openid 覆盖为本次值,手机号与登录名不动")
|
||||
void phoneHitReusesAccountAndOverwritesOpenid() {
|
||||
// feign 下发时 phone 是脱敏值(MpUser.phone 标了 @Sensitive),登录不改它
|
||||
MpUser existing = mpUser(11L, "138****0001", OTHER_OPENID, "driver");
|
||||
existing.setAccount("wangwu");
|
||||
when(mpUserClient.userByPhone(TENANT_ID, PHONE)).thenReturn(R.data(existing));
|
||||
when(mpUserClient.userInfoById(11L)).thenReturn(R.data(userInfo(existing, "driver")));
|
||||
when(driverClient.ensureProfile(any(DriverEnsureProfileDTO.class))).thenReturn(R.data(profileResult()));
|
||||
when(mpUserClient.bindOpenId(TENANT_ID, 11L, OPENID)).thenReturn(R.data(Boolean.TRUE));
|
||||
|
||||
OAuth2User user = mpLoginSupport.ensureLoginAccount(TENANT_ID, PHONE, OPENID);
|
||||
|
||||
assertNotNull(user);
|
||||
assertEquals("11", user.getUserId());
|
||||
assertEquals("wangwu", user.getAccount());
|
||||
assertEquals("138****0001", user.getPhone());
|
||||
assertNull(user.getDetail().get(MpLoginSupport.DETAIL_LOGIN_NOTICE));
|
||||
verify(mpUserClient).bindOpenId(TENANT_ID, 11L, OPENID);
|
||||
// 不再按 openid 找账号,也不再改手机号
|
||||
verify(mpUserClient, never()).userByOpenid(anyString(), anyString());
|
||||
verify(mpUserClient, never()).updateUser(any(MpUser.class));
|
||||
verify(mpUserClient, never()).ensureUser(any(MpUserEnsureDTO.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("同一手机号两次登录、openid 不同:落在同一账号,第二次覆盖第一次")
|
||||
void repeatedLoginOverwritesOpenid() {
|
||||
MpUser existing = mpUser(12L, PHONE, OPENID, "driver");
|
||||
when(mpUserClient.userByPhone(TENANT_ID, PHONE)).thenReturn(R.data(existing));
|
||||
when(mpUserClient.userInfoById(12L)).thenReturn(R.data(userInfo(existing, "driver")));
|
||||
when(driverClient.ensureProfile(any(DriverEnsureProfileDTO.class))).thenReturn(R.data(profileResult()));
|
||||
when(mpUserClient.bindOpenId(TENANT_ID, 12L, OPENID)).thenReturn(R.data(Boolean.TRUE));
|
||||
when(mpUserClient.bindOpenId(TENANT_ID, 12L, OTHER_OPENID)).thenReturn(R.data(Boolean.TRUE));
|
||||
|
||||
OAuth2User first = mpLoginSupport.ensureLoginAccount(TENANT_ID, PHONE, OPENID);
|
||||
OAuth2User second = mpLoginSupport.ensureLoginAccount(TENANT_ID, PHONE, OTHER_OPENID);
|
||||
|
||||
assertEquals("12", first.getUserId());
|
||||
assertEquals("12", second.getUserId(), "同一手机号始终落在同一账号");
|
||||
verify(mpUserClient).bindOpenId(TENANT_ID, 12L, OPENID);
|
||||
verify(mpUserClient).bindOpenId(TENANT_ID, 12L, OTHER_OPENID);
|
||||
// 账号已存在,两次登录都不该重建
|
||||
verify(mpUserClient, never()).ensureUser(any(MpUserEnsureDTO.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("openid 曾属于别的账号:仍按手机号登进本账号,无换号提示、不改手机号")
|
||||
void openidHistoryOnOtherAccountDoesNotAffectLogin() {
|
||||
MpUser phoneAccount = mpUser(21L, PHONE, null, "driver");
|
||||
when(mpUserClient.userByPhone(TENANT_ID, PHONE)).thenReturn(R.data(phoneAccount));
|
||||
when(mpUserClient.userInfoById(21L)).thenReturn(R.data(userInfo(phoneAccount, "driver")));
|
||||
when(driverClient.ensureProfile(any(DriverEnsureProfileDTO.class))).thenReturn(R.data(profileResult()));
|
||||
when(mpUserClient.bindOpenId(TENANT_ID, 21L, OPENID)).thenReturn(R.data(Boolean.TRUE));
|
||||
|
||||
OAuth2User user = mpLoginSupport.ensureLoginAccount(TENANT_ID, PHONE, OPENID);
|
||||
|
||||
assertEquals("21", user.getUserId());
|
||||
assertNull(user.getDetail().get(MpLoginSupport.DETAIL_LOGIN_NOTICE), "换号撞键提示已不存在");
|
||||
verify(mpUserClient, never()).userByOpenid(anyString(), anyString());
|
||||
verify(mpUserClient).bindOpenId(TENANT_ID, 21L, OPENID);
|
||||
verify(mpUserClient, never()).updateUser(any(MpUser.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("新手机号微信登录:建号 + 建档 + openid 落库,登录成功且无提示")
|
||||
void newPhoneCreatesAccountAndProfile() {
|
||||
MpUser created = mpUser(31L, "138****0001", OPENID, "driver");
|
||||
when(mpUserClient.userByPhone(TENANT_ID, PHONE)).thenReturn(R.data(null));
|
||||
when(mpUserClient.ensureUser(any(MpUserEnsureDTO.class))).thenReturn(R.data(created));
|
||||
when(mpUserClient.userInfoById(31L)).thenReturn(R.data(userInfo(created, "driver")));
|
||||
when(driverClient.ensureProfile(any(DriverEnsureProfileDTO.class))).thenReturn(R.data(profileResult()));
|
||||
when(mpUserClient.bindOpenId(TENANT_ID, 31L, OPENID)).thenReturn(R.data(Boolean.TRUE));
|
||||
|
||||
OAuth2User user = mpLoginSupport.ensureLoginAccount(TENANT_ID, PHONE, OPENID);
|
||||
|
||||
assertNotNull(user);
|
||||
assertEquals("31", 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());
|
||||
// 建档必须用登录入参的真实手机号,不能用被脱敏的账号 phone
|
||||
ArgumentCaptor<DriverEnsureProfileDTO> profileCaptor = ArgumentCaptor.forClass(DriverEnsureProfileDTO.class);
|
||||
verify(driverClient).ensureProfile(profileCaptor.capture());
|
||||
assertEquals(TENANT_ID, profileCaptor.getValue().getTenantId());
|
||||
assertEquals(11L, profileCaptor.getValue().getMpUserId());
|
||||
assertEquals(31L, 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).bindOpenId(TENANT_ID, 31L, OPENID);
|
||||
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, "139****0000", OPENID, "driver");
|
||||
when(mpUserClient.userByOpenid(TENANT_ID, OPENID)).thenReturn(R.data(openidAccount));
|
||||
@DisplayName("同一微信换手机号登录:登进新号对应的账号,旧账号不被改动")
|
||||
void sameOpenidNewPhoneLandsOnNewPhoneAccount() {
|
||||
MpUser created = mpUser(41L, NEW_PHONE, OPENID, "driver");
|
||||
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(mpUserClient.ensureUser(any(MpUserEnsureDTO.class))).thenReturn(R.data(created));
|
||||
when(mpUserClient.userInfoById(41L)).thenReturn(R.data(userInfo(created, "driver")));
|
||||
when(driverClient.ensureProfile(any(DriverEnsureProfileDTO.class))).thenReturn(R.data(profileResult()));
|
||||
when(mpUserClient.bindOpenId(TENANT_ID, 41L, OPENID)).thenReturn(R.data(Boolean.TRUE));
|
||||
|
||||
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("openid 命中且该手机号就是本账号:不重复更新手机号")
|
||||
void openidHitSameAccountDoesNotUpdatePhone() {
|
||||
MpUser openidAccount = mpUser(32L, "138****0001", OPENID, "driver");
|
||||
when(mpUserClient.userByOpenid(TENANT_ID, OPENID)).thenReturn(R.data(openidAccount));
|
||||
when(mpUserClient.userByPhone(TENANT_ID, PHONE)).thenReturn(R.data(openidAccount));
|
||||
when(mpUserClient.userInfoById(32L)).thenReturn(R.data(userInfo(openidAccount, "driver")));
|
||||
when(driverClient.ensureProfile(any(DriverEnsureProfileDTO.class))).thenReturn(R.data(profileResult()));
|
||||
|
||||
OAuth2User user = mpLoginSupport.ensureLoginAccount(TENANT_ID, PHONE, OPENID);
|
||||
|
||||
assertEquals("32", user.getUserId());
|
||||
verify(mpUserClient, never()).updateUser(any(MpUser.class));
|
||||
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));
|
||||
ArgumentCaptor<MpUserEnsureDTO> ensureCaptor = ArgumentCaptor.forClass(MpUserEnsureDTO.class);
|
||||
verify(mpUserClient).ensureUser(ensureCaptor.capture());
|
||||
assertEquals(NEW_PHONE, ensureCaptor.getValue().getPhone());
|
||||
// 旧账号既不改手机号也不改 openid
|
||||
verify(mpUserClient, never()).userByOpenid(anyString(), anyString());
|
||||
verify(mpUserClient, never()).updateUser(any(MpUser.class));
|
||||
verify(mpUserClient).bindOpenId(TENANT_ID, 41L, OPENID);
|
||||
}
|
||||
|
||||
@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")));
|
||||
@DisplayName("短信登录:不读不写 openid,也不改手机号")
|
||||
void smsLoginDoesNotTouchOpenid() {
|
||||
MpUser existing = mpUser(51L, PHONE, OTHER_OPENID, "driver");
|
||||
when(mpUserClient.userByPhone(TENANT_ID, PHONE)).thenReturn(R.data(existing));
|
||||
when(mpUserClient.userInfoById(51L)).thenReturn(R.data(userInfo(existing, "driver")));
|
||||
when(driverClient.ensureProfile(any(DriverEnsureProfileDTO.class))).thenReturn(R.data(profileResult()));
|
||||
|
||||
OAuth2User user = mpLoginSupport.ensureLoginAccount(TENANT_ID, PHONE, null);
|
||||
|
||||
assertEquals("51", user.getUserId());
|
||||
assertNull(user.getDetail().get(MpLoginSupport.DETAIL_LOGIN_NOTICE));
|
||||
verify(mpUserClient, never()).userByOpenid(anyString(), anyString());
|
||||
verify(mpUserClient, never()).bindOpenId(anyString(), anyLong(), anyString());
|
||||
verify(mpUserClient, never()).updateUser(any(MpUser.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("短信登录:司机档案冲突仍下发 loginNotice")
|
||||
void smsLoginReportsProfileConflict() {
|
||||
MpUser existing = mpUser(52L, PHONE, null, "driver");
|
||||
when(mpUserClient.userByPhone(TENANT_ID, PHONE)).thenReturn(R.data(existing));
|
||||
when(mpUserClient.userInfoById(52L)).thenReturn(R.data(userInfo(existing, "driver")));
|
||||
DriverEnsureProfileVO conflict = new DriverEnsureProfileVO();
|
||||
conflict.setMessage("手机号已被其他司机档案占用");
|
||||
when(driverClient.ensureProfile(any(DriverEnsureProfileDTO.class))).thenReturn(R.data(conflict));
|
||||
|
||||
OAuth2User user = mpLoginSupport.ensureLoginAccount(TENANT_ID, PHONE, null);
|
||||
|
||||
assertEquals("手机号已被其他司机档案占用", user.getDetail().get(MpLoginSupport.DETAIL_LOGIN_NOTICE));
|
||||
verify(mpUserClient, never()).bindOpenId(anyString(), anyLong(), anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("远端查无账号返回空对象(id 为空):按未命中建号,并用真实主键补绑 openid")
|
||||
void emptyAccountFromRemoteIsTreatedAsMiss() {
|
||||
// 服务端 R.data(null) 落 HTTP 是 {},Feign 反序列化成 id 为空的 MpUser 而非 null
|
||||
MpUser created = mpUser(61L, PHONE, null, "driver");
|
||||
when(mpUserClient.userByOpenid(TENANT_ID, OPENID)).thenReturn(R.data(new MpUser()));
|
||||
when(mpUserClient.userByPhone(TENANT_ID, PHONE)).thenReturn(R.data(new MpUser()));
|
||||
when(mpUserClient.ensureUser(any(MpUserEnsureDTO.class))).thenReturn(R.data(created));
|
||||
when(mpUserClient.bindOpenId(TENANT_ID, 61L, OPENID)).thenReturn(R.data(Boolean.TRUE));
|
||||
when(mpUserClient.userInfoById(61L)).thenReturn(R.data(userInfo(created, "driver")));
|
||||
@DisplayName("写 openid 失败:登录仍然成功")
|
||||
void openidWriteFailureDoesNotBlockLogin() {
|
||||
MpUser existing = mpUser(53L, PHONE, null, "driver");
|
||||
when(mpUserClient.userByPhone(TENANT_ID, PHONE)).thenReturn(R.data(existing));
|
||||
when(mpUserClient.userInfoById(53L)).thenReturn(R.data(userInfo(existing, "driver")));
|
||||
when(driverClient.ensureProfile(any(DriverEnsureProfileDTO.class))).thenReturn(R.data(profileResult()));
|
||||
when(mpUserClient.bindOpenId(TENANT_ID, 53L, OPENID)).thenThrow(new RuntimeException("system 不可用"));
|
||||
|
||||
OAuth2User user = mpLoginSupport.ensureLoginAccount(TENANT_ID, PHONE, OPENID);
|
||||
|
||||
assertNotNull(user);
|
||||
assertEquals("61", user.getUserId());
|
||||
assertNull(user.getDetail().get(MpLoginSupport.DETAIL_LOGIN_NOTICE), "不该出现换号撞键提示");
|
||||
verify(mpUserClient).ensureUser(any(MpUserEnsureDTO.class));
|
||||
verify(mpUserClient, never()).updateUser(any(MpUser.class));
|
||||
verify(mpUserClient).bindOpenId(TENANT_ID, 61L, OPENID);
|
||||
assertEquals("53", user.getUserId());
|
||||
verify(mpUserClient).bindOpenId(TENANT_ID, 53L, OPENID);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("两个查询都返回空对象时:不落空账号、不发起带空主键的绑定")
|
||||
void emptyAccountDoesNotLeakNullIdentityIntoBind() {
|
||||
MpUser created = mpUser(62L, PHONE, OPENID, "driver");
|
||||
when(mpUserClient.userByOpenid(TENANT_ID, OPENID)).thenReturn(R.data(new MpUser()));
|
||||
when(mpUserClient.userByPhone(TENANT_ID, PHONE)).thenReturn(R.data(new MpUser()));
|
||||
when(mpUserClient.ensureUser(any(MpUserEnsureDTO.class))).thenReturn(R.data(created));
|
||||
when(mpUserClient.userInfoById(62L)).thenReturn(R.data(userInfo(created, "driver")));
|
||||
when(driverClient.ensureProfile(any(DriverEnsureProfileDTO.class))).thenReturn(R.data(profileResult()));
|
||||
@DisplayName("建档抛异常:登录仍然成功")
|
||||
void profileFailureDoesNotBlockLogin() {
|
||||
MpUser existing = mpUser(54L, PHONE, OPENID, "driver");
|
||||
when(mpUserClient.userByPhone(TENANT_ID, PHONE)).thenReturn(R.data(existing));
|
||||
when(mpUserClient.userInfoById(54L)).thenReturn(R.data(userInfo(existing, "driver")));
|
||||
when(driverClient.ensureProfile(any(DriverEnsureProfileDTO.class)))
|
||||
.thenThrow(new RuntimeException("transport 不可用"));
|
||||
when(mpUserClient.bindOpenId(TENANT_ID, 54L, OPENID)).thenReturn(R.data(Boolean.TRUE));
|
||||
|
||||
OAuth2User user = mpLoginSupport.ensureLoginAccount(TENANT_ID, PHONE, OPENID);
|
||||
|
||||
assertEquals("62", user.getUserId());
|
||||
assertNotNull(user);
|
||||
assertEquals("54", user.getUserId());
|
||||
verify(driverClient).ensureProfile(any(DriverEnsureProfileDTO.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("建号失败是唯一阻断登录的条件")
|
||||
void accountCreationFailureBlocksLogin() {
|
||||
when(mpUserClient.userByPhone(TENANT_ID, PHONE)).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(mpUserClient, never()).bindOpenId(anyString(), anyLong(), anyString());
|
||||
verify(driverClient, never()).ensureProfile(any(DriverEnsureProfileDTO.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("纯 admin 身份:不建司机档案")
|
||||
void adminOnlyDoesNotCreateProfile() {
|
||||
MpUser admin = mpUser(61L, PHONE, OPENID, "admin");
|
||||
when(mpUserClient.userByPhone(TENANT_ID, PHONE)).thenReturn(R.data(admin));
|
||||
when(mpUserClient.userInfoById(61L)).thenReturn(R.data(userInfo(admin, "admin")));
|
||||
when(mpUserClient.bindOpenId(TENANT_ID, 61L, OPENID)).thenReturn(R.data(Boolean.TRUE));
|
||||
|
||||
OAuth2User user = mpLoginSupport.ensureLoginAccount(TENANT_ID, PHONE, OPENID);
|
||||
|
||||
assertEquals("61", user.getUserId());
|
||||
verify(driverClient, never()).ensureProfile(any(DriverEnsureProfileDTO.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("远端查无账号返回空对象(id 为空):按未命中建号,用真实主键写 openid")
|
||||
void emptyAccountFromRemoteIsTreatedAsMiss() {
|
||||
// 服务端 R.data(null) 落 HTTP 是 {},Feign 反序列化成 id 为空的 MpUser 而非 null
|
||||
MpUser created = mpUser(71L, PHONE, OPENID, "driver");
|
||||
when(mpUserClient.userByPhone(TENANT_ID, PHONE)).thenReturn(R.data(new MpUser()));
|
||||
when(mpUserClient.ensureUser(any(MpUserEnsureDTO.class))).thenReturn(R.data(created));
|
||||
when(mpUserClient.userInfoById(71L)).thenReturn(R.data(userInfo(created, "driver")));
|
||||
when(driverClient.ensureProfile(any(DriverEnsureProfileDTO.class))).thenReturn(R.data(profileResult()));
|
||||
when(mpUserClient.bindOpenId(TENANT_ID, 71L, OPENID)).thenReturn(R.data(Boolean.TRUE));
|
||||
|
||||
OAuth2User user = mpLoginSupport.ensureLoginAccount(TENANT_ID, PHONE, OPENID);
|
||||
|
||||
assertNotNull(user);
|
||||
assertEquals("71", user.getUserId());
|
||||
assertNull(user.getDetail().get(MpLoginSupport.DETAIL_LOGIN_NOTICE));
|
||||
verify(mpUserClient).ensureUser(any(MpUserEnsureDTO.class));
|
||||
verify(mpUserClient, never()).updateUser(any(MpUser.class));
|
||||
verify(mpUserClient).bindOpenId(TENANT_ID, 71L, OPENID);
|
||||
}
|
||||
|
||||
private MpUser mpUser(Long id, String phone, String openid, String identities) {
|
||||
|
||||
Reference in New Issue
Block a user