刘光辉
5 小时以前 06f314545ac3514d7ce28939920866ae8188d31c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
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());
    }
 
}