package jnpf.base.service;
|
|
import jnpf.base.UserInfo;
|
import jnpf.base.mapper.UploadEditDraftMapper;
|
import jnpf.base.model.flow.DataModel;
|
import jnpf.base.model.uploadedit.UploadEditDraft;
|
import jnpf.exception.DataException;
|
import jnpf.util.UserProvider;
|
import org.junit.Before;
|
import org.junit.After;
|
import org.junit.Test;
|
import org.mockito.MockedStatic;
|
import java.util.*;
|
import static org.junit.Assert.*;
|
import static org.mockito.Mockito.*;
|
import static org.mockito.ArgumentMatchers.*;
|
|
public class UploadEditDraftServiceTest {
|
private UploadEditDraftMapper mapper;
|
private UploadEditDraftService service;
|
private UploadEditAccess access;
|
private UploadFilePermissionService permissions;
|
private UploadEditTemporaryService temporary;
|
private UploadEditDraft draft;
|
private DataModel model;
|
private MockedStatic<UserProvider> users;
|
@Before public void setup() {
|
jnpf.util.context.SpringContext.setApplicationContext(mock(org.springframework.context.ApplicationContext.class));
|
UserInfo user = new UserInfo(); user.setUserId("owner"); user.setTenantId("tenant");
|
users = mockStatic(UserProvider.class); users.when(UserProvider::getUser).thenReturn(user);
|
users.when(UserProvider::getLoginUserId).thenReturn("owner");
|
mapper = mock(UploadEditDraftMapper.class);
|
access = mock(UploadEditAccess.class);
|
when(access.hasEnabledFields("model")).thenReturn(true);
|
temporary = mock(UploadEditTemporaryService.class);
|
permissions = mock(UploadFilePermissionService.class);
|
service = new UploadEditDraftService(mapper, access, temporary, permissions);
|
draft = new UploadEditDraft(); draft.setId("draft"); draft.setModelId("model"); draft.setRecordId("record");
|
draft.setOwnerId("owner"); draft.setTenantId("tenant"); draft.setRevision(3L); draft.setField("files");
|
draft.setBaselineJson("[{\"fileId\":\"A\"}]"); draft.setCurrentJson("[{\"fileId\":\"B\"}]"); draft.setLeaseId("lease");
|
when(mapper.lockRecord("tenant", "model", "record")).thenReturn(Collections.singletonList(draft));
|
when(mapper.compareAndSet(any(), anyLong())).thenReturn(1);
|
model = new DataModel(); model.setVisualId("model"); model.setMainId("record"); model.setDataNewMap(new HashMap<>());
|
model.getDataNewMap().put("files", Collections.singletonList(Collections.singletonMap("fileId", "B")));
|
Map<String, Object> revision = new HashMap<>(); revision.put("draftId", "draft"); revision.put("revision", 3L);
|
model.getDataNewMap().put(UploadEditDraftService.SUBMIT_STATE, Collections.singletonList(revision));
|
}
|
@After public void teardown() { if (users != null) users.close(); }
|
@Test public void incompatibleDmsLoadDoesNotCreatePrivateDraft() {
|
when(access.isDmsAudit(any())).thenReturn(true);
|
when(access.checkedBaseline(any(), eq("owner"))).thenReturn(new UploadEditAccess.CheckedBaseline(List.of(), false));
|
assertIncompatible(() -> service.load(UploadEditDraftService.context(draft)));
|
verifyNoInteractions(mapper);
|
}
|
@Test public void incompatibleDmsSaveIsRejectedEvenWithoutAnyDraft() {
|
when(access.isDmsAudit(any())).thenReturn(true);
|
when(access.hasEnabledFields("model")).thenReturn(false);
|
assertIncompatible(() -> service.prepareSave(model, field -> false));
|
verifyNoInteractions(mapper);
|
}
|
@Test public void privateDmsDraftCannotBeMutatedThroughOldCommands() {
|
when(access.isDmsAudit(any())).thenReturn(true);
|
when(mapper.find("tenant", "draft")).thenReturn(draft);
|
var command = new jnpf.base.model.uploadedit.UploadEditCommand();
|
command.setDraftId("draft"); command.setRevision(3L);
|
assertIncompatible(() -> service.begin(command));
|
assertIncompatible(() -> service.replace(command));
|
assertIncompatible(() -> service.discard(command));
|
verify(mapper, never()).compareAndSet(any(), anyLong());
|
}
|
@Test public void privateDmsLeaseCannotOpenOrSave() {
|
when(access.isDmsAudit(any())).thenReturn(true);
|
when(mapper.findLease("tenant", "lease")).thenReturn(draft);
|
assertIncompatible(() -> service.lease("tenant", "owner", "lease"));
|
var command = new jnpf.base.model.uploadedit.UploadEditSaveCommand();
|
command.setTenantId("tenant"); command.setOwnerId("owner"); command.setLeaseId("lease");
|
assertIncompatible(() -> service.save(command));
|
verify(mapper, never()).compareAndSet(any(), anyLong());
|
}
|
@Test public void privateDmsDraftCannotBeAdoptedEvenWithCurrentPermissions() {
|
var context = shared();
|
draft.setTaskId(null);
|
when(access.isDmsAudit(any())).thenReturn(true);
|
assertIncompatible(() -> service.prepareSave(model, field -> false, context));
|
assertEquals("[{\"fileId\":\"B\"}]", draft.getCurrentJson());
|
verify(mapper, never()).compareAndSet(any(), anyLong());
|
}
|
private static void assertIncompatible(Runnable action) {
|
try { action.run(); fail("incompatible DMS history must fail"); }
|
catch (DataException expected) { assertEquals(UploadEditAccess.DMS_INCOMPATIBLE, expected.getMessage()); }
|
}
|
@Test public void reviewPermissionCannotBeUpgradedByRequestedEditMode() {
|
org.springframework.data.redis.core.StringRedisTemplate redis = mock(org.springframework.data.redis.core.StringRedisTemplate.class);
|
service = new UploadEditDraftService(mapper, access, new UploadEditTemporaryService(access, mock(DmsAuditTemporaryEditService.class), redis), permissions);
|
shared(); draft.setLeaseId(null);
|
draft.setCurrentJson("[{\"fileId\":\"B\",\"name\":\"B.docx\"}]");
|
when(mapper.find("tenant", draft.getId())).thenReturn(draft);
|
when(access.baseline(any(), eq("owner"))).thenReturn(List.of(Map.of("fileId", "A")));
|
when(access.checkedBaseline(any(), eq("owner"))).thenReturn(new UploadEditAccess.CheckedBaseline(List.of(Map.of("fileId", "A")), true, "review"));
|
jnpf.base.model.uploadedit.UploadEditCommand command = new jnpf.base.model.uploadedit.UploadEditCommand();
|
command.setDraftId(draft.getId()); command.setRevision(3L); command.setFileId("B"); command.setMode("edit");
|
Map<String, Object> result = service.begin(command);
|
assertEquals("review", result.get("mode"));
|
assertTrue(draft.getLeaseId().startsWith("review_"));
|
when(mapper.findLease("tenant", draft.getLeaseId())).thenReturn(draft);
|
assertEquals("review", service.lease("tenant", "owner", draft.getLeaseId()).getMode());
|
assertTrue(service.canReadStatus("tenant", "owner", draft.getLeaseId()));
|
when(mapper.findLease("tenant", draft.getLeaseId())).thenReturn(null);
|
assertFalse(service.canReadStatus("tenant", "owner", draft.getLeaseId()));
|
try { service.lease("tenant", "owner", draft.getLeaseId()); fail("expired review lease"); }
|
catch (DataException expected) { assertEquals("编辑租约已失效", expected.getMessage()); }
|
verifyNoInteractions(redis);
|
}
|
@Test public void downgradedFullEditLeaseCannotOpenOrSave() {
|
when(mapper.findLease("tenant", "lease")).thenReturn(draft);
|
draft.setLeaseFileId("B");
|
when(access.baseline(any(), eq("owner"))).thenReturn(List.of(Map.of("fileId", "A")));
|
when(access.checkedBaseline(any(), eq("owner"))).thenReturn(new UploadEditAccess.CheckedBaseline(List.of(Map.of("fileId", "A")), true, "review"));
|
try { service.lease("tenant", "owner", "lease"); fail("downgraded lease"); } catch (DataException expected) { }
|
jnpf.base.model.uploadedit.UploadEditSaveCommand command = new jnpf.base.model.uploadedit.UploadEditSaveCommand();
|
command.setTenantId("tenant"); command.setOwnerId("owner"); command.setLeaseId("lease");
|
try { service.save(command); fail("downgraded callback"); } catch (DataException expected) { }
|
verify(mapper, never()).compareAndSet(any(), anyLong());
|
}
|
@Test public void loadedDraftReportsRestorePermissionInsteadOfOnlyContentDifference() {
|
when(access.checkedBaseline(any(), eq("owner"))).thenReturn(new UploadEditAccess.CheckedBaseline(Collections.singletonList(Collections.singletonMap("fileId", "A")), false));
|
when(mapper.find(anyString(), anyString())).thenReturn(draft);
|
when(permissions.canDiscardDraft(draft)).thenReturn(false, true);
|
assertEquals(false, service.load(UploadEditDraftService.context(draft)).get("canDiscard"));
|
assertEquals(true, service.load(UploadEditDraftService.context(draft)).get("canDiscard"));
|
assertTrue(draft.getCurrentJson().contains("B"));
|
}
|
@Test public void leaseRefreshesDownloadPermissionFromReleasedForm() {
|
when(mapper.findLease("tenant", "lease")).thenReturn(draft);
|
when(access.checkedBaseline(any(), eq("owner"))).thenReturn(new UploadEditAccess.CheckedBaseline(Collections.singletonList(Collections.singletonMap("fileId", "A")), false));
|
draft.setLeaseFileId("B");
|
when(access.allowDownload(any())).thenReturn(true, false);
|
assertTrue(service.lease("tenant", "owner", "lease").isAllowDownload());
|
assertFalse(service.lease("tenant", "owner", "lease").isAllowDownload());
|
verify(access, times(2)).allowDownload(argThat(context -> "model".equals(context.getModelId()) && "files".equals(context.getField())));
|
}
|
@Test public void controlledLeaseStillChecksPermissionAndBaselineBeforeReturningFile() {
|
when(mapper.findLease("tenant", "lease")).thenReturn(draft);
|
draft.setLeaseFileId("B"); draft.setTaskId("old-alias");
|
when(access.checkedBaseline(any(), eq("owner"))).thenAnswer(call -> {
|
jnpf.base.model.uploadedit.UploadEditContext context = call.getArgument(0);
|
context.setTaskId("actual-task");
|
return new UploadEditAccess.CheckedBaseline(Collections.singletonList(Collections.singletonMap("fileId", "A")), true);
|
});
|
assertEquals("B", service.lease("tenant", "owner", "lease").getFile().get("fileId"));
|
verify(access).checkedBaseline(any(), eq("owner"));
|
draft.setBaselineJson("[]");
|
try { service.lease("tenant", "owner", "lease"); fail("stale baseline"); } catch (DataException expected) { }
|
}
|
@Test public void statusChecksOnlyCurrentLeaseOwnershipWithoutLoadingForm() {
|
when(mapper.findLease("tenant", "lease")).thenReturn(draft);
|
assertTrue(service.canReadStatus("tenant", "owner", "lease"));
|
assertFalse(service.canReadStatus("tenant", "another-owner", "lease"));
|
assertFalse(service.canReadStatus("other-tenant", "owner", "lease"));
|
verifyNoInteractions(access);
|
verify(temporary, never()).find(any(), any(), any());
|
}
|
@Test public void replacedOrAdoptedLeaseCannotReadStatus() {
|
when(mapper.findLease("tenant", "lease")).thenReturn(draft);
|
assertTrue(service.canReadStatus("tenant", "owner", "lease"));
|
when(mapper.findLease("tenant", "lease")).thenReturn(null);
|
assertFalse(service.canReadStatus("tenant", "owner", "lease"));
|
verifyNoInteractions(access);
|
}
|
@Test public void temporaryStatusUsesOwnershipProbeRatherThanFileAuthorization() {
|
when(temporary.canReadStatus("tenant", "owner", "temporary")).thenReturn(true);
|
assertTrue(service.canReadStatus("tenant", "owner", "temporary"));
|
verifyNoInteractions(access);
|
verify(temporary, never()).find(any(), any(), any());
|
}
|
@Test public void statusAccessDoesNotBypassOpenOrSavePermissionChecks() {
|
when(mapper.findLease("tenant", "lease")).thenReturn(draft);
|
when(access.baseline(any(), eq("owner"))).thenThrow(new DataException("权限已撤销"));
|
when(access.checkedBaseline(any(), eq("owner"))).thenThrow(new DataException("权限已撤销"));
|
assertTrue(service.canReadStatus("tenant", "owner", "lease"));
|
try { service.lease("tenant", "owner", "lease"); fail("opening must recheck permissions"); }
|
catch (DataException expected) { assertEquals("权限已撤销", expected.getMessage()); }
|
jnpf.base.model.uploadedit.UploadEditSaveCommand command = new jnpf.base.model.uploadedit.UploadEditSaveCommand();
|
command.setTenantId("tenant"); command.setOwnerId("owner"); command.setLeaseId("lease");
|
try { service.save(command); fail("saving must recheck permissions"); }
|
catch (DataException expected) { assertEquals("权限已撤销", expected.getMessage()); }
|
verify(access).baseline(any(), eq("owner"));
|
verify(access).checkedBaseline(any(), eq("owner"));
|
verify(mapper, never()).compareAndSet(any(), anyLong());
|
}
|
@Test public void adoptsOnlyAfterNativeSaveAndStripsTransportMetadata() {
|
List<UploadEditDraft> prepared = service.prepareSave(model, field -> false);
|
assertFalse(model.getDataNewMap().containsKey(UploadEditDraftService.SUBMIT_STATE));
|
verify(mapper, never()).compareAndSet(any(), anyLong());
|
assertTrue(draft.getBaselineJson().contains("A"));
|
service.completeSave(prepared);
|
assertEquals(draft.getCurrentJson(), draft.getBaselineJson()); assertNull(draft.getLeaseId());
|
verify(mapper).compareAndSet(draft, 3L);
|
}
|
@Test(expected = DataException.class) public void missingVersionCannotSilentlyOverwriteDraft() {
|
model.getDataNewMap().remove(UploadEditDraftService.SUBMIT_STATE); service.prepareSave(model, field -> false);
|
}
|
@Test public void omittedFieldDoesNotAdoptDraft() {
|
model.getDataNewMap().remove("files"); assertTrue(service.prepareSave(model, field -> false).isEmpty());
|
}
|
@Test public void nativeDeniedFieldDoesNotAdvanceBaseline() {
|
assertTrue(service.prepareSave(model, field -> "files".equals(field)).isEmpty()); assertTrue(draft.getBaselineJson().contains("A"));
|
}
|
@Test public void deletingChildRowInvalidatesDraftOnSuccessfulAdoption() {
|
draft.setParentField("rows"); draft.setRowId("row"); model.getDataNewMap().put("rows", Collections.emptyList());
|
List<UploadEditDraft> prepared = service.prepareSave(model, field -> false); assertEquals(1, prepared.size());
|
service.completeSave(prepared); assertEquals("[]", draft.getBaselineJson()); assertNull(draft.getLeaseId());
|
}
|
@Test public void partialChildUpdateDoesNotDeleteMissingRowDraft() {
|
draft.setParentField("rows"); draft.setRowId("row"); model.getDataNewMap().put("rows", Collections.emptyList());
|
model.setOnlyUpdate(true); assertTrue(service.prepareSave(model, field -> false).isEmpty()); assertEquals("lease", draft.getLeaseId());
|
}
|
@Test public void controlledNativeSaveMaySupersedeOldDraftButDoesNotBecomeTrustedProof() {
|
model.getDataNewMap().remove(UploadEditDraftService.SUBMIT_STATE);
|
model.getDataNewMap().put("files", Collections.emptyList());
|
jnpf.flowable.model.filepermission.FilePermissionView view = new jnpf.flowable.model.filepermission.FilePermissionView();
|
UploadFilePermissionService.SaveContext permissions = new UploadFilePermissionService.SaveContext(Collections.emptyMap(), view);
|
List<UploadEditDraft> prepared = service.prepareSave(model, field -> false, permissions);
|
assertEquals(1, prepared.size()); assertTrue(permissions.nativeDraftIds.contains(draft.getId()));
|
assertEquals("[]", draft.getCurrentJson()); assertNull(draft.getEditOriginsJson());
|
verify(mapper, never()).compareAndSet(any(), anyLong());
|
}
|
@Test public void controlledSaveStillRequiresVersionToAdoptAnExistingEditedDraft() {
|
model.getDataNewMap().remove(UploadEditDraftService.SUBMIT_STATE);
|
UploadFilePermissionService.SaveContext permissions = new UploadFilePermissionService.SaveContext(Collections.emptyMap(),
|
new jnpf.flowable.model.filepermission.FilePermissionView());
|
try { service.prepareSave(model, field -> false, permissions); fail("missing revision"); }
|
catch (DataException expected) { assertTrue(expected.getMessage().contains("版本缺失")); }
|
}
|
|
private UploadFilePermissionService.SaveContext shared() {
|
draft.setTaskId("task"); draft.setLeaseId(null);
|
draft.setId(jnpf.base.util.UploadEditWorkingCopy.id("tenant", UploadEditDraftService.context(draft)));
|
draft.setEditOriginsJson("{\"B\":\"A\"}");
|
model.getDataNewMap().put(UploadEditDraftService.SUBMIT_STATE,
|
Collections.singletonList(Map.of("draftId", draft.getId(), "revision", 3L)));
|
jnpf.flowable.model.filepermission.FilePermissionView view = jnpf.flowable.model.filepermission.FilePermissionView.legacy("task", "model");
|
view.setEnabled(true); view.setRevision("r1");
|
jnpf.flowable.model.filepermission.FilePermissionView.FieldAccess field = new jnpf.flowable.model.filepermission.FilePermissionView.FieldAccess();
|
field.setStatus("ready"); field.setActions(Map.of(jnpf.flowable.model.filepermission.FilePermissionPolicy.Action.VIEW, true));
|
view.setFields(Map.of("files", field));
|
return new UploadFilePermissionService.SaveContext(new HashMap<>(Map.of("files", List.of(Map.of("fileId", "A")))), view);
|
}
|
@Test public void viewerAdoptsAnotherEditorsPureChangeInReadonlyField() {
|
UploadFilePermissionService.SaveContext context = shared(); draft.setOwnerId("reviewer");
|
model.getDataNewMap().put("files", List.of(Map.of("fileId", "forged")));
|
assertEquals(1, service.prepareSave(model, field -> true, context).size());
|
assertTrue(model.getWorkflowWorkingFields().contains("files"));
|
assertEquals("B", ((Map<?, ?>)((List<?>)model.getDataNewMap().get("files")).get(0)).get("fileId"));
|
assertTrue(context.sharedDraftIds.contains(draft.getId()));
|
assertTrue(draft.getBaselineJson().contains("A"));
|
service.completeSave(List.of(draft));
|
assertTrue(draft.getBaselineJson().contains("B"));
|
}
|
@Test public void readonlyAdoptionRejectsUploadReplacementDisguisedAsEdit() {
|
UploadFilePermissionService.SaveContext context = shared(); draft.setEditOriginsJson(null);
|
try { service.prepareSave(model, field -> true, context); fail("replacement"); } catch (DataException expected) { }
|
assertTrue(model.getWorkflowWorkingFields().isEmpty());
|
}
|
@Test public void sharedAdoptionRejectsStaleRevisionAndActiveLease() {
|
UploadFilePermissionService.SaveContext context = shared(); draft.setRevision(4L);
|
try { service.prepareSave(model, field -> true, context); fail("stale"); } catch (DataException expected) { }
|
context = shared(); draft.setLeaseId("active"); draft.setUpdatedAt(new Date());
|
try { service.prepareSave(model, field -> true, context); fail("active lease"); } catch (DataException expected) { }
|
}
|
@Test public void sharedAdoptionRequiresVersionExceptForServerNodeCheckpoint() {
|
UploadFilePermissionService.SaveContext context = shared(); model.getDataNewMap().remove(UploadEditDraftService.SUBMIT_STATE);
|
try { service.prepareSave(model, field -> true, context); fail("missing version"); } catch (DataException expected) { }
|
model.setWorkflowFileCheckpoint(true);
|
assertEquals(1, service.prepareSave(model, field -> true, context).size());
|
}
|
@Test public void sharedReadDoesNotRequireEditorOwnership() {
|
shared(); draft.setOwnerId("reviewer");
|
when(access.sharedBaseline(any(), eq("owner"))).thenReturn(new UploadEditAccess.CheckedBaseline(List.of(Map.of("fileId", "A")), true));
|
when(mapper.find("tenant", draft.getId())).thenReturn(draft);
|
assertEquals(true, service.loadShared(UploadEditDraftService.context(draft)).get("shared"));
|
verify(access, never()).checkedBaseline(any(), anyString());
|
}
|
@Test public void sharedLeaseCannotBeTakenOverUntilClosedOrExpired() {
|
when(access.checkedBaseline(any(), eq("owner"))).thenReturn(new UploadEditAccess.CheckedBaseline(List.of(Map.of("fileId", "A")), true));
|
shared(); draft.setCurrentJson("[{\"fileId\":\"B\",\"name\":\"B.docx\"}]");
|
draft.setOwnerId("reviewer"); draft.setLeaseId("active"); draft.setUpdatedAt(new Date());
|
when(mapper.find("tenant", draft.getId())).thenReturn(draft);
|
when(access.baseline(any(), eq("owner"))).thenReturn(List.of(Map.of("fileId", "A")));
|
jnpf.base.model.uploadedit.UploadEditCommand command = new jnpf.base.model.uploadedit.UploadEditCommand();
|
command.setDraftId(draft.getId()); command.setRevision(3L); command.setFileId("B");
|
try { service.begin(command); fail("active lease"); } catch (DataException expected) { }
|
draft.setUpdatedAt(new Date(System.currentTimeMillis() - 121_000));
|
service.begin(command); assertEquals("owner", draft.getOwnerId()); assertFalse("active".equals(draft.getLeaseId()));
|
command.setFileId(draft.getLeaseId()); service.close(command); assertNull(draft.getLeaseId());
|
}
|
@Test public void expiredLeaseCannotSaveOrRenewAndForeignUserCannotClose() {
|
shared(); draft.setLeaseId("active"); draft.setUpdatedAt(new Date(System.currentTimeMillis() - 121_000));
|
when(mapper.find("tenant", draft.getId())).thenReturn(draft); when(mapper.findLease("tenant", "active")).thenReturn(draft);
|
jnpf.base.model.uploadedit.UploadEditCommand command = new jnpf.base.model.uploadedit.UploadEditCommand();
|
command.setDraftId(draft.getId()); command.setFileId("active");
|
try { service.heartbeat(command); fail("expired"); } catch (DataException expected) { }
|
try { service.lease("tenant", "owner", "active"); fail("expired"); } catch (DataException expected) { }
|
draft.setOwnerId("other");
|
try { service.close(command); fail("foreign"); } catch (DataException expected) { }
|
verify(mapper, never()).touchLease(any(), any(), any(), any(), any(), any());
|
}
|
|
@Test public void sharedChildAdoptionUsesEachRowsServerValueWithoutGrantingOtherRowWrites() {
|
UploadFilePermissionService.SaveContext initial = shared();
|
draft.setParentField("rows"); draft.setRowId("one"); draft.setId(jnpf.base.util.UploadEditWorkingCopy.id("tenant", UploadEditDraftService.context(draft)));
|
UploadEditDraft second = new UploadEditDraft(); second.setTenantId("tenant"); second.setTaskId("task"); second.setModelId("model"); second.setRecordId("record");
|
second.setParentField("rows"); second.setRowId("two"); second.setField("files"); second.setOwnerId("reviewer"); second.setRevision(3L);
|
second.setBaselineJson("[{\"fileId\":\"C\"}]"); second.setCurrentJson("[{\"fileId\":\"D\"}]"); second.setEditOriginsJson("{\"D\":\"C\"}");
|
second.setId(jnpf.base.util.UploadEditWorkingCopy.id("tenant",UploadEditDraftService.context(second)));
|
when(mapper.lockRecord("tenant","model","record")).thenReturn(List.of(draft,second));
|
initial.permissions.setFields(Map.of("rows-files",initial.permissions.getFields().get("files")));
|
Map<String,Object> one = new HashMap<>(Map.of("id","one","files",List.of(Map.of("fileId","A")),"note","keep"));
|
Map<String,Object> two = new HashMap<>(Map.of("id","two","files",List.of(Map.of("fileId","C"))));
|
Map<String,Object> baseline = Map.of("rows", List.of(new HashMap<>(one),new HashMap<>(two)));
|
model.getDataNewMap().put("rows",List.of(one,two));
|
model.getDataNewMap().put(UploadEditDraftService.SUBMIT_STATE,List.of(Map.of("draftId",draft.getId(),"revision",3L),Map.of("draftId",second.getId(),"revision",3L)));
|
assertEquals(2,service.prepareSave(model,key -> !model.getWorkflowWorkingFields().contains(key),new UploadFilePermissionService.SaveContext(baseline,initial.permissions)).size());
|
assertEquals(List.of(Map.of("fileId","B")),one.get("files")); assertEquals(List.of(Map.of("fileId","D")),two.get("files"));
|
assertEquals("keep",one.get("note"));
|
assertEquals(1,model.getWorkflowWorkingFields().size());
|
}
|
|
@Test public void nativeMainSaveKeepsPersistedSourceButClearsOrdinaryReplacement() {
|
when(access.isDmsAuditTemporary(any())).thenReturn(true);
|
when(mapper.lockRecord("tenant", "model", "record")).thenReturn(List.of());
|
when(permissions.dmsMainAttachmentBaseline(model)).thenReturn(List.of(Map.of("fileId", "A", "laiyuan_muban_id", "templateA")));
|
model.getDataNewMap().put("wenjian", "[{\"fileId\":\"A\",\"laiyuan_muban_id\":\"forged\"}]");
|
service.prepareSave(model, field -> false);
|
assertTrue(model.getDataNewMap().get("wenjian").toString().contains("templateA"));
|
when(access.hasEnabledFields("model")).thenReturn(false);
|
service.prepareSave(model, field -> false);
|
assertTrue(model.getDataNewMap().get("wenjian").toString().contains("templateA"));
|
model.getDataNewMap().put("wenjian", List.of(Map.of("fileId", "new", "laiyuan_muban_id", "templateA")));
|
service.prepareSave(model, field -> false);
|
assertFalse(((Map<?,?>)((List<?>)model.getDataNewMap().get("wenjian")).get(0)).containsKey("laiyuan_muban_id"));
|
}
|
@Test public void nativeMainSaveAcceptsNewTemplateForSameFileId() {
|
when(access.isDmsAuditTemporary(any())).thenReturn(true);
|
when(mapper.lockRecord("tenant", "model", "record")).thenReturn(List.of());
|
when(permissions.dmsMainAttachmentBaseline(model)).thenReturn(List.of(Map.of("fileId", "A", "laiyuan_muban_id", "templateA")));
|
try(var tickets=mockStatic(jnpf.util.TicketUtil.class)) {
|
tickets.when(() -> jnpf.util.TicketUtil.parseTicket("new")).thenReturn(jnpf.util.JsonUtil.getObjectToString(Map.of(
|
"purpose", "upload-edit-source", "tenantId", "tenant", "ownerId", "owner", "fileId", "A", "laiyuan_muban_id", "templateB")));
|
model.getDataNewMap().put("wenjian", List.of(Map.of("fileId", "A", "uploadReceipt", "new")));
|
service.prepareSave(model, field -> false);
|
assertEquals("templateB", ((Map<?,?>)((List<?>)model.getDataNewMap().get("wenjian")).get(0)).get("laiyuan_muban_id"));
|
}
|
}
|
|
|
@Test public void dmsLoadWithoutDraftIsReadOnlyAndReturnsContentIdentity() {
|
shared();
|
when(access.sharedBaseline(any(), eq("owner"))).thenReturn(new UploadEditAccess.CheckedBaseline(List.of(Map.of("fileId", "A")), true, "edit", true));
|
Map<String,Object> view = service.loadShared(UploadEditDraftService.context(draft));
|
assertEquals(true, view.get("dmsAudit"));
|
verify(access, never()).isDmsAudit(any());
|
assertEquals(jnpf.base.util.UploadEditStateMachine.contentToken(draft.getBaselineJson()), view.get("contentToken"));
|
verify(mapper, never()).insert(any()); verify(mapper, never()).compareAndSet(any(), anyLong());
|
}
|
@Test public void dmsStaleLoadDoesNotResetSavedDraft() {
|
shared();
|
when(mapper.find(anyString(), anyString())).thenReturn(draft);
|
when(access.sharedBaseline(any(), eq("owner"))).thenReturn(new UploadEditAccess.CheckedBaseline(List.of(Map.of("fileId", "C")), true, "edit", true));
|
Map<String,Object> view = service.loadShared(UploadEditDraftService.context(draft));
|
assertEquals(true, view.get("dmsAudit"));
|
verify(access, never()).isDmsAudit(any());
|
assertEquals(List.of(Map.of("fileId", "C")), view.get("files"));
|
assertTrue(draft.getCurrentJson().contains("B"));
|
verify(mapper, never()).insert(any()); verify(mapper, never()).compareAndSet(any(), anyLong());
|
}
|
@Test public void dmsContentConfirmationSurvivesLeaseRevisionButRejectsChangedContent() {
|
UploadFilePermissionService.SaveContext context = shared(); when(access.isDmsAudit(any())).thenReturn(true);
|
String token = jnpf.base.util.UploadEditStateMachine.contentToken(draft.getCurrentJson());
|
model.getDataNewMap().put(UploadEditDraftService.SUBMIT_STATE, List.of(Map.of("draftId", draft.getId(), "revision", 1L, "contentToken", token)));
|
assertEquals(1, service.prepareSave(model, field -> true, context).size());
|
model.getDataNewMap().put(UploadEditDraftService.SUBMIT_STATE, List.of(Map.of("draftId", draft.getId(), "revision", 3L, "contentToken", "old")));
|
try { service.prepareSave(model, field -> true, context); fail("changed content"); }
|
catch (DataException expected) { assertTrue(expected.getMessage().contains("重新预览")); }
|
}
|
@Test public void dmsCheckpointCannotAdoptUnconfirmedSavedContent() {
|
UploadFilePermissionService.SaveContext context = shared(); when(access.isDmsAudit(any())).thenReturn(true);
|
model.setWorkflowFileCheckpoint(true); model.getDataNewMap().remove(UploadEditDraftService.SUBMIT_STATE);
|
try { service.prepareSave(model, field -> true, context); fail("unconfirmed"); }
|
catch (DataException expected) { assertTrue(expected.getMessage().contains("尚未确认")); }
|
}
|
@Test public void dmsBeginLocksExistingParentBeforeCreatingFirstDraftAndCloseKeepsContent() {
|
shared(); when(access.isDmsAudit(any())).thenReturn(true);
|
when(access.dmsAuditTable("model")).thenReturn("dms_wenjian_xiuding_shenhe");
|
when(mapper.lockDmsRecord("tenant", "dms_wenjian_xiuding_shenhe", "record")).thenReturn("record");
|
var baseline = new UploadEditAccess.CheckedBaseline(List.of(Map.of("fileId", "A", "name", "A.docx")), true, "edit", true);
|
when(access.sharedBaseline(any(), eq("owner"))).thenReturn(baseline);
|
when(access.checkedBaseline(any(), eq("owner"))).thenReturn(baseline);
|
when(access.baseline(any(), eq("owner"))).thenReturn(baseline.getFiles());
|
java.util.concurrent.atomic.AtomicReference<UploadEditDraft> stored = new java.util.concurrent.atomic.AtomicReference<>();
|
when(mapper.find(anyString(), anyString())).thenAnswer(call -> stored.get());
|
when(mapper.insert(any())).thenAnswer(call -> { stored.set(call.getArgument(0)); return 1; });
|
var command = new jnpf.base.model.uploadedit.UploadEditCommand();
|
command.setContext(UploadEditDraftService.context(draft)); command.setDraftId(draft.getId()); command.setFileId("A");
|
command.setContentToken(jnpf.base.util.UploadEditStateMachine.contentToken(baseline.getFiles()));
|
var lease = service.begin(command);
|
var order = inOrder(mapper); order.verify(mapper).lockDmsRecord("tenant", "dms_wenjian_xiuding_shenhe", "record");
|
order.verify(mapper).insert(any());
|
try { service.begin(command); fail("concurrent lease"); } catch (DataException expected) { assertTrue(expected.getMessage().contains("正在编辑")); }
|
var close = new jnpf.base.model.uploadedit.UploadEditCommand(); close.setDraftId(draft.getId()); close.setFileId((String)lease.get("leaseId"));
|
service.close(close);
|
assertEquals(lease.get("contentToken"), service.loadShared(command.getContext()).get("contentToken"));
|
}
|
@Test public void dmsMetadataChangesAreRejectedEvenWhenContentTokenMatches() {
|
var context = shared(); when(access.isDmsAudit(any())).thenReturn(true);
|
model.getDataNewMap().put(UploadEditDraftService.SUBMIT_STATE, List.of(Map.of("draftId", draft.getId(), "revision", 3L,
|
"contentToken", jnpf.base.util.UploadEditStateMachine.contentToken(draft.getCurrentJson()), "metadataToken", "stale")));
|
try { service.prepareSave(model, f -> false, context); fail("metadata conflict must reject"); }
|
catch (DataException expected) { assertTrue(expected.getMessage().contains("模板来源")); }
|
}
|
@Test public void callbackRechecksLeaseAfterAcquiringNativeParentLock() {
|
shared(); draft.setLeaseId("lease"); draft.setUpdatedAt(new Date());
|
when(access.isDmsAudit(any())).thenReturn(true);
|
when(access.dmsAuditTable("model")).thenReturn("dms_wenjian_xinzeng_shenhe");
|
when(mapper.lockDmsRecord("tenant", "dms_wenjian_xinzeng_shenhe", "record")).thenReturn("record");
|
when(mapper.findLease("tenant", "lease")).thenReturn(draft, null);
|
var command = new jnpf.base.model.uploadedit.UploadEditSaveCommand();
|
command.setTenantId("tenant"); command.setOwnerId("owner"); command.setLeaseId("lease");
|
assertFalse(service.save(command));
|
var order = inOrder(mapper);
|
order.verify(mapper).findLease("tenant", "lease");
|
order.verify(mapper).lockDmsRecord("tenant", "dms_wenjian_xinzeng_shenhe", "record");
|
order.verify(mapper).findLease("tenant", "lease");
|
verify(mapper, never()).compareAndSet(any(), anyLong());
|
}
|
}
|