刘光辉
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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
package jnpf.dmsService.expiry;
 
import jnpf.dmsEntity.storage.expiry.*;
import jnpf.dmsMapper.*;
import jnpf.dmsService.DmsFileLifecycleService;
import jnpf.dmsService.DmsFileValidityProjection;
import jnpf.dmsService.search.DmsSearchProperties;
import jnpf.dmsService.search.meili.DmsMeilisearchClient;
import jnpf.exception.DataException;
import org.junit.jupiter.api.Test;
import org.mockito.InOrder;
import java.util.*;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
 
class DmsFileExpiryTest {
    private final DmsFileExpiryMapper mapper = mock(DmsFileExpiryMapper.class);
    private final DmsFileExpiryProjectionMapper resources = mock(DmsFileExpiryProjectionMapper.class);
    private final DmsFileExpiryProjection projection = new DmsFileExpiryProjection(resources, mapper);
    private final UUID version = UUID.randomUUID();
    private final Date end = new Date(1700000000000L);
 
    @Test void expiresBusinessVersionResourceAndRelationsButRetainsPointer() {
        DmsFileExpiryResource resource = resource("effective", null);
        when(resources.lockResources("t", version)).thenReturn(List.of(resource));
        when(resources.disable("t", resource, end, "actor")).thenReturn(1);
        when(resources.expireBusinessVersion("t", resource, "actor")).thenReturn(1);
        projection.apply("t", version, end, "actor");
        InOrder ordered = inOrder(resources, mapper);
        ordered.verify(resources).hasPendingProjection("t", version);
        ordered.verify(resources).lockResources("t", version);
        ordered.verify(resources).disable("t", resource, end, "actor");
        ordered.verify(resources).expireBusinessVersion("t", resource, "actor");
        ordered.verify(resources).closeRelations("t", "file", end, "actor");
        ordered.verify(mapper).enqueueCleanup("t", version);
        assertEquals("business-version", resource.getCurrentVersionId());
    }
 
    @Test void repairsExpiredProjectionAndEnqueuesCenterOnlyVersions() {
        when(resources.lockResources("t", version)).thenReturn(List.of());
        projection.apply("t", version, end, "actor");
        verify(mapper).enqueueCleanup("t", version);
        verify(resources, never()).disable(any(), any(), any(), any());
    }
 
    @Test void refusesToOverwriteOtherDisableReasonsOrAmbiguousMappings() {
        when(resources.lockResources("t", version)).thenReturn(List.of(resource("disabled", "yichexiao")));
        assertThrows(DataException.class, () -> projection.apply("t", version, end, "actor"));
        when(resources.lockResources("t", version)).thenReturn(List.of(resource("effective", null), resource("effective", null)));
        assertThrows(DataException.class, () -> projection.apply("t", version, end, "actor"));
        verifyNoInteractions(mapper);
    }
 
    @Test void defersExpiryUntilApprovedBusinessProjectionFinishes() {
        when(resources.hasPendingProjection("t", version)).thenReturn(true);
        assertThrows(DataException.class, () -> projection.apply("t", version, end, "actor"));
        verify(resources, never()).lockResources(any(), any());
        verifyNoInteractions(mapper);
    }
 
    @Test void validityProjectionRejectsDisabledBusinessResources() {
        when(resources.lockResources("t", version)).thenReturn(List.of(resource("disabled", "yidaoqi")));
        assertThrows(DataException.class, () -> new DmsFileValidityProjection(resources).apply("t", version, end, "actor"));
        verify(resources, never()).extendValidity(any(), any(), any(), any());
    }
 
    @Test void executorSkipsLockedDocumentsAndCandidatesExtendedAfterDiscovery() {
        DmsFileLifecycleService lifecycle = mock(DmsFileLifecycleService.class);
        DmsFileExpiryExecutor executor = new DmsFileExpiryExecutor(mapper, lifecycle, new jnpf.dmsService.DmsBusinessLifecycleGuard(null, false));
        DmsFileExpiryItem item = item();
        assertFalse(executor.process(item));
        when(mapper.tryLockDocument("t", item.getDocumentId())).thenReturn(item.getDocumentId());
        assertFalse(executor.process(item));
        verifyNoInteractions(lifecycle);
        when(mapper.dueState("t", version)).thenReturn(item);
        assertTrue(executor.process(item));
        verify(lifecycle).updateExpired(argThat(form -> version.equals(form.getFileVersionId())), eq("dms-expiry-job"));
    }
 
    @Test void occupiedFileSkipsBeforeDocumentLockAndDoesNotReportApplied() {
        DmsFileLifecycleService lifecycle = mock(DmsFileLifecycleService.class);
        jnpf.dmsService.DmsBusinessLifecycleGuard guard = mock(jnpf.dmsService.DmsBusinessLifecycleGuard.class);
        DmsFileExpiryExecutor executor = new DmsFileExpiryExecutor(mapper, lifecycle, guard);
        assertFalse(executor.process(item()));
        verifyNoInteractions(mapper, lifecycle);
    }
 
    @Test void defaultJobProcessesDueVersionsAndContinuesAfterFailure() {
        DmsFileExpiryProperties properties = new DmsFileExpiryProperties();
        properties.setBatchSize(1);
        DmsFileExpiryExecutor executor = mock(DmsFileExpiryExecutor.class);
        DmsFileExpirySearchCleanup cleanup = mock(DmsFileExpirySearchCleanup.class);
        DmsFileExpiryItem first = item(), second = item(); second.setFileVersionId(UUID.randomUUID());
        when(mapper.candidates(null, 1)).thenReturn(List.of(first));
        when(mapper.candidates(first, 1)).thenReturn(List.of(second));
        when(mapper.candidates(second, 1)).thenReturn(List.of());
        when(executor.process(first)).thenThrow(new DataException("conflict"));
        when(executor.process(second)).thenReturn(true);
        DmsFileExpiryJob job = new DmsFileExpiryJob(properties, mapper, executor, cleanup, new DmsSearchProperties());
        DmsFileExpiryJob.Result result = job.run();
        assertEquals(2, result.getScanned()); assertEquals(1, result.getFailed()); assertEquals(1, result.getApplied());
        verify(executor).process(first);
        verify(executor).process(second);
        verifyNoInteractions(cleanup);
    }
 
    @Test void sharedCleanupRunsEvenWhenNoVersionsAreDue() {
        when(mapper.candidates(null, 100)).thenReturn(List.of());
        DmsFileExpiryItem receipt = item();
        when(mapper.cleanupCandidates(100)).thenReturn(List.of(receipt));
        DmsFileExpiryExecutor executor = mock(DmsFileExpiryExecutor.class);
        DmsFileExpirySearchCleanup cleanup = mock(DmsFileExpirySearchCleanup.class);
        DmsSearchProperties search = new DmsSearchProperties(); search.setEnabled(true);
        DmsFileExpiryJob.Result result = new DmsFileExpiryJob(new DmsFileExpiryProperties(), mapper, executor, cleanup, search).run();
        assertEquals(0, result.getScanned());
        verify(cleanup).process(receipt);
        verifyNoInteractions(executor);
    }
 
    @Test void cleanupPersistsAcceptedTaskAndWaitsForConfirmedSuccess() throws Exception {
        DmsMeilisearchClient client = mock(DmsMeilisearchClient.class);
        DmsFileExpirySearchCleanup worker = new DmsFileExpirySearchCleanup(mapper, client, new DmsFileExpiryProperties());
        DmsFileExpiryItem item = item();
        when(mapper.lockCleanup("t", version)).thenReturn(item);
        when(client.deleteDocument("t", version)).thenReturn(42L);
        worker.process(item);
        verify(mapper).updateCleanup(item, false, null, 60);
        when(client.taskStatus(42L)).thenReturn("succeeded");
        worker.process(item);
        verify(mapper).updateCleanup(item, true, null, 60);
        verify(client, times(1)).deleteDocument("t", version);
    }
 
    @Test void cleanupRetriesFailedOrLostTasksAndRetainsNetworkFailures() throws Exception {
        DmsMeilisearchClient client = mock(DmsMeilisearchClient.class);
        DmsFileExpirySearchCleanup worker = new DmsFileExpirySearchCleanup(mapper, client, new DmsFileExpiryProperties());
        DmsFileExpiryItem item = item(); item.setSearchTaskId(42L);
        when(mapper.lockCleanup("t", version)).thenReturn(item);
        when(client.taskStatus(42L)).thenReturn("failed");
        worker.process(item);
        assertNull(item.getSearchTaskId());
        when(client.deleteDocument("t", version)).thenThrow(new java.io.IOException("offline"));
        worker.process(item);
        verify(mapper).updateCleanup(item, false, "IOException: offline", 60);
    }
 
    private DmsFileExpiryResource resource(String status, String reason) {
        DmsFileExpiryResource value = new DmsFileExpiryResource();
        value.setId("file"); value.setCurrentVersionId("business-version"); value.setCenterVersionId(version.toString());
        value.setVersionCenterId(version.toString()); value.setBizStatus(status); value.setDisabledSource(reason);
        return value;
    }
    private DmsFileExpiryItem item() {
        DmsFileExpiryItem value = new DmsFileExpiryItem();
        value.setTenantId("t"); value.setDocumentId(UUID.randomUUID()); value.setFileVersionId(version);
        value.setLifecycleStatus("EFFECTIVE"); value.setEffectiveFrom(new Date(1600000000000L)); value.setEffectiveTo(end);
        return value;
    }
}