package jnpf.dmsService; import jnpf.base.UserInfo; import jnpf.dmsEntity.permission.entity.DmsDocumentEntity; import jnpf.dmsEntity.permission.entity.DmsFileVersionEntity; import jnpf.dmsEntity.permission.entity.DmsPermissionResourceEntity; import jnpf.dmsEntity.storage.entity.DmsFileObjectEntity; import jnpf.dmsEntity.storage.form.DmsFileRevisionRegisterForm; import jnpf.dmsEntity.storage.vo.DmsFileUploadVO; import jnpf.dmsIntegration.audit.DmsAuditAdapter; import jnpf.dmsMapper.DmsDocumentMapper; import jnpf.dmsMapper.DmsFileObjectMapper; import jnpf.dmsMapper.DmsFileVersionMapper; import jnpf.dmsMapper.DmsPermissionResourceMapper; import jnpf.dmsMapper.DmsVersionStateMapper; import jnpf.dmsPermission.DmsCurrentIdentity; import jnpf.dmsPermission.DmsPermissionException; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; import java.time.LocalDate; import java.util.UUID; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; class DmsFileRevisionServiceTest { @Test void createsNewVersionAndAtomicallySwitchesEffectiveState() { Fixture fixture = new Fixture(); DmsFileRevisionRegisterForm form = fixture.form(); DmsFileUploadVO created = fixture.view(form); when(fixture.fileObjectMapper.selectUploadBySource("tenant-1", "jnpf-file", "revision-1")) .thenReturn(null, created); DmsFileUploadVO result = fixture.service.register(form, "jnpf-file"); assertSame(created, result); verify(fixture.fileObjectMapper).lockSource("tenant-1", "jnpf-file", "revision-1"); verify(fixture.decisionService).requireAllowed(any(), any(), any()); ArgumentCaptor versionCaptor = ArgumentCaptor.forClass(DmsFileVersionEntity.class); verify(fixture.fileVersionMapper).insert(versionCaptor.capture()); assertEquals(2L, versionCaptor.getValue().getVersionNo()); assertEquals("D(01)", versionCaptor.getValue().getVersionLabel()); verify(fixture.resourceMapper).insert(any(DmsPermissionResourceEntity.class)); verify(fixture.fileObjectMapper).insert(any(DmsFileObjectEntity.class)); verify(fixture.versionStateMapper).supersedeCurrent("tenant-1", form.getDocumentId(), form.getBaseFileVersionId(), "D(01)", "user-1"); verify(fixture.versionStateMapper).insertRevisionEffective("tenant-1", form.getDocumentId(), form.getBaseFileVersionId(), form.getFileVersionId(), "revision-1", "更新质量职责", "user-1"); } @Test void returnsExactIdempotentRetryWithoutWritingAgain() { Fixture fixture = new Fixture(); DmsFileRevisionRegisterForm form = fixture.form(); DmsFileUploadVO existing = fixture.view(form); when(fixture.fileObjectMapper.selectUploadBySource("tenant-1", "jnpf-file", "revision-1")) .thenReturn(existing); DmsFileUploadVO result = fixture.service.register(form, "jnpf-file"); assertSame(existing, result); verify(fixture.documentMapper, never()).selectByTenantAndIdForUpdate(any(), any()); verify(fixture.fileVersionMapper, never()).insert(any(DmsFileVersionEntity.class)); verify(fixture.versionStateMapper, never()).supersedeCurrent(any(), any(), any(), any(), any()); } @Test void rejectsRetryWhenRevisionReasonChanges() { Fixture fixture = new Fixture(); DmsFileRevisionRegisterForm form = fixture.form(); DmsFileUploadVO existing = fixture.view(form); form.setRevisionReason("另一个原因"); when(fixture.fileObjectMapper.selectUploadBySource("tenant-1", "jnpf-file", "revision-1")) .thenReturn(existing); assertThrows(DmsPermissionException.class, () -> fixture.service.register(form, "jnpf-file")); verify(fixture.fileVersionMapper, never()).insert(any(DmsFileVersionEntity.class)); } @Test void rejectsWhenSelectedVersionIsNoLongerCurrent() { Fixture fixture = new Fixture(); DmsFileRevisionRegisterForm form = fixture.form(); DmsFileVersionEntity anotherCurrent = new DmsFileVersionEntity(); anotherCurrent.setId(UUID.fromString("018f0000-0000-7000-8000-000000000599")); when(fixture.fileVersionMapper.selectCurrentEffective("tenant-1", form.getDocumentId())) .thenReturn(anotherCurrent); assertThrows(DmsPermissionException.class, () -> fixture.service.register(form, "jnpf-file")); verify(fixture.fileVersionMapper, never()).insert(any(DmsFileVersionEntity.class)); verify(fixture.versionStateMapper, never()).supersedeCurrent(any(), any(), any(), any(), any()); } private static class Fixture { private final DmsCurrentIdentity currentIdentity = mock(DmsCurrentIdentity.class); private final DmsPermissionDecisionService decisionService = mock(DmsPermissionDecisionService.class); private final DmsDocumentMapper documentMapper = mock(DmsDocumentMapper.class); private final DmsFileVersionMapper fileVersionMapper = mock(DmsFileVersionMapper.class); private final DmsPermissionResourceMapper resourceMapper = mock(DmsPermissionResourceMapper.class); private final DmsFileObjectMapper fileObjectMapper = mock(DmsFileObjectMapper.class); private final DmsVersionStateMapper versionStateMapper = mock(DmsVersionStateMapper.class); private final DmsAuditAdapter auditAdapter = mock(DmsAuditAdapter.class); private final DmsFileRevisionService service = new DmsFileRevisionService(currentIdentity, decisionService, documentMapper, fileVersionMapper, resourceMapper, fileObjectMapper, versionStateMapper, auditAdapter); private final UUID documentId = UUID.fromString("018f0000-0000-7000-8000-000000000501"); private final UUID baseVersionId = UUID.fromString("018f0000-0000-7000-8000-000000000502"); private Fixture() { UserInfo user = new UserInfo(); user.setUserId("user-1"); user.setTenantId("tenant-1"); when(currentIdentity.requireUser()).thenReturn(user); when(currentIdentity.tenantId(user)).thenReturn("tenant-1"); DmsDocumentEntity document = new DmsDocumentEntity(); document.setId(documentId); when(documentMapper.selectByTenantAndIdForUpdate("tenant-1", documentId)) .thenReturn(document); DmsFileVersionEntity current = new DmsFileVersionEntity(); current.setId(baseVersionId); when(fileVersionMapper.selectCurrentEffective("tenant-1", documentId)).thenReturn(current); when(fileVersionMapper.selectNextVersionNo("tenant-1", documentId)).thenReturn(2L); when(versionStateMapper.supersedeCurrent(any(), any(), any(), any(), any())).thenReturn(1); } private DmsFileRevisionRegisterForm form() { DmsFileRevisionRegisterForm form = new DmsFileRevisionRegisterForm(); form.setDocumentId(documentId); form.setBaseFileVersionId(baseVersionId); form.setFileVersionId(UUID.fromString("018f0000-0000-7000-8000-000000000503")); form.setFileObjectId(UUID.fromString("018f0000-0000-7000-8000-000000000504")); form.setVersionLabel("D(01)"); form.setFileName("quality-manual-v2.docx"); form.setMimeType("application/vnd.openxmlformats-officedocument.wordprocessingml.document"); form.setSizeBytes(1024L); form.setChecksumSha256("b".repeat(64)); form.setPreparedBy("author-1"); form.setPreparedOn(LocalDate.of(2026, 8, 22)); form.setRevisionReason("更新质量职责"); form.setStoragePlatform("seaweedfs-1"); form.setBucketName("jnpf-dms"); form.setBasePath("dms/"); form.setObjectPath("2026/08/22/document/"); form.setObjectName("version-v2.docx"); form.setSourceId("revision-1"); return form; } private DmsFileUploadVO view(DmsFileRevisionRegisterForm form) { DmsFileUploadVO view = new DmsFileUploadVO(); view.setDocumentId(form.getDocumentId()); view.setBaseFileVersionId(form.getBaseFileVersionId()); view.setFileVersionId(form.getFileVersionId()); view.setFileObjectId(form.getFileObjectId()); view.setVersionNo(2L); view.setVersionLabel(form.getVersionLabel()); view.setFileName(form.getFileName()); view.setMimeType(form.getMimeType()); view.setSizeBytes(form.getSizeBytes()); view.setChecksumSha256(form.getChecksumSha256()); view.setPreparedBy(form.getPreparedBy()); view.setPreparedOn(form.getPreparedOn()); view.setRevisionReason(form.getRevisionReason()); view.setStoragePlatform(form.getStoragePlatform()); view.setBucketName(form.getBucketName()); view.setBasePath(form.getBasePath()); view.setObjectPath(form.getObjectPath()); view.setObjectName(form.getObjectName()); view.setSourceId(form.getSourceId()); return view; } } }