From 6831ba6fcab9c80f7ec39ca06636786ade4c576d Mon Sep 17 00:00:00 2001 From: "weicw1996@qq.com" Date: Fri, 2 Oct 2026 16:36:41 +0800 Subject: [PATCH] =?UTF-8?q?:white=5Fcheck=5Fmark:=20=E8=A1=A5=E4=B8=AA?= =?UTF-8?q?=E4=BA=BA=E8=AE=A4=E8=AF=81=E3=80=8C=E6=8F=90=E4=BA=A4=E2=86=92?= =?UTF-8?q?=E5=AE=A1=E6=A0=B8=E2=86=92=E7=94=9F=E6=95=88=E3=80=8D=E9=97=AD?= =?UTF-8?q?=E7=8E=AF=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 司机认证最关键的一条业务闭环,单看提交侧或审核侧都验证不到: 提交之后司机侧查到的必须仍是旧生效值(否则等于提交即自动认证), 审核通过之后才变成新值(否则认证永远不生效)。 用内存 Map 承接记录读写,让提交服务与审核服务真实共享同一份状态, 避免两边各自 mock 成「恰好一致」——那种测试对集成缺陷是盲的。 5 个用例覆盖: - 提交后仍是旧值、审核通过后才变新值; - 驳回后生效值不变(已认证司机不会被驳回下线); - 从未认证时审核前查不到生效值; - 连续提交两次只剩一条待审,且被作废的那条不能再被审核; - 已通过的记录不可再审(防止把生效快照改坏)。 全模块 63 个测试通过:mvn -o test -pl blade-service/blade-transport -am --- .../service/impl/PersonCertRoundTripTest.java | 217 ++++++++++++++++++ 1 file changed, 217 insertions(+) create mode 100644 blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/PersonCertRoundTripTest.java diff --git a/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/PersonCertRoundTripTest.java b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/PersonCertRoundTripTest.java new file mode 100644 index 00000000..2015fc67 --- /dev/null +++ b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/PersonCertRoundTripTest.java @@ -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; + +/** + * 个人认证的「提交 → 生效值不变 → 审核通过 → 生效值更新」闭环测试。 + *

+ * 这是司机认证最关键的一条业务闭环,也是单看提交侧或审核侧都验证不到的: + * 提交之后司机侧查到的必须仍是**旧生效值**(否则等于提交即自动认证), + * 审核通过之后才变成**新值**(否则认证永远不生效)。 + *

+ * 用一个内存版的记录存储把两侧接起来,避免两边各自 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 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; + } + +}