package jnpf.flowable.service.dev;
|
|
import jnpf.base.UserInfo;
|
import jnpf.config.ConfigValueUtil;
|
import jnpf.exception.WorkFlowException;
|
import jnpf.util.UserProvider;
|
import jnpf.database.util.TenantDataSourceUtil;
|
import org.junit.*;
|
import org.mockito.MockedStatic;
|
import java.util.*;
|
import java.util.concurrent.Executor;
|
import static org.junit.Assert.*;
|
import static org.mockito.Mockito.*;
|
import static org.mockito.ArgumentMatchers.*;
|
|
public class DevApprovalRunsTest {
|
private final DevApprovalService service = mock(DevApprovalService.class);
|
private final DevApprovalSignatures signatures = mock(DevApprovalSignatures.class);
|
private final DevApprovalRuns runs = new DevApprovalRuns(service, signatures, mock(ConfigValueUtil.class));
|
private final List<Runnable> jobs = new ArrayList<>();
|
private MockedStatic<UserProvider> users;
|
private MockedStatic<TenantDataSourceUtil> tenants;
|
private UserInfo user;
|
|
@Before public void setup() throws Exception {
|
jnpf.util.context.SpringContext.setApplicationContext(mock(org.springframework.context.ApplicationContext.class));
|
users = mockStatic(UserProvider.class); tenants = mockStatic(TenantDataSourceUtil.class);
|
user = new UserInfo(); user.setUserId("admin"); user.setTenantId("tenant"); user.setToken("session"); user.setIsAdministrator(true);
|
users.when(UserProvider::getUser).thenReturn(user);
|
users.when(() -> UserProvider.isValidToken("session")).thenReturn(true);
|
when(service.allowed()).thenReturn(true);
|
when(service.inspect("task", null)).thenReturn(state("READY", null));
|
var field = DevApprovalRuns.class.getDeclaredField("executor"); field.setAccessible(true);
|
field.set(runs, (Executor) jobs::add);
|
}
|
private interface Checked { void run() throws Exception; }
|
private void rejected(Checked action) throws Exception {
|
try { action.run(); fail("Must reject"); } catch (WorkFlowException expected) { }
|
}
|
@After public void cleanup() { users.close(); tenants.close(); }
|
private DevApprovalService.State state(String status, String operator) {
|
var s = new DevApprovalService.State(); s.setEnabled(true); s.setStatus(status); s.setOperatorId(operator); return s;
|
}
|
@Test public void startReturnsBeforeExecutionAndRepeatedStartIsIdempotent() throws Exception {
|
var first = runs.start("task", "sign");
|
assertEquals("RUNNING", first.status()); assertEquals(1, jobs.size());
|
assertEquals(first.runId(), runs.start("task", "sign").runId());
|
verify(service, never()).approve(anyString(), any(), anyString());
|
when(service.approve("task", null, "sign")).thenReturn(state("ADVANCED", "op1"), state("PASSED", "op2"));
|
jobs.getFirst().run();
|
assertEquals("PASSED", runs.status("task").status()); assertEquals(2, runs.status("task").completed());
|
assertEquals(first.runId(), runs.start("task", "sign").runId()); assertEquals(1, jobs.size());
|
verify(service, times(2)).approve("task", null, "sign");
|
users.verify(UserProvider::clearLocalUser);
|
}
|
@Test public void disabledOrNonAdminCannotStartReadOrStop() throws Exception {
|
when(service.allowed()).thenReturn(false);
|
rejected(() -> runs.start("task", "sign"));
|
rejected(() -> runs.status("task"));
|
rejected(() -> runs.stop("task", "id"));
|
assertTrue(jobs.isEmpty()); verifyNoInteractions(signatures);
|
}
|
@Test public void invalidSignatureNeverSchedules() throws Exception {
|
when(signatures.require("task", "bad")).thenThrow(new WorkFlowException("签名无效"));
|
rejected(() -> runs.start("task", "bad")); assertTrue(jobs.isEmpty());
|
}
|
@Test public void ownerAndTenantIsolationAndStaleStop() throws Exception {
|
var first = runs.start("task", "sign");
|
user.setUserId("other");
|
rejected(() -> runs.status("task"));
|
rejected(() -> runs.start("task", "sign"));
|
rejected(() -> runs.stop("task", first.runId()));
|
user.setTenantId("another"); assertEquals("IDLE", runs.status("task").status());
|
user.setTenantId("tenant"); user.setUserId("admin");
|
rejected(() -> runs.stop("task", "stale"));
|
}
|
@Test public void stopCompletesCurrentStepAndNeverAdvancesNext() throws Exception {
|
var first = runs.start("task", "sign");
|
when(service.approve("task", null, "sign")).thenAnswer(call -> {
|
runs.stop("task", first.runId()); return state("ADVANCED", "op1");
|
});
|
jobs.getFirst().run();
|
assertEquals("STOPPED", runs.status("task").status()); assertEquals(1, runs.status("task").completed());
|
verify(service, times(1)).approve("task", null, "sign");
|
}
|
@Test public void expiryAndBusinessFailureStopWithoutRetry() throws Exception {
|
runs.start("task", "sign"); users.when(() -> UserProvider.isValidToken("session")).thenReturn(false);
|
jobs.getFirst().run(); assertEquals("FAILED", runs.status("task").status());
|
verify(service, never()).approve(anyString(), any(), anyString());
|
users.when(() -> UserProvider.isValidToken("session")).thenReturn(true);
|
when(service.approve("task", null, "new-sign")).thenThrow(new WorkFlowException("必填数据不完整"));
|
runs.start("task", "new-sign"); jobs.get(1).run();
|
assertEquals("FAILED", runs.status("task").status());
|
assertEquals("必填数据不完整", runs.status("task").message());
|
verify(service, times(1)).approve("task", null, "new-sign");
|
}
|
@Test public void longFlowsStopAtTheServerLimitAndReleaseCapacity() throws Exception {
|
var sequence = new java.util.concurrent.atomic.AtomicInteger();
|
when(service.approve("task", null, "sign")).thenAnswer(call -> state("ADVANCED", "op" + sequence.incrementAndGet()));
|
runs.start("task", "sign");
|
jobs.getFirst().run();
|
assertEquals("STOPPED", runs.status("task").status());
|
assertEquals(100, runs.status("task").completed());
|
verify(service, times(100)).approve("task", null, "sign");
|
assertEquals("RUNNING", runs.start("task", "new-sign").status());
|
assertEquals(2, jobs.size());
|
}
|
|
}
|