✅ 补个人认证「提交→审核→生效」闭环测试
司机认证最关键的一条业务闭环,单看提交侧或审核侧都验证不到: 提交之后司机侧查到的必须仍是旧生效值(否则等于提交即自动认证), 审核通过之后才变成新值(否则认证永远不生效)。 用内存 Map 承接记录读写,让提交服务与审核服务真实共享同一份状态, 避免两边各自 mock 成「恰好一致」——那种测试对集成缺陷是盲的。 5 个用例覆盖: - 提交后仍是旧值、审核通过后才变新值; - 驳回后生效值不变(已认证司机不会被驳回下线); - 从未认证时审核前查不到生效值; - 连续提交两次只剩一条待审,且被作废的那条不能再被审核; - 已通过的记录不可再审(防止把生效快照改坏)。 全模块 63 个测试通过:mvn -o test -pl blade-service/blade-transport -am
This commit is contained in:
+217
@@ -0,0 +1,217 @@
|
||||
package org.springblade.transport.service.impl;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springblade.transport.pojo.dto.PersonCertSubmitRequest;
|
||||
import org.springblade.transport.pojo.entity.Driver;
|
||||
import org.springblade.transport.pojo.entity.TransportCertRecord;
|
||||
import org.springblade.transport.service.IDriverAppService;
|
||||
import org.springblade.transport.service.IDriverService;
|
||||
import org.springblade.transport.service.ITransportCertRecordService;
|
||||
import org.springblade.transport.service.ITransportVehicleService;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyLong;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* 个人认证的「提交 → 生效值不变 → 审核通过 → 生效值更新」闭环测试。
|
||||
* <p>
|
||||
* 这是司机认证最关键的一条业务闭环,也是单看提交侧或审核侧都验证不到的:
|
||||
* 提交之后司机侧查到的必须仍是**旧生效值**(否则等于提交即自动认证),
|
||||
* 审核通过之后才变成**新值**(否则认证永远不生效)。
|
||||
* <p>
|
||||
* 用一个内存版的记录存储把两侧接起来,避免两边各自 mock 成「恰好一致」。
|
||||
*/
|
||||
class PersonCertRoundTripTest {
|
||||
|
||||
private static final Long DRIVER_ID = 9001L;
|
||||
|
||||
private IDriverAppService driverAppService;
|
||||
private IDriverService driverService;
|
||||
private ITransportCertRecordService certRecordService;
|
||||
private DriverCertServiceImpl driverCertService;
|
||||
private CertAuditServiceImpl certAuditService;
|
||||
|
||||
/**
|
||||
* 内存记录表:Id → 记录
|
||||
*/
|
||||
private final Map<Long, TransportCertRecord> recordStore = new HashMap<>();
|
||||
private Driver driver;
|
||||
private long idSequence = 100L;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
recordStore.clear();
|
||||
idSequence = 100L;
|
||||
|
||||
driverAppService = mock(IDriverAppService.class);
|
||||
driverService = mock(IDriverService.class);
|
||||
certRecordService = mock(ITransportCertRecordService.class);
|
||||
ITransportVehicleService transportVehicleService = mock(ITransportVehicleService.class);
|
||||
|
||||
driver = new Driver();
|
||||
driver.setId(DRIVER_ID);
|
||||
driver.setDriverName("张三");
|
||||
when(driverAppService.currentDriverEntity()).thenAnswer(invocation -> driver);
|
||||
// 司机表只持有指针,指针是被审核链路改的,这里让桩跟着内存状态走
|
||||
when(driverService.updateById(any(Driver.class))).thenAnswer(invocation -> {
|
||||
Driver patch = invocation.getArgument(0);
|
||||
if (patch.getEffectiveCertRecordId() != null) {
|
||||
driver.setEffectiveCertRecordId(patch.getEffectiveCertRecordId());
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
stubRecordStore();
|
||||
|
||||
driverCertService = new DriverCertServiceImpl(driverAppService, driverService,
|
||||
transportVehicleService, certRecordService);
|
||||
certAuditService = new CertAuditServiceImpl(certRecordService, transportVehicleService, driverService);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("提交后仍是旧生效值,审核通过后才变新值")
|
||||
void submitThenApprove() {
|
||||
// 1) 先走一遍完整流程,让该司机有一个生效值
|
||||
Long firstRecordId = driverCertService.submitPersonCert(personCert("张三", "110101199001011234"));
|
||||
certAuditService.audit(firstRecordId, true, null);
|
||||
assertEquals("张三", driverCertService.getEffectivePersonCert().getRealName(),
|
||||
"审核通过后应能查到生效值");
|
||||
|
||||
// 2) 提交新内容:生效值必须**不变**
|
||||
Long secondRecordId = driverCertService.submitPersonCert(personCert("张三改名", "110101199001011234"));
|
||||
assertEquals("张三", driverCertService.getEffectivePersonCert().getRealName(),
|
||||
"提交只产生待审记录,不得改变生效值——否则等于提交即自动认证");
|
||||
assertEquals(firstRecordId, driver.getEffectiveCertRecordId(),
|
||||
"指针应仍指向原来那条已通过记录");
|
||||
|
||||
// 3) 审核通过:生效值才变成新值
|
||||
certAuditService.audit(secondRecordId, true, null);
|
||||
assertEquals("张三改名", driverCertService.getEffectivePersonCert().getRealName(),
|
||||
"审核通过后生效值应切到新记录");
|
||||
assertEquals(secondRecordId, driver.getEffectiveCertRecordId());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("驳回后生效值不变(已认证对象不会被驳回下线)")
|
||||
void rejectKeepsEffectiveValue() {
|
||||
Long firstRecordId = driverCertService.submitPersonCert(personCert("张三", "110101199001011234"));
|
||||
certAuditService.audit(firstRecordId, true, null);
|
||||
|
||||
Long secondRecordId = driverCertService.submitPersonCert(personCert("张三改名", "110101199001011234"));
|
||||
certAuditService.audit(secondRecordId, false, "证件照片模糊");
|
||||
|
||||
assertEquals("张三", driverCertService.getEffectivePersonCert().getRealName(),
|
||||
"驳回只终结该次提交,既得认证必须保持有效");
|
||||
assertEquals(firstRecordId, driver.getEffectiveCertRecordId());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("从未认证过时,审核通过前查不到生效值")
|
||||
void noEffectiveBeforeFirstApproval() {
|
||||
assertNull(driverCertService.getEffectivePersonCert());
|
||||
|
||||
Long recordId = driverCertService.submitPersonCert(personCert("张三", "110101199001011234"));
|
||||
assertNull(driverCertService.getEffectivePersonCert(),
|
||||
"待审期间不应有生效值");
|
||||
|
||||
certAuditService.audit(recordId, true, null);
|
||||
assertEquals("张三", driverCertService.getEffectivePersonCert().getRealName());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("连续提交两次,不存在两条待审,且旧待审被作废")
|
||||
void consecutiveSubmitsLeaveSinglePending() {
|
||||
Long first = driverCertService.submitPersonCert(personCert("第一次", "110101199001011234"));
|
||||
Long second = driverCertService.submitPersonCert(personCert("第二次", "110101199001011234"));
|
||||
|
||||
assertEquals(TransportCertRecord.STATUS_SUPERSEDED, recordStore.get(first).getStatus(),
|
||||
"前一条待审应被作废");
|
||||
assertEquals(TransportCertRecord.STATUS_PENDING, recordStore.get(second).getStatus());
|
||||
|
||||
long pendingCount = recordStore.values().stream()
|
||||
.filter(record -> TransportCertRecord.STATUS_PENDING.equals(record.getStatus()))
|
||||
.count();
|
||||
assertEquals(1L, pendingCount, "同一司机至多一条待审");
|
||||
|
||||
// 被作废的那条不能再被审核(否则会把已作废的记录变成生效值)
|
||||
org.junit.jupiter.api.Assertions.assertThrows(
|
||||
org.springblade.core.log.exception.ServiceException.class,
|
||||
() -> certAuditService.audit(first, true, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("已通过的记录不可再被审核(防止把生效快照改坏)")
|
||||
void approvedRecordCannotBeReaudited() {
|
||||
Long recordId = driverCertService.submitPersonCert(personCert("张三", "110101199001011234"));
|
||||
certAuditService.audit(recordId, true, null);
|
||||
|
||||
org.junit.jupiter.api.Assertions.assertThrows(
|
||||
org.springblade.core.log.exception.ServiceException.class,
|
||||
() -> certAuditService.audit(recordId, false, "事后反悔"));
|
||||
assertEquals("张三", driverCertService.getEffectivePersonCert().getRealName(),
|
||||
"重复审核被拒后,生效值不受影响");
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------ 内存记录存储
|
||||
|
||||
/**
|
||||
* 用内存 Map 承接记录读写,让两个服务真实地共享同一份状态
|
||||
*/
|
||||
private void stubRecordStore() {
|
||||
when(certRecordService.save(any(TransportCertRecord.class))).thenAnswer(invocation -> {
|
||||
TransportCertRecord record = invocation.getArgument(0);
|
||||
record.setId(++idSequence);
|
||||
recordStore.put(record.getId(), copyOf(record));
|
||||
return true;
|
||||
});
|
||||
when(certRecordService.updateById(any(TransportCertRecord.class))).thenAnswer(invocation -> {
|
||||
TransportCertRecord patch = invocation.getArgument(0);
|
||||
TransportCertRecord stored = recordStore.get(patch.getId());
|
||||
if (stored != null) {
|
||||
if (patch.getStatus() != null) {
|
||||
stored.setStatus(patch.getStatus());
|
||||
}
|
||||
stored.setRejectReason(patch.getRejectReason());
|
||||
stored.setAuditTime(patch.getAuditTime());
|
||||
stored.setAuditUser(patch.getAuditUser());
|
||||
}
|
||||
return true;
|
||||
});
|
||||
when(certRecordService.getById(anyLong()))
|
||||
.thenAnswer(invocation -> recordStore.get(invocation.getArgument(0, Long.class)));
|
||||
when(certRecordService.selectPendingRecord(any(), anyLong())).thenAnswer(invocation -> {
|
||||
String bizType = invocation.getArgument(0);
|
||||
Long bizId = invocation.getArgument(1);
|
||||
return recordStore.values().stream()
|
||||
.filter(record -> bizType.equals(record.getBizType())
|
||||
&& bizId.equals(record.getBizId())
|
||||
&& TransportCertRecord.STATUS_PENDING.equals(record.getStatus()))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
});
|
||||
}
|
||||
|
||||
private TransportCertRecord copyOf(TransportCertRecord source) {
|
||||
return JSON.parseObject(JSON.toJSONString(source), TransportCertRecord.class);
|
||||
}
|
||||
|
||||
private PersonCertSubmitRequest personCert(String realName, String idCardNo) {
|
||||
PersonCertSubmitRequest request = new PersonCertSubmitRequest();
|
||||
request.setRealName(realName);
|
||||
request.setIdCardNo(idCardNo);
|
||||
request.setIdCardFrontUrl("http://example.com/front.jpg");
|
||||
request.setIdCardBackUrl("http://example.com/back.jpg");
|
||||
return request;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user