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;
|
}
|
}
|