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.DmsFileUploadRegisterForm;
|
import jnpf.dmsEntity.storage.vo.DmsFileUploadVO;
|
import jnpf.dmsIntegration.audit.DmsAuditAdapter;
|
import jnpf.dmsMapper.DmsDocumentMapper;
|
import jnpf.dmsMapper.DmsFolderMapper;
|
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 jnpf.dmsService.search.DmsSearchIndexJobService;
|
import jnpf.dmsService.pdf.DmsPdfGenerationTaskService;
|
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.verifyNoInteractions;
|
import static org.mockito.Mockito.when;
|
|
class DmsFileUploadServiceTest {
|
|
@Test
|
void registersDocumentVersionResourcesAndObjectInOneServiceCall() {
|
Fixture fixture = new Fixture();
|
DmsFileUploadRegisterForm form = fixture.form();
|
form.setExtendedProperties(new com.fasterxml.jackson.databind.ObjectMapper().createObjectNode().put("prepared_by_name", "管理员"));
|
DmsFileUploadVO created = fixture.view(form);
|
when(fixture.fileObjectMapper.selectUploadBySource("tenant-1", "jnpf-file", "source-1"))
|
.thenReturn(null, created);
|
|
DmsFileUploadVO result = fixture.service.register(form, "jnpf-file");
|
|
assertSame(created, result);
|
verify(fixture.decisionService).requireAllowed(any(), any(), any());
|
ArgumentCaptor<DmsDocumentEntity> documentCaptor = ArgumentCaptor.forClass(DmsDocumentEntity.class);
|
verify(fixture.documentMapper).insert(documentCaptor.capture());
|
assertEquals("质量手册", documentCaptor.getValue().getName());
|
assertEquals("MM-R02", documentCaptor.getValue().getDocumentNo());
|
assertEquals("QUALITY_MANUAL", documentCaptor.getValue().getDocumentType());
|
assertEquals("dept-quality", documentCaptor.getValue().getResponsibleDeptId());
|
|
ArgumentCaptor<DmsFileVersionEntity> versionCaptor = ArgumentCaptor.forClass(DmsFileVersionEntity.class);
|
verify(fixture.propertiesMapper).insertVersion(versionCaptor.capture(), org.mockito.ArgumentMatchers.eq(form.getExtendedProperties() == null ? null : form.getExtendedProperties().toString()));
|
assertEquals("D(00)", versionCaptor.getValue().getVersionLabel());
|
assertEquals("author-1", versionCaptor.getValue().getPreparedBy());
|
assertEquals(LocalDate.of(2026, 8, 21), versionCaptor.getValue().getPreparedOn());
|
assertEquals("yes", versionCaptor.getValue().getShifouShoukong());
|
verify(fixture.resourceMapper, org.mockito.Mockito.times(2)).insert(any(DmsPermissionResourceEntity.class));
|
verify(fixture.versionStateMapper).insertDirectEffective("tenant-1", form.getDocumentId(),
|
form.getFileVersionId(), "user-1");
|
verify(fixture.fileObjectMapper).insert(any(DmsFileObjectEntity.class));
|
verify(fixture.searchIndexJobService).enqueueContentUpsert("tenant-1", form.getDocumentId(),
|
form.getFileVersionId(), form.getChecksumSha256());
|
verify(fixture.operationLogService).recordUpload(any(), org.mockito.ArgumentMatchers.eq("tenant-1"),
|
org.mockito.ArgumentMatchers.eq(form.getDocumentId()),
|
org.mockito.ArgumentMatchers.eq(form.getFileVersionId()),
|
org.mockito.ArgumentMatchers.eq("report.txt"),
|
org.mockito.ArgumentMatchers.eq("jnpf-file"),
|
org.mockito.ArgumentMatchers.eq("source-1"));
|
verify(fixture.auditAdapter).fileChange(any());
|
}
|
|
@Test
|
void ordinaryUploadIgnoresForgedUncontrolledFlag() {
|
Fixture fixture = new Fixture();
|
DmsFileUploadRegisterForm form = fixture.form();
|
form.setShifouShoukong("no");
|
DmsFileUploadVO created = fixture.view(form);
|
created.setShifouShoukong("yes");
|
when(fixture.fileObjectMapper.selectUploadBySource("tenant-1", "jnpf-file", "source-1"))
|
.thenReturn(null, created);
|
|
fixture.service.register(form, "jnpf-file");
|
|
ArgumentCaptor<DmsFileVersionEntity> versionCaptor =
|
ArgumentCaptor.forClass(DmsFileVersionEntity.class);
|
verify(fixture.propertiesMapper).insertVersion(versionCaptor.capture(), org.mockito.ArgumentMatchers.eq(form.getExtendedProperties() == null ? null : form.getExtendedProperties().toString()));
|
assertEquals("yes", versionCaptor.getValue().getShifouShoukong());
|
}
|
|
@Test
|
void approvedUploadPreservesTrustedUncontrolledFlag() {
|
Fixture fixture = new Fixture();
|
DmsFileUploadRegisterForm form = fixture.form();
|
form.setShifouShoukong("no");
|
DmsFileUploadVO created = fixture.view(form);
|
when(fixture.fileObjectMapper.selectUploadBySource("tenant-1", "jnpf-file", "source-1"))
|
.thenReturn(null, created);
|
|
fixture.service.registerApproved(form, "jnpf-file", "flow-1", "task-1",
|
fixture.approvedActor());
|
|
ArgumentCaptor<DmsFileVersionEntity> versionCaptor =
|
ArgumentCaptor.forClass(DmsFileVersionEntity.class);
|
verify(fixture.propertiesMapper).insertVersion(versionCaptor.capture(), org.mockito.ArgumentMatchers.eq(form.getExtendedProperties() == null ? null : form.getExtendedProperties().toString()));
|
assertEquals("no", versionCaptor.getValue().getShifouShoukong());
|
verify(fixture.decisionService, never()).requireAllowed(any(), any(), any());
|
}
|
|
@Test
|
void returnsExistingUploadWithoutDuplicateWrites() {
|
Fixture fixture = new Fixture();
|
DmsFileUploadRegisterForm form = fixture.form();
|
DmsFileUploadVO existing = fixture.view(form);
|
when(fixture.fileObjectMapper.selectUploadBySource("tenant-1", "jnpf-file", "source-1"))
|
.thenReturn(existing);
|
|
DmsFileUploadVO result = fixture.service.register(form, "jnpf-file");
|
|
assertSame(existing, result);
|
verify(fixture.documentMapper, never()).insert(any(DmsDocumentEntity.class));
|
verify(fixture.propertiesMapper, never()).insertVersion(any(), org.mockito.ArgumentMatchers.nullable(String.class));
|
verify(fixture.versionStateMapper, never()).insertDirectEffective(any(), any(), any(), any());
|
verify(fixture.fileObjectMapper, never()).insert(any(DmsFileObjectEntity.class));
|
verify(fixture.searchIndexJobService).enqueueContentUpsert("tenant-1", form.getDocumentId(),
|
form.getFileVersionId(), form.getChecksumSha256());
|
verify(fixture.auditAdapter, never()).fileChange(any());
|
}
|
|
@Test
|
void ordinaryUploadRetryAlsoIgnoresForgedUncontrolledFlag() {
|
Fixture fixture = new Fixture();
|
DmsFileUploadRegisterForm form = fixture.form();
|
form.setShifouShoukong("no");
|
DmsFileUploadVO existing = fixture.view(form);
|
existing.setShifouShoukong("yes");
|
when(fixture.fileObjectMapper.selectUploadBySource("tenant-1", "jnpf-file", "source-1"))
|
.thenReturn(existing);
|
|
assertSame(existing, fixture.service.register(form, "jnpf-file"));
|
|
verify(fixture.propertiesMapper, never()).insertVersion(any(), org.mockito.ArgumentMatchers.nullable(String.class));
|
}
|
|
@Test
|
void approvedUploadRetryUsesTrustedUncontrolledFlag() {
|
Fixture fixture = new Fixture();
|
DmsFileUploadRegisterForm form = fixture.form();
|
form.setShifouShoukong("no");
|
DmsFileUploadVO existing = fixture.view(form);
|
when(fixture.fileObjectMapper.selectUploadBySource("tenant-1", "jnpf-file", "source-1"))
|
.thenReturn(existing);
|
|
DmsFileUploadVO result = fixture.service.registerApproved(form, "jnpf-file", "flow-1", "task-1",
|
fixture.approvedActor());
|
|
assertSame(existing, result);
|
verify(fixture.propertiesMapper, never()).insertVersion(any(), org.mockito.ArgumentMatchers.nullable(String.class));
|
}
|
|
@Test
|
void rejectsIdempotentRetryWhenDocumentNumberChanges() {
|
Fixture fixture = new Fixture();
|
DmsFileUploadRegisterForm form = fixture.form();
|
DmsFileUploadVO existing = fixture.view(form);
|
form.setDocumentNo("MM-R03");
|
when(fixture.fileObjectMapper.selectUploadBySource("tenant-1", "jnpf-file", "source-1"))
|
.thenReturn(existing);
|
|
assertThrows(DmsPermissionException.class,
|
() -> fixture.service.register(form, "jnpf-file"));
|
|
verify(fixture.documentMapper, never()).insert(any(DmsDocumentEntity.class));
|
verify(fixture.propertiesMapper, never()).insertVersion(any(), org.mockito.ArgumentMatchers.nullable(String.class));
|
verify(fixture.versionStateMapper, never()).insertDirectEffective(any(), any(), any(), any());
|
verify(fixture.fileObjectMapper, never()).insert(any(DmsFileObjectEntity.class));
|
}
|
|
@Test
|
void workflowUploadIsStoredPendingWithoutSearchIndex() {
|
Fixture fixture = new Fixture();
|
DmsFileUploadRegisterForm form = fixture.form();
|
DmsFileUploadVO created = fixture.view(form);
|
created.setLifecycleStatus("PENDING_APPROVAL");
|
when(fixture.fileObjectMapper.selectUploadBySource("tenant-1", "jnpf-file", "source-1"))
|
.thenReturn(null, created);
|
|
DmsFileUploadVO result = fixture.service.registerWorkflowPending(
|
form, "jnpf-file", "flow-1", "task-1");
|
|
assertSame(created, result);
|
verify(fixture.versionStateMapper).insertWorkflowPending("tenant-1", form.getDocumentId(),
|
form.getFileVersionId(), "source-1", "flow-1", "task-1", "CREATE", "user-1");
|
verify(fixture.versionStateMapper, never()).insertDirectEffective(any(), any(), any(), any());
|
verifyNoInteractions(fixture.searchIndexJobService);
|
}
|
|
@Test
|
void genericUploadCannotClaimBusinessOccupiedNumber() {
|
Fixture f=new Fixture();
|
var form=f.form();form.setDocumentNo("BUSINESS-001");
|
org.mockito.Mockito.doThrow(new jnpf.exception.DataException("business number occupied"))
|
.when(f.businessOccupancy).guardStandaloneRegistration("tenant-1","BUSINESS-001");
|
assertThrows(jnpf.exception.DataException.class,()->f.service.register(form,"jnpf-file"));
|
org.mockito.Mockito.verifyNoInteractions(f.fileObjectMapper,f.documentMapper,f.versionStateMapper);
|
}
|
|
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 DmsFolderMapper folderMapper = mock(DmsFolderMapper.class);
|
private final DmsFileVersionMapper fileVersionMapper = mock(DmsFileVersionMapper.class);
|
private final jnpf.dmsMapper.DmsVersionPropertiesMapper propertiesMapper = mock(jnpf.dmsMapper.DmsVersionPropertiesMapper.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 DmsFileOperationLogService operationLogService = mock(DmsFileOperationLogService.class);
|
private final DmsSearchIndexJobService searchIndexJobService = mock(DmsSearchIndexJobService.class);
|
private final DmsBusinessLifecycleGuard businessOccupancy = mock(DmsBusinessLifecycleGuard.class);
|
private final DmsPdfGenerationTaskService pdfGenerationTaskService = mock(DmsPdfGenerationTaskService.class);
|
private final DmsFileUploadService service = new DmsFileUploadService(new DmsVersionPropertiesService(propertiesMapper), currentIdentity, decisionService,
|
documentMapper, folderMapper, fileVersionMapper, resourceMapper, fileObjectMapper,
|
versionStateMapper, auditAdapter, operationLogService, searchIndexJobService,
|
pdfGenerationTaskService, businessOccupancy);
|
|
private Fixture() {
|
when(propertiesMapper.insertVersion(any(), org.mockito.ArgumentMatchers.nullable(String.class))).thenReturn(1);
|
UserInfo user = new UserInfo();
|
user.setUserId("user-1");
|
user.setTenantId("tenant-1");
|
when(currentIdentity.requireUser()).thenReturn(user);
|
when(currentIdentity.tenantId(user)).thenReturn("tenant-1");
|
}
|
|
private DmsFileUploadRegisterForm form() {
|
DmsFileUploadRegisterForm form = new DmsFileUploadRegisterForm();
|
form.setDocumentId(UUID.fromString("018f0000-0000-7000-8000-000000000301"));
|
form.setFileVersionId(UUID.fromString("018f0000-0000-7000-8000-000000000302"));
|
form.setFileObjectId(UUID.fromString("018f0000-0000-7000-8000-000000000303"));
|
form.setFolderId(UUID.fromString("018f0000-0000-7000-8000-000000000304"));
|
form.setDocumentNo("MM-R02");
|
form.setDocumentName("质量手册");
|
form.setDocumentType("QUALITY_MANUAL");
|
form.setResponsibleDeptId("dept-quality");
|
form.setVersionLabel("D(00)");
|
form.setFileName("report.txt");
|
form.setMimeType("text/plain");
|
form.setSizeBytes(12L);
|
form.setChecksumSha256("a".repeat(64));
|
form.setPreparedBy("author-1");
|
form.setPreparedOn(LocalDate.of(2026, 8, 21));
|
form.setStoragePlatform("seaweedfs-1");
|
form.setBucketName("jnpf-dms");
|
form.setBasePath("dms/");
|
form.setObjectPath("2026/08/17/document/");
|
form.setObjectName("version.txt");
|
form.setSourceId("source-1");
|
return form;
|
}
|
|
private DmsFileUploadVO view(DmsFileUploadRegisterForm form) {
|
DmsFileUploadVO view = new DmsFileUploadVO();
|
view.setDocumentId(form.getDocumentId());
|
view.setFileVersionId(form.getFileVersionId());
|
view.setFileObjectId(form.getFileObjectId());
|
view.setFolderId(form.getFolderId());
|
view.setDocumentNo(form.getDocumentNo());
|
view.setDocumentName(form.getDocumentName());
|
view.setDocumentType(form.getDocumentType());
|
view.setResponsibleDeptId(form.getResponsibleDeptId());
|
view.setVersionNo(1L);
|
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.setShifouShoukong(form.getShifouShoukong() == null ? "yes" : form.getShifouShoukong());
|
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;
|
}
|
|
private UserInfo approvedActor() {
|
UserInfo actor = new UserInfo();
|
actor.setUserId("approver-1");
|
actor.setTenantId("tenant-1");
|
when(currentIdentity.tenantId(actor)).thenReturn("tenant-1");
|
return actor;
|
}
|
}
|
}
|