package jnpf.bizcommon.onlyoffice.service.impl; import cn.hutool.crypto.digest.DigestUtil; import cn.hutool.jwt.JWTUtil; import jnpf.base.ActionResult; import jnpf.base.model.uploadedit.UploadEditLease; import jnpf.bizcommon.onlyoffice.entity.OnlyOfficeCallbackParam; import jnpf.bizcommon.onlyoffice.entity.OnlyOfficeSessionEntity; import jnpf.exception.DataException; import jnpf.onlinedev.UploadEditApi; import org.junit.Test; import java.util.*; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; public class UploadEditSessionSupportTest { private ActionResult success(T value) { ActionResult result = new ActionResult<>(); result.setCode(200); result.setData(value); return result; } private OnlyOfficeSessionEntity session() { OnlyOfficeSessionEntity s = new OnlyOfficeSessionEntity(); s.setTenantId("tenant"); s.setLockUserId("owner"); s.setFileId("lease"); s.setFileName("source.docx"); return s; } private OnlyOfficeCallbackParam callback() { OnlyOfficeCallbackParam c = new OnlyOfficeCallbackParam(); c.setKey("key"); c.setStatus(6); c.setUrl("https://office/result.docx"); return c; } @Test public void docCallbackUsesActualOutputNameInStorageAndAdoptsItsResult() { for (String output : Arrays.asList("docx", "doc")) { UploadEditApi api = mock(UploadEditApi.class); UploadEditStorage storage = mock(UploadEditStorage.class); UploadEditLease lease = new UploadEditLease(); lease.setTracked(true); lease.setOwnerId("owner"); lease.setLeaseId("lease"); lease.setFile(Collections.singletonMap("fileId", "source")); when(api.lease("tenant", "owner", "lease")).thenReturn(success(lease)); Map saved = Map.of("fileId", "saved", "name", "source." + output, "fileExtension", output); when(storage.save(eq("tenant"), eq("lease"), eq("source." + output), any())).thenReturn(saved); when(api.save(any())).thenReturn(success(true)); OnlyOfficeSessionEntity session = session(); session.setFileName("source.doc"); OnlyOfficeCallbackParam callback = callback(); callback.setFiletype(output); new UploadEditSessionSupport(api, storage).save(session, callback, new byte[]{1}); verify(storage).save(eq("tenant"), eq("lease"), eq("source." + output), any()); verify(api).save(argThat(command -> saved.equals(command.getFile()))); } } @Test(expected = DataException.class) public void docCallbackWithoutOutputTypeCannotMislabelItsBytes() { UploadEditSessionSupport.savedFileName("source.doc", null); } @Test(expected = DataException.class) public void unexpectedOutputTypeIsRejected() { UploadEditSessionSupport.savedFileName("source.doc", "pdf"); } @Test(expected = DataException.class) public void outputTypeMustMatchSignedCallback() { OnlyOfficeCallbackParam c = callback(); c.setFiletype("doc"); Map claims = new HashMap<>(); claims.put("key", c.getKey()); claims.put("status", c.getStatus()); claims.put("url", c.getUrl()); claims.put("filetype", "docx"); c.setToken(JWTUtil.createToken(claims, new byte[]{1, 2, 3})); UploadEditSessionSupport.requireBoundCallback(c, null); } @Test public void statusDoesNotFetchFilesOrWriteStorage() { UploadEditApi api = mock(UploadEditApi.class); UploadEditStorage storage = mock(UploadEditStorage.class); when(api.canReadStatus("tenant", "owner", "lease")).thenReturn(success(true)); new UploadEditSessionSupport(api, storage).requireStatusAccess("tenant", "owner", "lease"); verify(api, never()).lease(any(), any(), any()); verifyNoInteractions(storage); } @Test(expected = DataException.class) public void expiredOrForeignLeaseCannotReadStatus() { UploadEditApi api = mock(UploadEditApi.class); when(api.canReadStatus("tenant", "owner", "lease")).thenReturn(success(false)); new UploadEditSessionSupport(api, mock(UploadEditStorage.class)).requireStatusAccess("tenant", "owner", "lease"); } @Test(expected = DataException.class) public void failedStatusProbeCannotBeTreatedAsSuccess() { UploadEditApi api = mock(UploadEditApi.class); ActionResult failed = success(true); failed.setCode(400); when(api.canReadStatus("tenant", "owner", "lease")).thenReturn(failed); new UploadEditSessionSupport(api, mock(UploadEditStorage.class)).requireStatusAccess("tenant", "owner", "lease"); } @Test public void authorizedRevisionTemporaryCallbackStoresSeparateCopy() { UploadEditApi api = mock(UploadEditApi.class); UploadEditStorage storage = mock(UploadEditStorage.class); UploadEditLease lease = new UploadEditLease(); lease.setRevisionTemporary(true); lease.setFile(Map.of("fileId", "original")); when(api.lease("tenant", "owner", "lease")).thenReturn(success(lease)); Map copy = Map.of("fileId", "copy", "name", "source.docx"); when(storage.save(eq("tenant"),eq("lease"),eq("source.docx"),any())).thenReturn(copy); when(api.save(any())).thenReturn(success(true)); new UploadEditSessionSupport(api,storage).save(session(),callback(),new byte[]{1}); verify(api).save(argThat(c -> copy.equals(c.getFile()))); } @Test public void untrackedCallbackNeverWritesFileOrDraft() { UploadEditApi api = mock(UploadEditApi.class); UploadEditStorage storage = mock(UploadEditStorage.class); UploadEditLease lease = new UploadEditLease(); lease.setFile(Collections.singletonMap("fileId", "source")); when(api.lease("tenant", "owner", "lease")).thenReturn(success(lease)); new UploadEditSessionSupport(api, storage).save(session(), callback(), new byte[]{1}); verifyNoInteractions(storage); verify(api, never()).save(any()); } @Test public void repeatedCallbackDoesNotCreateAnotherPhysicalFile() { UploadEditApi api = mock(UploadEditApi.class); UploadEditStorage storage = mock(UploadEditStorage.class); UploadEditLease lease = new UploadEditLease(); lease.setTracked(true); lease.setFile(Collections.singletonMap("fileId", "source")); OnlyOfficeCallbackParam c = callback(); lease.setLastOperationId(DigestUtil.sha256Hex(c.getKey() + "\n" + c.getStatus() + "\n" + c.getUrl())); when(api.lease("tenant", "owner", "lease")).thenReturn(success(lease)); new UploadEditSessionSupport(api, storage).save(session(), c, new byte[]{1}); verifyNoInteractions(storage); verify(api, never()).save(any()); } @Test public void acceptsMatchingRootAndWrappedSignedPayloads() { for (String output : Arrays.asList(null, "doc", "docx")) { OnlyOfficeCallbackParam c = callback(); c.setFiletype(output); Map claims = new HashMap<>(); claims.put("key", c.getKey()); claims.put("status", c.getStatus()); claims.put("url", c.getUrl()); if (output != null) claims.put("filetype", output); UploadEditSessionSupport.requireBoundCallback(c, "Bearer " + JWTUtil.createToken(claims, new byte[]{1, 2, 3})); c.setToken(JWTUtil.createToken(Collections.singletonMap("payload", claims), new byte[]{1, 2, 3})); UploadEditSessionSupport.requireBoundCallback(c, null); } } @Test(expected = DataException.class) public void rejectsUnsignedRequestBodySubstitution() { OnlyOfficeCallbackParam c = callback(); Map claims = new HashMap<>(); claims.put("key", c.getKey()); claims.put("status", c.getStatus()); claims.put("url", "https://different"); c.setToken(JWTUtil.createToken(claims, new byte[]{1, 2, 3})); UploadEditSessionSupport.requireBoundCallback(c, null); } }