Skip to content

Commit 6eaf6df

Browse files
committed
Integration test added
1 parent a790c8b commit 6eaf6df

File tree

2 files changed

+54
-4
lines changed

2 files changed

+54
-4
lines changed

services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/multipart/MultipartDownloaderSubscriber.java

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ public void onNext(AsyncResponseTransformer<GetObjectResponse, GetObjectResponse
138138
getObjectFuture.whenComplete((response, error) -> {
139139
if (error != null) {
140140
log.debug(() -> "Error encountered during GetObjectRequest with partNumber=" + nextPartToGet);
141-
onError(error);
141+
handleError(error);
142142
return;
143143
}
144144
requestMoreIfNeeded(response);
@@ -174,12 +174,20 @@ private void requestMoreIfNeeded(GetObjectResponse response) {
174174
if (totalParts != null && totalParts > 1 && totalComplete < totalParts) {
175175
subscription.request(1);
176176
} else {
177+
validatePartsCount();
177178
log.debug(() -> String.format("Completing multipart download after a total of %d parts downloaded.", totalParts));
178179
subscription.cancel();
179180
}
180181
}
181182
}
182183

184+
/**
185+
* The method used by the Subscriber itself when error occured.
186+
*/
187+
private void handleError(Throwable t) {
188+
onError(t);
189+
}
190+
183191
@Override
184192
public void onError(Throwable t) {
185193
CompletableFuture<GetObjectResponse> partFuture;
@@ -191,7 +199,6 @@ public void onError(Throwable t) {
191199

192200
@Override
193201
public void onComplete() {
194-
validatePartsCount();
195202
future.complete(null);
196203
}
197204

@@ -213,9 +220,8 @@ private void validatePartsCount() {
213220
if (totalParts != null && actualGetCount != totalParts) {
214221
String errorMessage = String.format("PartsCount validation failed. Expected %d, downloaded %d parts.", totalParts,
215222
actualGetCount);
216-
subscription.cancel();
217223
SdkClientException exception = SdkClientException.create(errorMessage);
218-
onError(exception);
224+
handleError(exception);
219225
}
220226
}
221227
}

services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/multipart/S3MultipartClientGetObjectWiremockTest.java

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import static org.junit.jupiter.params.provider.Arguments.arguments;
3535
import static software.amazon.awssdk.services.s3.internal.multipart.utils.MultipartDownloadTestUtils.internalErrorBody;
3636
import static software.amazon.awssdk.services.s3.internal.multipart.utils.MultipartDownloadTestUtils.transformersSuppliers;
37+
import static software.amazon.awssdk.services.s3.multipart.S3MultipartExecutionAttribute.MULTIPART_DOWNLOAD_RESUME_CONTEXT;
3738

3839
import com.github.tomakehurst.wiremock.http.Fault;
3940
import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo;
@@ -58,6 +59,7 @@
5859
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
5960
import software.amazon.awssdk.core.SplittingTransformerConfiguration;
6061
import software.amazon.awssdk.core.async.AsyncResponseTransformer;
62+
import software.amazon.awssdk.core.exception.SdkClientException;
6163
import software.amazon.awssdk.core.internal.async.ByteArrayAsyncResponseTransformer;
6264
import software.amazon.awssdk.core.internal.async.FileAsyncResponseTransformer;
6365
import software.amazon.awssdk.core.internal.async.InputStreamResponseTransformer;
@@ -67,6 +69,7 @@
6769
import software.amazon.awssdk.regions.Region;
6870
import software.amazon.awssdk.services.s3.S3AsyncClient;
6971
import software.amazon.awssdk.services.s3.internal.multipart.utils.MultipartDownloadTestUtils;
72+
import software.amazon.awssdk.services.s3.model.GetObjectRequest;
7073
import software.amazon.awssdk.services.s3.model.GetObjectResponse;
7174
import software.amazon.awssdk.services.s3.model.S3Exception;
7275
import software.amazon.awssdk.services.s3.utils.AsyncResponseTransformerTestSupplier;
@@ -144,6 +147,47 @@ public <T> void errorOnThirdPart_shouldCompleteExceptionallyOnlyPartsGreaterThan
144147
}
145148
}
146149

150+
@ParameterizedTest
151+
@MethodSource("partSizeAndTransformerParams")
152+
public <T> void partCountValidationFailure_shouldThrowException(
153+
AsyncResponseTransformerTestSupplier<T> supplier,
154+
int partSize) {
155+
156+
// To trigger the partCount failure, the resumeContext is used to initialize the actualGetCount larger than the
157+
// totalPart number set in the response. This won't happen in real scenario, just to test if the error can be surfaced
158+
// to the user if the validation fails.
159+
MultipartDownloadResumeContext resumeContext = new MultipartDownloadResumeContext();
160+
resumeContext.addCompletedPart(1);
161+
resumeContext.addCompletedPart(2);
162+
resumeContext.addCompletedPart(3);
163+
resumeContext.addToBytesToLastCompletedParts(3 * partSize);
164+
165+
GetObjectRequest request = GetObjectRequest.builder()
166+
.bucket(BUCKET)
167+
.key(KEY)
168+
.overrideConfiguration(config -> config
169+
.putExecutionAttribute(
170+
MULTIPART_DOWNLOAD_RESUME_CONTEXT,
171+
resumeContext))
172+
.build();
173+
174+
util.stubForPart(BUCKET, KEY, 4, 2, partSize);
175+
176+
// Skip the lazy transformer since the error won't surface unless the content is consumed
177+
AsyncResponseTransformer<GetObjectResponse, T> transformer = supplier.transformer();
178+
if (transformer instanceof InputStreamResponseTransformer || transformer instanceof PublisherAsyncResponseTransformer) {
179+
return;
180+
}
181+
182+
assertThatThrownBy(() -> {
183+
T res = multipartClient.getObject(request, transformer).join();
184+
supplier.body(res);
185+
}).isInstanceOf(CompletionException.class)
186+
.hasCauseInstanceOf(SdkClientException.class)
187+
.hasMessageContaining("PartsCount validation failed. Expected 2, downloaded 4 parts");
188+
189+
}
190+
147191
@ParameterizedTest
148192
@MethodSource("nonRetryableResponseTransformers")
149193
public <T> void errorOnFirstPart_shouldFail(AsyncResponseTransformerTestSupplier<T> supplier) {

0 commit comments

Comments
 (0)