✅ 补充司机档案自动建档与绑定回归测试
This commit is contained in:
+255
@@ -0,0 +1,255 @@
|
||||
package org.springblade.transport.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.MybatisConfiguration;
|
||||
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
|
||||
import org.apache.ibatis.builder.MapperBuilderAssistant;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.MockedStatic;
|
||||
import org.springblade.core.log.exception.ServiceException;
|
||||
import org.springblade.core.secure.utils.AuthUtil;
|
||||
import org.springblade.system.feign.IMpUserClient;
|
||||
import org.springblade.system.feign.ISysClient;
|
||||
import org.springblade.transport.mapper.DriverMapper;
|
||||
import org.springblade.transport.pojo.entity.Driver;
|
||||
import org.springblade.transport.pojo.vo.DriverEnsureProfileVO;
|
||||
import org.springframework.dao.DuplicateKeyException;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Collections;
|
||||
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.Mockito.mock;
|
||||
import static org.mockito.Mockito.mockStatic;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* 登录自动建档与绑定已有档案的测试。
|
||||
* <p>
|
||||
* 守住三条不变式:按 user_id 幂等重入不重复建档、同号冲突一律不绑定也不新建、
|
||||
* 绑定那次 patch 必须带上 driving_vehicle 原值(该列 {@code FieldStrategy.ALWAYS}
|
||||
* 会把缺失的列写成 NULL,司机端的车辆绑定会整块消失)。
|
||||
*/
|
||||
class DriverEnsureProfileTest {
|
||||
|
||||
private static final Long MP_USER_ID = 7001L;
|
||||
private static final Long OTHER_USER_ID = 7002L;
|
||||
private static final Long DRIVER_ID = 9001L;
|
||||
private static final Long NEW_DRIVER_ID = 9002L;
|
||||
private static final String TENANT_ID = "000000";
|
||||
private static final String MOBILE = "15975348524";
|
||||
private static final String PLATE = "桂A32523";
|
||||
|
||||
private DriverMapper driverMapper;
|
||||
private DriverServiceImpl service;
|
||||
|
||||
@BeforeAll
|
||||
static void initTableInfo() {
|
||||
// save 走 MyBatis-Plus 的实体元数据缓存
|
||||
MybatisConfiguration configuration = new MybatisConfiguration();
|
||||
MapperBuilderAssistant assistant = new MapperBuilderAssistant(configuration, "");
|
||||
assistant.setCurrentNamespace("test");
|
||||
TableInfoHelper.initTableInfo(assistant, Driver.class);
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws Exception {
|
||||
driverMapper = mock(DriverMapper.class);
|
||||
IMpUserClient mpUserClient = mock(IMpUserClient.class);
|
||||
ISysClient sysClient = mock(ISysClient.class);
|
||||
service = new DriverServiceImpl(mpUserClient, sysClient);
|
||||
inject("baseMapper", driverMapper);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------ 新建占位档案
|
||||
|
||||
@Test
|
||||
@DisplayName("无账号档案、无同号档案:按票 01 的占位值新建,created=true")
|
||||
void createsPlaceholderProfile() {
|
||||
// 第一次按 user_id 查、第二次按手机号查,都没有
|
||||
when(driverMapper.selectList(any())).thenReturn(Collections.emptyList(), Collections.emptyList());
|
||||
when(driverMapper.insert(any(Driver.class))).thenAnswer(invocation -> {
|
||||
Driver inserted = invocation.getArgument(0);
|
||||
inserted.setId(NEW_DRIVER_ID);
|
||||
return 1;
|
||||
});
|
||||
|
||||
DriverEnsureProfileVO result = service.ensureProfile(TENANT_ID, MP_USER_ID, MOBILE);
|
||||
|
||||
assertTrue(result.getCreated());
|
||||
assertFalse(result.getBoundExisting());
|
||||
assertNull(result.getMessage());
|
||||
assertEquals(NEW_DRIVER_ID, result.getDriverId());
|
||||
|
||||
ArgumentCaptor<Driver> captor = ArgumentCaptor.forClass(Driver.class);
|
||||
verify(driverMapper).insert(captor.capture());
|
||||
Driver saved = captor.getValue();
|
||||
assertEquals(TENANT_ID, saved.getTenantId(), "tenant_id 必须显式写入且非空");
|
||||
assertEquals(MP_USER_ID, saved.getUserId());
|
||||
assertEquals(MOBILE, saved.getMobile());
|
||||
assertEquals("司机8524", saved.getDriverName());
|
||||
assertNull(saved.getIdCardNo(), "身份证号留 NULL 才是「未采集」,也是补全判据");
|
||||
assertEquals("", saved.getDrivingType());
|
||||
assertEquals("", saved.getDriverType());
|
||||
assertEquals("", saved.getOrganizationName());
|
||||
assertEquals("", saved.getEmergencyContactName());
|
||||
assertEquals("", saved.getEmergencyContactMobile());
|
||||
assertEquals(1, saved.getStatus());
|
||||
assertEquals("login_auto", saved.getProfileSource());
|
||||
assertNotNull(saved.getAccountBoundTime());
|
||||
verify(driverMapper, never()).updateById(any(Driver.class));
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------ 绑定已有档案
|
||||
|
||||
@Test
|
||||
@DisplayName("命中同号未绑定的档案:绑定并留痕,patch 必须带上 driving_vehicle 原值")
|
||||
void bindsExistingProfileAndKeepsDrivingVehicle() {
|
||||
Driver existing = driver(DRIVER_ID, null, PLATE);
|
||||
when(driverMapper.selectList(any())).thenReturn(Collections.emptyList(), List.of(existing));
|
||||
when(driverMapper.selectById(DRIVER_ID)).thenReturn(existing);
|
||||
when(driverMapper.updateById(any(Driver.class))).thenReturn(1);
|
||||
|
||||
DriverEnsureProfileVO result = service.ensureProfile(TENANT_ID, MP_USER_ID, MOBILE);
|
||||
|
||||
assertFalse(result.getCreated());
|
||||
assertTrue(result.getBoundExisting());
|
||||
assertNull(result.getMessage());
|
||||
assertEquals(DRIVER_ID, result.getDriverId());
|
||||
|
||||
ArgumentCaptor<Driver> captor = ArgumentCaptor.forClass(Driver.class);
|
||||
verify(driverMapper).updateById(captor.capture());
|
||||
Driver patch = captor.getValue();
|
||||
assertEquals(MP_USER_ID, patch.getUserId());
|
||||
assertEquals("login_bound", patch.getProfileSource());
|
||||
assertNotNull(patch.getAccountBoundTime());
|
||||
assertEquals(PLATE, patch.getDrivingVehicle(),
|
||||
"绑定 patch 不带 driving_vehicle 会把司机的车辆绑定写掉");
|
||||
assertNull(patch.getMobile(), "绑定不得覆盖档案上的手机号");
|
||||
verify(driverMapper, never()).insert(any(Driver.class));
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------ 幂等重入
|
||||
|
||||
@Test
|
||||
@DisplayName("按 user_id 命中:直接返回该档案,不新建、不改手机号")
|
||||
void returnsExistingProfileForRepeatLogin() {
|
||||
when(driverMapper.selectList(any())).thenReturn(List.of(driver(DRIVER_ID, MP_USER_ID, PLATE)));
|
||||
|
||||
DriverEnsureProfileVO result = service.ensureProfile(TENANT_ID, MP_USER_ID, MOBILE);
|
||||
|
||||
assertFalse(result.getCreated());
|
||||
assertFalse(result.getBoundExisting());
|
||||
assertNull(result.getMessage());
|
||||
assertEquals(DRIVER_ID, result.getDriverId());
|
||||
verify(driverMapper, never()).insert(any(Driver.class));
|
||||
verify(driverMapper, never()).updateById(any(Driver.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("并发重复建档撞唯一键:重查后按已存在的档案返回,不把异常抛给登录方")
|
||||
void toleratesConcurrentInsert() {
|
||||
Driver concurrent = driver(DRIVER_ID, MP_USER_ID, PLATE);
|
||||
when(driverMapper.selectList(any())).thenReturn(
|
||||
Collections.emptyList(),
|
||||
Collections.emptyList(),
|
||||
List.of(concurrent));
|
||||
when(driverMapper.insert(any(Driver.class))).thenThrow(new DuplicateKeyException("uk_transport_driver_user"));
|
||||
|
||||
DriverEnsureProfileVO result = service.ensureProfile(TENANT_ID, MP_USER_ID, MOBILE);
|
||||
|
||||
assertFalse(result.getCreated());
|
||||
assertFalse(result.getBoundExisting());
|
||||
assertEquals(DRIVER_ID, result.getDriverId());
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------ 冲突
|
||||
|
||||
@Test
|
||||
@DisplayName("同手机号多条档案:拒绝绑定也不新建,返回可读原因")
|
||||
void rejectsMultipleProfilesWithSameMobile() {
|
||||
when(driverMapper.selectList(any())).thenReturn(
|
||||
Collections.emptyList(),
|
||||
List.of(driver(DRIVER_ID, null, PLATE), driver(9003L, null, "")));
|
||||
|
||||
DriverEnsureProfileVO result = service.ensureProfile(TENANT_ID, MP_USER_ID, MOBILE);
|
||||
|
||||
assertFalse(result.getCreated());
|
||||
assertFalse(result.getBoundExisting());
|
||||
assertNull(result.getDriverId());
|
||||
assertEquals("该手机号存在多条司机档案,请联系管理员核对", result.getMessage());
|
||||
verify(driverMapper, never()).insert(any(Driver.class));
|
||||
verify(driverMapper, never()).updateById(any(Driver.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("同手机号档案已属于其他账号:不覆盖,返回可读原因")
|
||||
void rejectsMobileOwnedByAnotherAccount() {
|
||||
when(driverMapper.selectList(any())).thenReturn(
|
||||
Collections.emptyList(),
|
||||
List.of(driver(DRIVER_ID, OTHER_USER_ID, PLATE)));
|
||||
|
||||
DriverEnsureProfileVO result = service.ensureProfile(TENANT_ID, MP_USER_ID, MOBILE);
|
||||
|
||||
assertFalse(result.getCreated());
|
||||
assertFalse(result.getBoundExisting());
|
||||
assertNull(result.getDriverId());
|
||||
assertEquals("该手机号已被其他账号占用,请联系管理员", result.getMessage());
|
||||
verify(driverMapper, never()).insert(any(Driver.class));
|
||||
verify(driverMapper, never()).updateById(any(Driver.class));
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------ 入参兜底
|
||||
|
||||
@Test
|
||||
@DisplayName("租户为空且当前上下文也没有租户:拒绝写库")
|
||||
void rejectsBlankTenant() {
|
||||
try (MockedStatic<AuthUtil> authUtil = mockStatic(AuthUtil.class)) {
|
||||
authUtil.when(AuthUtil::getTenantId).thenReturn(null);
|
||||
|
||||
assertThrows(ServiceException.class, () -> service.ensureProfile(null, MP_USER_ID, MOBILE));
|
||||
}
|
||||
verify(driverMapper, never()).insert(any(Driver.class));
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------ 夹具
|
||||
|
||||
private Driver driver(Long id, Long userId, String drivingVehicle) {
|
||||
Driver driver = new Driver();
|
||||
driver.setId(id);
|
||||
driver.setIsDeleted(0);
|
||||
driver.setTenantId(TENANT_ID);
|
||||
driver.setUserId(userId);
|
||||
driver.setMobile(MOBILE);
|
||||
driver.setDrivingVehicle(drivingVehicle);
|
||||
return driver;
|
||||
}
|
||||
|
||||
private void inject(String field, Object value) throws Exception {
|
||||
Class<?> type = service.getClass();
|
||||
while (type != null) {
|
||||
try {
|
||||
Field declared = type.getDeclaredField(field);
|
||||
declared.setAccessible(true);
|
||||
declared.set(service, value);
|
||||
return;
|
||||
} catch (NoSuchFieldException ignored) {
|
||||
type = type.getSuperclass();
|
||||
}
|
||||
}
|
||||
throw new IllegalStateException("未找到字段:" + field);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user