From 521595b1d7f271fabe2b3870001a88959ec08c0d Mon Sep 17 00:00:00 2001 From: Adrian Date: Fri, 4 Jul 2025 11:37:45 -0600 Subject: [PATCH 1/2] Fixing missing bytes when processing chunks of a file during upload --- .../graph/core/tasks/LargeFileUploadTask.java | 37 +++++++---- .../graph/core/tasks/LargeFileUploadTest.java | 64 +++++++++++++++++++ 2 files changed, 89 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/microsoft/graph/core/tasks/LargeFileUploadTask.java b/src/main/java/com/microsoft/graph/core/tasks/LargeFileUploadTask.java index eec1e05fe..f8a45a65f 100644 --- a/src/main/java/com/microsoft/graph/core/tasks/LargeFileUploadTask.java +++ b/src/main/java/com/microsoft/graph/core/tasks/LargeFileUploadTask.java @@ -1,5 +1,20 @@ package com.microsoft.graph.core.tasks; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.InvocationTargetException; +import java.time.OffsetDateTime; +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.CancellationException; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + import com.microsoft.graph.core.ErrorConstants; import com.microsoft.graph.core.exceptions.ClientException; import com.microsoft.graph.core.models.IProgressCallback; @@ -18,19 +33,10 @@ import com.microsoft.kiota.serialization.Parsable; import com.microsoft.kiota.serialization.ParsableFactory; import com.microsoft.kiota.serialization.ParseNode; -import okhttp3.OkHttpClient; import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.lang.reflect.InvocationTargetException; -import java.time.OffsetDateTime; -import java.util.*; -import java.util.concurrent.CancellationException; -import java.util.concurrent.TimeUnit; -import java.util.function.Consumer; +import okhttp3.OkHttpClient; /** * Task for uploading large files including pausing and resuming. @@ -274,8 +280,15 @@ private long nextSliceSize(long rangeBegin, long rangeEnd) { } private byte[] chunkInputStream(InputStream stream, int length) throws IOException { byte[] buffer = new byte[length]; - int lengthAssert = stream.read(buffer); - assert lengthAssert == length; + int totalRead = 0; + while (totalRead < length) { + int bytesRead = stream.read(buffer, totalRead, length - totalRead); + if (bytesRead == -1) { + // End of stream reached + break; + } + totalRead += bytesRead; + } return buffer; } } diff --git a/src/test/java/com/microsoft/graph/core/tasks/LargeFileUploadTest.java b/src/test/java/com/microsoft/graph/core/tasks/LargeFileUploadTest.java index 5f50476a3..5a2ad7774 100644 --- a/src/test/java/com/microsoft/graph/core/tasks/LargeFileUploadTest.java +++ b/src/test/java/com/microsoft/graph/core/tasks/LargeFileUploadTest.java @@ -5,9 +5,14 @@ import com.microsoft.graph.core.models.UploadSession; import com.microsoft.kiota.authentication.AuthenticationProvider; import com.microsoft.kiota.http.OkHttpRequestAdapter; + import org.junit.jupiter.api.Test; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.doReturn; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import org.mockito.ArgumentCaptor; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -17,6 +22,10 @@ import java.util.ArrayList; import java.util.Arrays; +import org.mockito.internal.matchers.Any; + +import com.microsoft.graph.core.models.UploadResult; + class LargeFileUploadTest { final OkHttpRequestAdapter adapter = new OkHttpRequestAdapter(mock(AuthenticationProvider.class)); @@ -106,4 +115,59 @@ void BreakStreamIntoCorrectRanges() throws IOException, NoSuchFieldException, Il assertEquals(size%maxSliceSize, lastSlice.getRangeLength()); assertEquals(size-1, lastSlice.getRangeEnd()); } + // Test for chunkInputStream method with a 5MB file + @Test + void uploads5MBFileSuccessfully() throws Exception { + // Arrange + UploadSession session = new UploadSession(); + session.setNextExpectedRanges(Arrays.asList("0-")); + session.setUploadUrl("http://localhost"); + session.setExpirationDateTime(OffsetDateTime.now().plusHours(1)); + + // 5MB file + byte[] data = new byte[5 * 1024 * 1024]; + for (int i = 0; i < data.length; i++) { + data[i] = (byte)(i % 256); + } + ByteArrayInputStream stream = new ByteArrayInputStream(data); + int size = stream.available(); + + // Create a real task to get the real builder(s) + LargeFileUploadTask realTask = new LargeFileUploadTask<>(adapter, session, stream, size, TestDriveItem::createFromDiscriminatorValue); + var realBuilders = realTask.getUploadSliceRequests(); + + // Spy the builder(s) and mock put() + ArrayList> spyBuilders = new ArrayList<>(); + ArgumentCaptor captor = ArgumentCaptor.forClass(ByteArrayInputStream.class); + + for (UploadSliceRequestBuilder builder : realBuilders) { + UploadSliceRequestBuilder spyBuilder = spy(builder); + UploadResult mockResult = new UploadResult<>(); + TestDriveItem item = new TestDriveItem(); + item.size = data.length; + mockResult.itemResponse = item; + doReturn(mockResult).when(spyBuilder).put(captor.capture()); + spyBuilders.add(spyBuilder); + } + + // Subclass LargeFileUploadTask to inject our spy builders + LargeFileUploadTask task = new LargeFileUploadTask<>(adapter, session, stream, size, TestDriveItem::createFromDiscriminatorValue) { + @Override + protected java.util.List> getUploadSliceRequests() { + return spyBuilders; + } + }; + + // Act + UploadResult result = task.upload(3, null); + + // Verify the chunkStream content + ByteArrayInputStream capturedStream = captor.getValue(); + byte[] capturedBytes = new byte[data.length]; + int read = capturedStream.read(capturedBytes); + assertEquals(data.length, read, "Should read all bytes from chunkStream"); + for (int i = 0; i < data.length; i++) { + assertEquals(data[i], capturedBytes[i], "Byte at position " + i + " should match original data"); + } + } } From 7c452cc2ce967bf2104e6a81c062c435f8481cfb Mon Sep 17 00:00:00 2001 From: Adrian Date: Fri, 4 Jul 2025 12:26:27 -0600 Subject: [PATCH 2/2] Fixing spot bug issue --- .../com/microsoft/graph/core/tasks/LargeFileUploadTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/com/microsoft/graph/core/tasks/LargeFileUploadTest.java b/src/test/java/com/microsoft/graph/core/tasks/LargeFileUploadTest.java index 5a2ad7774..26eacec54 100644 --- a/src/test/java/com/microsoft/graph/core/tasks/LargeFileUploadTest.java +++ b/src/test/java/com/microsoft/graph/core/tasks/LargeFileUploadTest.java @@ -159,7 +159,7 @@ protected java.util.List> getUploadSlic }; // Act - UploadResult result = task.upload(3, null); + task.upload(3, null); // Verify the chunkStream content ByteArrayInputStream capturedStream = captor.getValue();