✅ 补充司机档案自动建档与绑定回归测试
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