package jnpf.bizcommon.onlyoffice.service.impl; import cn.hutool.http.HttpRequest; import cn.hutool.http.HttpResponse; import cn.hutool.http.HttpUtil; import cn.hutool.jwt.JWTUtil; import com.baomidou.mybatisplus.core.MybatisConfiguration; import com.baomidou.mybatisplus.core.metadata.TableInfoHelper; import jnpf.bizcommon.onlyoffice.entity.OnlyOfficeCallbackParam; import jnpf.bizcommon.onlyoffice.entity.OnlyOfficeProperties; import jnpf.bizcommon.onlyoffice.entity.OnlyOfficeSessionEntity; import jnpf.bizcommon.onlyoffice.mapper.OnlyOfficeSessionMapper; import jnpf.exception.DataException; import jnpf.file.FileApi; import jnpf.file.FileUploadApi; import org.apache.ibatis.builder.MapperBuilderAssistant; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.mockito.MockedStatic; import java.nio.charset.StandardCharsets; import java.util.Map; import static org.junit.Assert.*; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; /** 使用内存中的文档响应验证场景分发,不监听端口或访问外部文件服务。 */ public class UploadEditCallbackTest { private OnlyOfficeSessionMapper sessionMapper; private FileApi fileApi; private FileUploadApi fileUploadApi; private OnlyOfficeServiceImpl service; private MockedStatic http; @Before public void setup() { TableInfoHelper.initTableInfo(new MapperBuilderAssistant(new MybatisConfiguration(), ""), OnlyOfficeSessionEntity.class); sessionMapper = mock(OnlyOfficeSessionMapper.class); when(sessionMapper.update(isNull(), any())).thenReturn(1); fileApi = mock(FileApi.class); fileUploadApi = mock(FileUploadApi.class); when(fileApi.getPath("annex")).thenReturn("WebAnnexFile"); OnlyOfficeProperties properties = new OnlyOfficeProperties(); properties.setJwtSecret("test-secret"); service = new OnlyOfficeServiceImpl(properties, sessionMapper, null, null, null, null, null, fileApi, fileUploadApi, new DmsQianpiyeDocxPropertiesWriter()); http = mockStatic(HttpUtil.class); HttpRequest request = mock(HttpRequest.class); HttpResponse response = mock(HttpResponse.class); http.when(() -> HttpUtil.createGet("http://document-server/saved.docx")).thenReturn(request); when(request.timeout(anyInt())).thenReturn(request); when(request.executeAsync()).thenReturn(response); when(response.isOk()).thenReturn(true); when(response.bodyBytes()).thenReturn("saved-document".getBytes(StandardCharsets.UTF_8)); } @After public void cleanup() { if (http != null) http.close(); } @Test public void uploadCallbackSaveFailureUsesUploadSpecificException() { OnlyOfficeSessionEntity session = session(); session.setBizModule("visualdev"); session.setBizScene("visualdev.uploadFile"); when(sessionMapper.selectOne(any())).thenReturn(session); for (int status : new int[] {2, 6}) { OnlyOfficeCallbackParam callback = callback(); callback.setKey(session.getDocKey()); callback.setStatus(status); callback.setUrl(null); Map claims = new java.util.LinkedHashMap<>(); claims.put("key", callback.getKey()); claims.put("status", status); String token = JWTUtil.createToken(claims, "test-secret".getBytes(StandardCharsets.UTF_8)); try { service.handleCallback(callback, "Bearer " + token); fail("upload save without a source URL must fail"); } catch (jnpf.bizcommon.onlyoffice.exception.UploadEditCallbackException e) { assertTrue(e.getCause() instanceof DataException); assertTrue(e.getMessage().contains("缺少下载 url")); } } verifyNoInteractions(fileApi); verify(fileUploadApi, never()).replaceFile(any(), any(), any()); } @Test public void uploadCallbackSavesThroughUploadBranchOnly() throws Exception { UploadEditSessionSupport support = mock(UploadEditSessionSupport.class); java.lang.reflect.Field field = OnlyOfficeServiceImpl.class.getDeclaredField("uploadEditSessionSupport"); field.setAccessible(true); field.set(service, support); for (int status : new int[] {2, 6}) { OnlyOfficeSessionEntity session = session(); session.setBizModule("visualdev"); session.setBizScene("visualdev.uploadFile"); session.setSessionStatus(OnlyOfficeSessionEntity.SessionStatus.EDITING); when(sessionMapper.selectOne(any())).thenReturn(session); OnlyOfficeCallbackParam callback = callback(); callback.setKey(session.getDocKey()); callback.setStatus(status); Map claims = new java.util.LinkedHashMap<>(); claims.put("key", callback.getKey()); claims.put("status", status); claims.put("url", callback.getUrl()); callback.setFiletype("docx"); claims.put("filetype", callback.getFiletype()); String token = JWTUtil.createToken(claims, "test-secret".getBytes(StandardCharsets.UTF_8)); service.handleCallback(callback, "Bearer " + token); verify(support).save(eq(session), eq(callback), any(byte[].class)); } http.verify(() -> HttpUtil.createGet("http://document-server/saved.docx"), times(2)); verify(fileUploadApi, never()).replaceFile(any(), any(), any()); } @Test public void nonUploadSaveFailureKeepsOriginalException() { OnlyOfficeSessionEntity session = session(); session.setBizModule("other-business"); when(sessionMapper.selectOne(any())).thenReturn(session); OnlyOfficeCallbackParam callback = callback(); callback.setUrl(null); String token = JWTUtil.createToken(java.util.Collections.singletonMap("scope", "test"), "test-secret".getBytes(StandardCharsets.UTF_8)); try { service.handleCallback(callback, "Bearer " + token); fail("invalid save must fail"); } catch (DataException e) { assertTrue(e.getMessage().contains("缺少下载 url")); } http.verifyNoInteractions(); } private OnlyOfficeSessionEntity session() { OnlyOfficeSessionEntity session = new OnlyOfficeSessionEntity(); session.setId("session-1"); session.setTenantId("tenant-a"); session.setFileId("lease-1"); session.setFileType("annex"); session.setDocKey("key-1"); session.setVersionNo(1); return session; } private OnlyOfficeCallbackParam callback() { OnlyOfficeCallbackParam callback = new OnlyOfficeCallbackParam(); callback.setKey("key-1"); callback.setStatus(2); callback.setUrl("http://document-server/saved.docx"); return callback; } }