diff --git a/.github/workflows/gradle-build.yml b/.github/workflows/gradle-build.yml index 7bb8af52b..5200b4e04 100644 --- a/.github/workflows/gradle-build.yml +++ b/.github/workflows/gradle-build.yml @@ -16,10 +16,6 @@ jobs: java-version: 21 distribution: 'temurin' cache: gradle - - name: Detect secrets - run: | - pip install detect-secrets - git ls-files -z | xargs -0 detect-secrets-hook --baseline .secrets.baseline - name: Grant execute permission for gradlew run: chmod +x gradlew - name: Build with Gradle diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 39d5a95e9..d3d6d3e4e 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "3.6.3" + ".": "3.6.4" } diff --git a/.secrets.baseline b/.secrets.baseline deleted file mode 100644 index 6bac09a59..000000000 --- a/.secrets.baseline +++ /dev/null @@ -1,113 +0,0 @@ -{ - "version": "1.5.0", - "plugins_used": [ - { - "name": "ArtifactoryDetector" - }, - { - "name": "AWSKeyDetector" - }, - { - "name": "AzureStorageKeyDetector" - }, - { - "name": "Base64HighEntropyString", - "limit": 4.5 - }, - { - "name": "BasicAuthDetector" - }, - { - "name": "CloudantDetector" - }, - { - "name": "HexHighEntropyString", - "limit": 3.0 - }, - { - "name": "IbmCloudIamDetector" - }, - { - "name": "IbmCosHmacDetector" - }, - { - "name": "JwtTokenDetector" - }, - { - "name": "KeywordDetector", - "keyword_exclude": "" - }, - { - "name": "MailchimpDetector" - }, - { - "name": "NpmDetector" - }, - { - "name": "PrivateKeyDetector" - }, - { - "name": "SlackDetector" - }, - { - "name": "SoftlayerDetector" - }, - { - "name": "SquareOAuthDetector" - }, - { - "name": "StripeDetector" - }, - { - "name": "TwilioKeyDetector" - } - ], - "filters_used": [ - { - "path": "detect_secrets.filters.allowlist.is_line_allowlisted" - }, - { - "path": "detect_secrets.filters.common.is_baseline_file", - "filename": ".secrets.baseline" - }, - { - "path": "detect_secrets.filters.common.is_ignored_due_to_verification_policies", - "min_level": 2 - }, - { - "path": "detect_secrets.filters.heuristic.is_indirect_reference" - }, - { - "path": "detect_secrets.filters.heuristic.is_likely_id_string" - }, - { - "path": "detect_secrets.filters.heuristic.is_lock_file" - }, - { - "path": "detect_secrets.filters.heuristic.is_not_alphanumeric_string" - }, - { - "path": "detect_secrets.filters.heuristic.is_potential_uuid" - }, - { - "path": "detect_secrets.filters.heuristic.is_prefixed_with_dollar_sign" - }, - { - "path": "detect_secrets.filters.heuristic.is_sequential_string" - }, - { - "path": "detect_secrets.filters.heuristic.is_swagger_file" - }, - { - "path": "detect_secrets.filters.heuristic.is_templated_secret" - }, - { - "path": "detect_secrets.filters.regex.should_exclude_file", - "pattern": [ - "release-please-config.json" - ] - } - ], - "results": {}, - "generated_at": "2024-08-12T15:21:45Z" -} diff --git a/CHANGELOG.md b/CHANGELOG.md index dc9f38e22..7dda7202e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [3.6.4](https://github.com/microsoftgraph/msgraph-sdk-java-core/compare/v3.6.3...v3.6.4) (2025-07-04) + + +### Bug Fixes + +* missing bytes when processing chunks of a file during upload ([e0dbff6](https://github.com/microsoftgraph/msgraph-sdk-java-core/commit/e0dbff6fe4b15b0cc661cf79d38152e2cb34d117)) + ## [3.6.3](https://github.com/microsoftgraph/msgraph-sdk-java-core/compare/v3.6.2...v3.6.3) (2025-06-20) diff --git a/README.md b/README.md index 77548b02a..0d8984623 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ repositories { dependencies { // Include the sdk as a dependency // x-release-please-start-version - implementation 'com.microsoft.graph:microsoft-graph-core:3.6.3' + implementation 'com.microsoft.graph:microsoft-graph-core:3.6.4' // x-release-please-end // This dependency is only needed if you are using the TokenCredentialAuthProvider implementation 'com.azure:azure-identity:1.11.0' @@ -40,7 +40,7 @@ Add the dependency in `dependencies` in pom.xml com.microsoft.graph microsoft-graph-core - 3.6.3 + 3.6.4 com.azure diff --git a/android/build.gradle b/android/build.gradle index 4821bb370..175e29751 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -6,7 +6,7 @@ buildscript { dependencies { classpath "com.gradle:gradle-enterprise-gradle-plugin:3.19.2" - classpath "com.android.tools.build:gradle:8.10.1" + classpath "com.android.tools.build:gradle:8.11.0" classpath "com.github.ben-manes:gradle-versions-plugin:0.52.0" } } diff --git a/build.gradle b/build.gradle index 85cba9289..f71ef6189 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ plugins { id 'maven-publish' id 'signing' id 'jacoco' - id 'com.github.spotbugs' version '6.2.0' + id 'com.github.spotbugs' version '6.2.1' id "org.sonarqube" version "6.2.0.5505" } diff --git a/gradle.properties b/gradle.properties index 97f5d5db2..f038876fc 100644 --- a/gradle.properties +++ b/gradle.properties @@ -30,7 +30,7 @@ mavenMajorVersion = 3 mavenMinorVersion = 6 # x-release-please-end # x-release-please-start-patch -mavenPatchVersion = 3 +mavenPatchVersion = 4 # x-release-please-end mavenArtifactSuffix = diff --git a/gradle/dependencies.gradle b/gradle/dependencies.gradle index 8712ad006..d41fb6bd2 100644 --- a/gradle/dependencies.gradle +++ b/gradle/dependencies.gradle @@ -1,6 +1,6 @@ dependencies { // Use JUnit test framework - testImplementation 'org.junit.jupiter:junit-jupiter:5.13.1' + testImplementation 'org.junit.jupiter:junit-jupiter:5.13.2' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' testImplementation 'org.mockito:mockito-core:5.18.0' testImplementation 'io.opentelemetry:opentelemetry-api:1.51.0' @@ -18,11 +18,11 @@ dependencies { api 'com.squareup.okhttp3:okhttp:4.12.0' api 'com.azure:azure-core:1.54.1' - api 'com.microsoft.kiota:microsoft-kiota-abstractions:1.8.4' - api 'com.microsoft.kiota:microsoft-kiota-authentication-azure:1.8.4' - implementation 'com.microsoft.kiota:microsoft-kiota-http-okHttp:1.8.4' - implementation 'com.microsoft.kiota:microsoft-kiota-serialization-json:1.8.4' - implementation 'com.microsoft.kiota:microsoft-kiota-serialization-text:1.8.4' - implementation 'com.microsoft.kiota:microsoft-kiota-serialization-form:1.8.4' - implementation 'com.microsoft.kiota:microsoft-kiota-serialization-multipart:1.8.4' + api 'com.microsoft.kiota:microsoft-kiota-abstractions:1.8.7' + api 'com.microsoft.kiota:microsoft-kiota-authentication-azure:1.8.7' + implementation 'com.microsoft.kiota:microsoft-kiota-http-okHttp:1.8.7' + implementation 'com.microsoft.kiota:microsoft-kiota-serialization-json:1.8.7' + implementation 'com.microsoft.kiota:microsoft-kiota-serialization-text:1.8.7' + implementation 'com.microsoft.kiota:microsoft-kiota-serialization-form:1.8.7' + implementation 'com.microsoft.kiota:microsoft-kiota-serialization-multipart:1.8.7' } diff --git a/pom.xml b/pom.xml index f4d6ea1c0..f22ac1911 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ com.microsoft.graph microsoft-graph-core - 3.6.3 + 3.6.4 pom @@ -32,18 +32,18 @@ com.azure azure-core - 1.55.4 + 1.55.5 org.junit.jupiter junit-jupiter-api - 5.13.1 + 5.13.2 test org.junit.jupiter junit-jupiter-params - 5.13.1 + 5.13.2 test diff --git a/src/main/java/com/microsoft/graph/core/CoreConstants.java b/src/main/java/com/microsoft/graph/core/CoreConstants.java index 0d3388fef..b7efb07bb 100644 --- a/src/main/java/com/microsoft/graph/core/CoreConstants.java +++ b/src/main/java/com/microsoft/graph/core/CoreConstants.java @@ -19,7 +19,7 @@ private static class VersionValues { private static final int MINOR = 6; // x-release-please-end // x-release-please-start-patch - private static final int PATCH = 3; + private static final int PATCH = 4; // x-release-please-end } 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..26eacec54 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 + 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"); + } + } }