Skip to content

Add AsyncPresignedUrlManager integration tests #6290

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
716b6ad
Amazon DataZone Update: This release adds support for 1) highlighting…
Jul 24, 2025
92dfa68
Amazon Omics Update: Add Git integration and README support for Healt…
Jul 24, 2025
30d8556
Updated endpoints.json and partitions.json.
Jul 24, 2025
8d5886b
Release 2.32.8. Updated CHANGELOG.md, README.md and all pom.xml.
Jul 24, 2025
ed1c3a2
Update to next snapshot version: 2.32.9-SNAPSHOT
Jul 24, 2025
0320721
Amazon AppIntegrations Service Update: Amazon AppIntegrations introdu…
Jul 25, 2025
461da35
AWS Key Management Service Update: Doc only update: fixed grammatical…
Jul 25, 2025
d2c453e
AWS Config Update: Documentation improvements have been made to the E…
Jul 25, 2025
03ae0f0
AWS End User Messaging Social Update: This release introduces new Wha…
Jul 25, 2025
abf62a0
AWS Budgets Update: Adds IPv6 and PrivateLink support for AWS Budgets…
Jul 25, 2025
6206090
Amazon Simple Queue Service Update: Documentation updates for Amazon …
Jul 25, 2025
47a2ab7
AWS Elemental MediaPackage v2 Update: This release adds support for s…
Jul 25, 2025
6751460
Amazon Elastic Compute Cloud Update: Transit Gateway native integrati…
Jul 25, 2025
4f0801c
Release 2.32.9. Updated CHANGELOG.md, README.md and all pom.xml.
Jul 25, 2025
8a11913
Update to next snapshot version: 2.32.10-SNAPSHOT
Jul 25, 2025
c743a0f
AWS IoT SiteWise Update: Add support for native anomaly detection in …
Jul 28, 2025
ced5b95
Amazon OpenSearch Ingestion Update: Add Pipeline Role Arn as an optio…
Jul 28, 2025
cdf14bd
AWS Direct Connect Update: Enable MACSec support and features on Inte…
Jul 28, 2025
9a2358a
Updated endpoints.json and partitions.json.
Jul 28, 2025
d277242
Release 2.32.10. Updated CHANGELOG.md, README.md and all pom.xml.
Jul 28, 2025
6122640
Update to next snapshot version: 2.32.11-SNAPSHOT
Jul 28, 2025
a7addde
Rebase #6269: Update PresignedUrlManager to PresignedUrlExtension naming
jencymaryjoseph Jul 17, 2025
9a729a5
Integ tests
jencymaryjoseph Jul 22, 2025
124771a
Refactor AsyncPresignedUrlManager integration tests to extend S3Integ…
jencymaryjoseph Jul 23, 2025
c46feb6
updated test method names to follow testing guidelines
jencymaryjoseph Jul 23, 2025
1604d29
simplified tests
jencymaryjoseph Jul 25, 2025
d7369b7
added checksum test for largeobjects
jencymaryjoseph Jul 25, 2025
a0fe47c
Merge branch 'feature/master/pre-signed-url-getobject' into feature/i…
jencymaryjoseph Jul 28, 2025
4518aa8
rename in test files, address nit comment, fix rebase issues
jencymaryjoseph Jul 28, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -206,10 +206,10 @@ private void updateRetryStrategyClientConfiguration(SdkClientConfiguration.Build

private SdkClientConfiguration updateSdkClientConfiguration(SdkRequest request, SdkClientConfiguration clientConfiguration) {
List<SdkPlugin> plugins = request.overrideConfiguration().map(c -> c.plugins()).orElse(Collections.emptyList());
SdkClientConfiguration.Builder configuration = clientConfiguration.toBuilder();
if (plugins.isEmpty()) {
return configuration.build();
return clientConfiguration;
}
SdkClientConfiguration.Builder configuration = clientConfiguration.toBuilder();
JsonServiceClientConfigurationBuilder serviceConfigBuilder = new JsonServiceClientConfigurationBuilder(configuration);
for (SdkPlugin plugin : plugins) {
plugin.configureClient(serviceConfigBuilder);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package software.amazon.awssdk.services.s3.presignedurl;

import org.junit.jupiter.api.BeforeAll;
import software.amazon.awssdk.services.s3.S3AsyncClient;

public class AsyncPresignedUrlExtensionIntegrationTest extends AsyncPresignedUrlExtensionTestSuite {

@BeforeAll
static void setUpIntegrationTest() {
S3AsyncClient s3AsyncClient = s3AsyncClientBuilder().build();
presignedUrlExtension = s3AsyncClient.presignedUrlExtension();
}

@Override
protected S3AsyncClient createS3AsyncClient() {
return s3AsyncClientBuilder().build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,303 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package software.amazon.awssdk.services.s3.presignedurl;

import static org.apache.commons.lang3.RandomStringUtils.randomAscii;
import static org.assertj.core.api.Assertions.assertThat;

import java.io.ByteArrayInputStream;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;

import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

import software.amazon.awssdk.core.ResponseBytes;
import software.amazon.awssdk.core.async.AsyncRequestBody;
import software.amazon.awssdk.core.async.AsyncResponseTransformer;
import software.amazon.awssdk.metrics.MetricCollection;
import software.amazon.awssdk.metrics.MetricPublisher;
import software.amazon.awssdk.services.s3.S3AsyncClient;
import software.amazon.awssdk.services.s3.S3IntegrationTestBase;
import software.amazon.awssdk.services.s3.model.DeleteObjectRequest;
import software.amazon.awssdk.services.s3.model.GetObjectResponse;
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
import software.amazon.awssdk.services.s3.presigner.S3Presigner;
import software.amazon.awssdk.services.s3.presigner.model.PresignedGetObjectRequest;
import software.amazon.awssdk.services.s3.presignedurl.model.PresignedUrlDownloadRequest;
import software.amazon.awssdk.services.s3.utils.S3TestUtils;
import software.amazon.awssdk.testutils.service.S3BucketUtils;
import software.amazon.awssdk.utils.Md5Utils;

/**
* Abstract test suite for AsyncPresignedUrlExtension integration tests.
*/
public abstract class AsyncPresignedUrlExtensionTestSuite extends S3IntegrationTestBase {
protected static S3Presigner presigner;
protected static AsyncPresignedUrlExtension presignedUrlExtension;
protected static String testBucket;

@TempDir
static Path temporaryFolder;

protected static String testGetObjectKey;
protected static String testLargeObjectKey;
protected static String testObjectContent;
protected static byte[] testLargeObjectContent;
protected static String expectedLargeObjectMd5;

protected abstract S3AsyncClient createS3AsyncClient();

@BeforeAll
static void setUpTestSuite() throws Exception {
setUp();

presigner = S3Presigner.builder()
.region(DEFAULT_REGION)
.credentialsProvider(CREDENTIALS_PROVIDER_CHAIN)
.build();
testBucket = S3BucketUtils.temporaryBucketName("async-presigned-url-extension-test");
createBucket(testBucket);
testGetObjectKey = generateRandomObjectKey();
testLargeObjectKey = generateRandomObjectKey() + "-large";
testObjectContent = "Hello AsyncPresignedUrlExtension Integration Test";
testLargeObjectContent = randomAscii(5 * 1024 * 1024).getBytes(StandardCharsets.UTF_8);

try (ByteArrayInputStream originalStream = new ByteArrayInputStream(testLargeObjectContent)) {
expectedLargeObjectMd5 = Md5Utils.md5AsBase64(originalStream);
} catch (Exception e) {
throw new RuntimeException("Failed to compute MD5 for test data", e);
}

S3TestUtils.putObject(AsyncPresignedUrlExtensionTestSuite.class, s3, testBucket, testGetObjectKey, testObjectContent);
s3Async.putObject(
PutObjectRequest.builder()
.bucket(testBucket)
.key(testLargeObjectKey)
.build(),
AsyncRequestBody.fromBytes(testLargeObjectContent)
).join();
S3TestUtils.addCleanupTask(AsyncPresignedUrlExtensionTestSuite.class, () -> {
s3.deleteObject(DeleteObjectRequest.builder()
.bucket(testBucket)
.key(testGetObjectKey)
.build());
s3.deleteObject(DeleteObjectRequest.builder()
.bucket(testBucket)
.key(testLargeObjectKey)
.build());
deleteBucketAndAllContents(testBucket);
});
}

@AfterAll
static void tearDownTestSuite() {
try {
S3TestUtils.runCleanupTasks(AsyncPresignedUrlExtensionTestSuite.class);
} catch (Exception e) {
}
if (presigner != null) {
presigner.close();
}
cleanUpResources();
}

@ParameterizedTest(name = "{0}")
@MethodSource("basicFunctionalityTestData")
void getObject_withValidPresignedUrl_returnsContent(String testDescription,
String objectKey,
String expectedContent) throws Exception {
PresignedUrlDownloadRequest request = createRequestForKey(objectKey);

CompletableFuture<ResponseBytes<GetObjectResponse>> future =
presignedUrlExtension.getObject(request, AsyncResponseTransformer.toBytes());
ResponseBytes<GetObjectResponse> response = future.get();

assertThat(response).isNotNull();
if (expectedContent != null) {
assertThat(response.asUtf8String()).isEqualTo(expectedContent);
assertThat(response.response().contentLength()).isEqualTo(expectedContent.length());
} else {
try (ByteArrayInputStream downloadedStream = new ByteArrayInputStream(response.asByteArray())) {
String downloadedMd5 = Md5Utils.md5AsBase64(downloadedStream);
assertThat(downloadedMd5).isEqualTo(expectedLargeObjectMd5);
assertThat(response.asByteArray().length).isEqualTo(testLargeObjectContent.length);
}
assertThat(response.response()).isNotNull();
}
}

@Test
void getObject_withValidPresignedUrl_savesContentToFile() throws Exception {
PresignedUrlDownloadRequest request = createRequestForKey(testGetObjectKey);
Path downloadFile = temporaryFolder.resolve("download-" + UUID.randomUUID() + ".txt");
CompletableFuture<GetObjectResponse> future =
presignedUrlExtension.getObject(request, downloadFile);
GetObjectResponse response = future.get();

assertThat(response).isNotNull();
assertThat(downloadFile).exists();
assertThat(downloadFile).hasContent(testObjectContent);
}

@ParameterizedTest(name = "{0}")
@MethodSource("rangeTestData")
void getObject_withRangeRequest_returnsSpecifiedRange(String testDescription,
String range,
String expectedContent) throws Exception {
PresignedUrlDownloadRequest request = createRequestForKey(testGetObjectKey, range);

CompletableFuture<ResponseBytes<GetObjectResponse>> future =
presignedUrlExtension.getObject(request, AsyncResponseTransformer.toBytes());
ResponseBytes<GetObjectResponse> response = future.get();

assertThat(response.asUtf8String()).isEqualTo(expectedContent);
}

@Test
void getObject_withMultipleRangeRequestsConcurrently_returnsCorrectContent() throws Exception {
String concurrentTestKey = uploadTestObject("concurrent-test", "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ");
List<CompletableFuture<ResponseBytes<GetObjectResponse>>> futures = new ArrayList<>();

futures.add(presignedUrlExtension.getObject(
createRequestForKey(concurrentTestKey, "bytes=0-8"), // "012345678"
AsyncResponseTransformer.toBytes()));
futures.add(presignedUrlExtension.getObject(
createRequestForKey(concurrentTestKey, "bytes=9-17"), // "9ABCDEFGH"
AsyncResponseTransformer.toBytes()));
futures.add(presignedUrlExtension.getObject(
createRequestForKey(concurrentTestKey, "bytes=18-26"), // "IJKLMNOPQ"
AsyncResponseTransformer.toBytes()));
futures.add(presignedUrlExtension.getObject(
createRequestForKey(concurrentTestKey, "bytes=27-35"), // "RSTUVWXYZ"
AsyncResponseTransformer.toBytes()));

CompletableFuture<Void> allFutures = CompletableFuture.allOf(
futures.toArray(new CompletableFuture[0]));
allFutures.get(30, TimeUnit.SECONDS);

StringBuilder result = new StringBuilder();
for (CompletableFuture<ResponseBytes<GetObjectResponse>> future : futures) {
result.append(future.get().asUtf8String());
}

assertThat(result.toString()).isEqualTo("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ");
}

@Test
void getObject_withLargeObjectToFile_savesCompleteContentAndCollectsMetrics() throws Exception {
List<MetricCollection> collectedMetrics = new ArrayList<>();
MetricPublisher metricPublisher = new MetricPublisher() {
@Override
public void publish(MetricCollection metricCollection) {
collectedMetrics.add(metricCollection);
}
@Override
public void close() {}
};

try (S3AsyncClient clientWithMetrics = s3AsyncClientBuilder()
.overrideConfiguration(o -> o.addMetricPublisher(metricPublisher))
.build()) {

AsyncPresignedUrlExtension metricsExtension = clientWithMetrics.presignedUrlExtension();
PresignedUrlDownloadRequest request = createRequestForKey(testLargeObjectKey);
Path downloadFile = temporaryFolder.resolve("large-download-with-metrics-" + UUID.randomUUID() + ".bin");

CompletableFuture<GetObjectResponse> future =
metricsExtension.getObject(request, downloadFile);
GetObjectResponse response = future.get(60, TimeUnit.SECONDS);

assertThat(response).isNotNull();
assertThat(downloadFile).exists();
assertThat(downloadFile.toFile().length()).isEqualTo(testLargeObjectContent.length);
assertThat(collectedMetrics).isNotEmpty();
}
}

static Stream<Arguments> basicFunctionalityTestData() {
return Stream.of(
Arguments.of("getObject_withValidUrl_returnsContent",
testGetObjectKey, testObjectContent),
Arguments.of("getObject_withValidLargeObjectUrl_returnsContent",
testLargeObjectKey, null)
);
}

static Stream<Arguments> rangeTestData() {
String content = "Hello AsyncPresignedUrlExtension Integration Test";
return Stream.of(
Arguments.of("getObject_withPrefix10BytesRange_returnsFirst10Bytes",
"bytes=0-9", content.substring(0, 10)),
Arguments.of("getObject_withSuffix10BytesRange_returnsLast10Bytes",
"bytes=-10", content.substring(content.length() - 10)),
Arguments.of("getObject_withMiddle10BytesRange_returnsMiddle10Bytes",
"bytes=10-19", content.substring(10, 20)),
Arguments.of("getObject_withSingleByteRange_returnsSingleByte",
"bytes=0-0", content.substring(0, 1))
);
}

// Helper methods
private static String generateRandomObjectKey() {
return "async-presigned-url-extension-test-" + UUID.randomUUID();
}

private PresignedUrlDownloadRequest createRequestForKey(String key) {
return PresignedUrlDownloadRequest.builder()
.presignedUrl(createPresignedUrl(key))
.build();
}

private PresignedUrlDownloadRequest createRequestForKey(String key, String range) {
return PresignedUrlDownloadRequest.builder()
.presignedUrl(createPresignedUrl(key))
.range(range)
.build();
}

private URL createPresignedUrl(String key) {
PresignedGetObjectRequest presignedRequest = presigner.presignGetObject(r -> r
.getObjectRequest(req -> req.bucket(testBucket).key(key))
.signatureDuration(Duration.ofMinutes(10)));
return presignedRequest.url();
}

private String uploadTestObject(String keyPrefix, String content) {
String key = keyPrefix + "-" + UUID.randomUUID();
S3TestUtils.putObject(AsyncPresignedUrlExtensionTestSuite.class, s3, testBucket, key, content);

S3TestUtils.addCleanupTask(AsyncPresignedUrlExtensionTestSuite.class, () -> {
s3.deleteObject(DeleteObjectRequest.builder()
.bucket(testBucket)
.key(key)
.build());
});
return key;
}
}