刘光辉
2 小时以前 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
package jnpf.bizcommon.onlyoffice.service.impl;
 
import cn.hutool.jwt.JWTUtil;
import jnpf.base.UserInfo;
import jnpf.bizcommon.onlyoffice.entity.OnlyOfficeCallbackParam;
import jnpf.bizcommon.onlyoffice.entity.OnlyOfficeProperties;
import jnpf.dmsEntity.compare.DmsCompareTicket;
import jnpf.dmsEntity.preview.vo.DmsFilePreviewTicketVO;
import jnpf.exception.DataException;
import jnpf.util.JsonUtil;
import org.junit.Before;
import org.junit.Test;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.List;
import java.util.UUID;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
 
public class OnlyOfficeCompareServiceTest {
    private final OnlyOfficeProperties properties = new OnlyOfficeProperties();
    private final DmsOnlyOfficePreviewTicketValidator validator = mock(DmsOnlyOfficePreviewTicketValidator.class);
    private final DmsCompareTicket ticket = new DmsCompareTicket();
    private final UserInfo user = new UserInfo();
    private OnlyOfficeCompareService service;
    private final OnlyOfficeComparisonRenderer renderer = mock(OnlyOfficeComparisonRenderer.class);
 
    @Before public void setup() {
        properties.setJwtSecret("test-only-compare-secret");
        properties.setFileFetchBaseUrl("http://gateway"); properties.setCallbackBaseUrl("http://gateway");
        ticket.setTenantId("t1"); ticket.setUserId("u1"); ticket.setDocumentId(UUID.randomUUID());
        ticket.setExpiresAt(System.currentTimeMillis() + 300_000);
        ticket.setHistorical(source("old-ticket")); ticket.setCurrent(source("new-ticket"));
        ticket.setHistoricalLabel("v1"); ticket.setCurrentLabel("v3");
        user.setTenantId("t1"); user.setUserId("u1");
        service = new OnlyOfficeCompareService(properties, new DmsDocumentCompareConfigBuilder(), validator,
                value -> JsonUtil.getObjectToString(ticket), renderer);
        when(renderer.generate(anyString(), anyString())).thenReturn("/cache/files/compare/output/Result.docx?md5=test");
    }
 
    @SuppressWarnings("unchecked")
    @Test public void signsCorrectDirectionAndIsolatedSessionWithoutNormalSaveRoute() {
        Map<String, Object> result = service.createConfig("ticket", user);
        Map<String, Object> config = (Map<String, Object>) result.get("config");
        Map<String, Object> doc = (Map<String, Object>) config.get("document");
        Map<String, Object> editor = (Map<String, Object>) config.get("editorConfig");
        assertFalse(result.containsKey("revisedDocument"));
        assertEquals("view", editor.get("mode"));
        Map<String, Object> permissions = (Map<String, Object>) doc.get("permissions");
        Map<String, Object> customization = (Map<String, Object>) editor.get("customization");
        assertEquals(false, permissions.get("edit"));
        assertEquals(false, permissions.get("review"));
        for (String permission : List.of("download", "print", "comment", "chat", "protect")) {
            assertEquals(permission, false, permissions.get(permission));
        }
        for (String option : List.of("autosave", "forcesave", "plugins", "macros", "comments", "help", "feedback", "suggestFeature")) {
            assertEquals(option, false, customization.get(option));
        }
        for (String option : List.of("toolbarHideFileName", "hideRightMenu", "compactToolbar")) {
            assertEquals(option, true, customization.get(option));
        }
        assertEquals(Map.of("spellcheck", false), customization.get("features"));
        assertEquals(Map.of("trackChanges", false, "reviewDisplay", "markup", "showReviewChanges", true), customization.get("review"));
        verify(renderer).generate(endsWith("ticket=old-ticket"), endsWith("ticket=new-ticket"));
        assertTrue(doc.get("url").toString().startsWith("http://gateway/api/biz/onlyoffice/compare/result?ticket="));
        assertEquals("http://gateway/api/biz/onlyoffice/compare/callback", editor.get("callbackUrl"));
        assertTrue(JWTUtil.verify(config.get("token").toString(), properties.getJwtSecret().getBytes(StandardCharsets.UTF_8)));
        Map<String, Object> second = (Map<String, Object>) service.createConfig("ticket", user).get("config");
        assertNotEquals(doc.get("key"), ((Map<String, Object>)second.get("document")).get("key"));
        assertFalse(new DmsDocumentCompareConfigBuilder().build(null).canEdit());
    }
 
    @SuppressWarnings("unchecked")
    @Test public void inheritsPreviewPolicyWithoutMutatingPreviewDefaults() {
        Map<String, Object> previewUi = OnlyOfficeUiConfig.previewCustomization();
        Map<String, Object> previewPermissions = OnlyOfficeUiConfig.previewPermissions();
        Map<String, Object> config = new DmsDocumentCompareConfigBuilder()
                .comparisonConfig("key", "title", "source", "callback", user);
        Map<String, Object> editor = (Map<String, Object>) config.get("editorConfig");
        Map<String, Object> actualUi = (Map<String, Object>) editor.get("customization");
        Map<String, Object> doc = (Map<String, Object>) config.get("document");
        Map<String, Object> actualPermissions = (Map<String, Object>) doc.get("permissions");
        Map<String, Object> expectedUi = new java.util.LinkedHashMap<>(previewUi);
        expectedUi.put("forcesave", false);
        expectedUi.put("close", Map.of("visible", false));
        expectedUi.put("review", Map.of("trackChanges", false, "reviewDisplay", "markup", "showReviewChanges", true));
        assertEquals(expectedUi, actualUi);
        Map<String, Object> expectedPermissions = new java.util.LinkedHashMap<>(previewPermissions);
        assertEquals(expectedPermissions, actualPermissions);
        assertEquals(previewUi, OnlyOfficeUiConfig.previewCustomization());
        assertEquals(previewPermissions, OnlyOfficeUiConfig.previewPermissions());
    }
 
    @Test public void rejectsExpiredAndCrossUserTickets() {
        user.setUserId("intruder");
        rejects(() -> service.createConfig("ticket", user));
        user.setUserId("u1"); ticket.setExpiresAt(0);
        rejects(() -> service.createConfig("ticket", user));
        verifyNoInteractions(validator);
    }
 
    @SuppressWarnings("unchecked")
    @Test public void resultStreamRequiresAnUnexpiredSignedResultTicket() {
        Map<String, Object> config = (Map<String, Object>) service.createConfig("ticket", user).get("config");
        String url = ((Map<String, Object>) config.get("document")).get("url").toString();
        String resultTicket = url.substring(url.indexOf("?ticket=") + 8);
        when(renderer.readResult(anyString())).thenReturn(new byte[]{1, 2});
        assertArrayEquals(new byte[]{1, 2}, service.readResult(resultTicket));
        verify(renderer).readResult("/cache/files/compare/output/Result.docx?md5=test");
        clearInvocations(renderer);
        rejects(() -> service.readResult(resultTicket + "tampered"));
        String expired = JWTUtil.createToken(Map.of("scope", "dms:onlyoffice:compare-result", "expiresAt", 0),
                properties.getJwtSecret().getBytes(StandardCharsets.UTF_8));
        rejects(() -> service.readResult(expired));
        verifyNoInteractions(renderer);
    }
 
    @Test public void generationFailureNeverReturnsAnEditableFallback() {
        when(renderer.generate(anyString(), anyString())).thenThrow(new DataException("生成失败"));
        rejects(() -> service.createConfig("ticket", user));
    }
 
    @Test public void acknowledgesSaveStatusesWithoutFetchingOutputOrWritingSource() {
        OnlyOfficeCallbackParam callback = new OnlyOfficeCallbackParam();
        callback.setKey("dms-compare-" + UUID.randomUUID());
        callback.setUrl("http://must-not-be-fetched.invalid/output.docx");
        String token = JWTUtil.createToken(Map.of("key", callback.getKey()), properties.getJwtSecret().getBytes(StandardCharsets.UTF_8));
        for (int status : new int[]{1, 2, 3, 4, 6, 7}) {
            callback.setStatus(status); service.acknowledgeCallback(callback, "Bearer " + token);
        }
        verifyNoInteractions(validator);
        rejects(() -> service.acknowledgeCallback(callback, null));
        callback.setKey("ordinary-edit-key");
        rejects(() -> service.acknowledgeCallback(callback, "Bearer " + token));
    }
 
    private void rejects(Runnable action) {
        try { action.run(); fail("Expected DataException"); } catch (DataException expected) { }
    }
 
    private DmsFilePreviewTicketVO source(String value) {
        DmsFilePreviewTicketVO s = new DmsFilePreviewTicketVO(); s.setFileVersionId(UUID.randomUUID());
        s.setFileName("procedure.docx"); s.setAccessTicket(value); return s;
    }
}