package jnpf.onlinedev.service; import jnpf.base.UserInfo; import jnpf.base.entity.VisualdevEntity; import jnpf.base.mapper.UploadEditDraftMapper; import jnpf.base.model.flow.DataModel; import jnpf.base.model.uploadedit.UploadEditDraft; import jnpf.base.service.*; import jnpf.base.util.*; import jnpf.exception.DataException; import jnpf.exception.WorkFlowException; import jnpf.flowable.model.filepermission.FilePermissionView; import jnpf.onlinedev.util.FlowFormCustomUtils; import jnpf.util.UserProvider; import org.junit.jupiter.api.*; import org.mockito.MockedStatic; import java.util.*; import java.util.function.Consumer; import static org.mockito.Mockito.*; import static org.mockito.ArgumentMatchers.*; import static org.junit.jupiter.api.Assertions.*; public class WorkflowFileCheckpointServiceTest { private VisualdevService models; private FlowFormDataUtil data; private FlowFormCustomUtils nativeForms; private UploadEditDraftMapper drafts; private UploadFilePermissionQuery permissions; private WorkflowFileCheckpointService service; private UploadEditDraft draft; private MockedStatic users; private Map persisted; @BeforeEach public void setup() { jnpf.util.context.SpringContext.setApplicationContext(mock(org.springframework.context.ApplicationContext.class)); users = mockStatic(UserProvider.class); UserInfo user = new UserInfo(); user.setTenantId("0"); user.setUserId("reviewer"); users.when(UserProvider::getUser).thenReturn(user); models = mock(VisualdevService.class); data = mock(FlowFormDataUtil.class); nativeForms = mock(FlowFormCustomUtils.class); drafts = mock(UploadEditDraftMapper.class); permissions = mock(UploadFilePermissionQuery.class); service = new WorkflowFileCheckpointService(models,data,nativeForms,drafts,permissions); FilePermissionView view = FilePermissionView.legacy("task", "form"); view.setEnabled(true); when(permissions.current("task", "form")).thenReturn(view); when(models.getReleaseInfo("form")).thenReturn(new VisualdevEntity()); persisted = new HashMap<>(Map.of("id", "record", "flowTaskId", "task", "files", List.of(Map.of("fileId", "A")), "title", "persisted")); when(data.getFilePermissionDataInfo(any(), eq("task"), any())).thenReturn(persisted); draft = new UploadEditDraft(); draft.setTenantId("0"); draft.setModelId("form"); draft.setRecordId("record"); draft.setTaskId("task"); draft.setField("files"); draft.setBaselineJson("[{\"fileId\":\"A\"}]"); draft.setCurrentJson("[{\"fileId\":\"B\"}]"); draft.setId(UploadEditWorkingCopy.id("0", UploadEditDraftService.context(draft))); when(drafts.lockRecord("0", "form", "record")).thenReturn(List.of(draft)); } @AfterEach public void close() { users.close(); } @Test public void checkpointsOnlyUsingPersistedDataAndContinuesAfterNativeSave() throws Exception { when(nativeForms.checkpointWorkflowFiles(any(), any())).thenAnswer(call -> { jnpf.base.model.flow.FlowFormDataModel model = call.getArgument(1); assertEquals("persisted", model.getMap().get("title")); assertEquals("record",model.getId()); assertTrue(model.getSkipRequiredValidation()); model.getMap().put("files", List.of(Map.of("fileId", "B"))); DataModel saved = new DataModel(); saved.getWorkflowWorkingFields().add("files"); return saved; }); List> output = new ArrayList<>(); service.save("task", "form", Map.of("title", "unsaved opinion"), output::add); assertEquals(1,output.size()); assertEquals("unsaved opinion",output.get(0).get("title")); assertEquals(List.of(Map.of("fileId", "B")),output.get(0).get("files")); } @Test public void nativeFailurePreventsNodeContinuation() throws Exception { when(nativeForms.checkpointWorkflowFiles(any(),any())).thenThrow(new WorkFlowException("native failed")); Consumer> continuation = mock(Consumer.class); try { service.save("task","form",Map.of(),continuation); fail("must reject"); } catch (DataException expected) { } verifyNoInteractions(continuation); } @Test public void openLeasePreventsNativeSaveAndContinuation() { draft.setLeaseId("active"); draft.setUpdatedAt(new Date()); Consumer> continuation = mock(Consumer.class); try { service.save("task","form",Map.of(),continuation); fail("must reject"); } catch (DataException expected) { } verifyNoInteractions(nativeForms, continuation); } @Test public void unchangedWorkingFileDoesNotWriteNativeForm() { draft.setCurrentJson(draft.getBaselineJson()); List> output = new ArrayList<>(); service.save("task","form",Map.of("opinion","keep"),output::add); assertEquals("keep",output.get(0).get("opinion")); verifyNoInteractions(nativeForms); } @Test public void mismatchedInstanceCannotCheckpoint() { persisted.put("flowTaskId", "other"); Consumer> continuation = mock(Consumer.class); try { service.save("task","form",Map.of(),continuation); fail("instance"); } catch (DataException expected) { } verifyNoInteractions(nativeForms, continuation, drafts); } @Test public void checkpointPassesHandlingReferenceToNativeSave() throws Exception { when(nativeForms.checkpointWorkflowFiles(any(), any())).thenAnswer(call -> { jnpf.base.model.flow.FlowFormDataModel model = call.getArgument(1); assertEquals("operator-round-2", model.getWorkflowOperatorId()); return new DataModel(); }); service.save("task", "form", "operator-round-2", Map.of(), saved -> { }); verify(nativeForms, times(1)).checkpointWorkflowFiles(any(), any()); } }