Skip to content

Commit 1c80c05

Browse files
Add AsyncPresignedUrlManager integration tests (#6290)
* Amazon DataZone Update: This release adds support for 1) highlighting relevant text in returned results for Search and SearchListings APIs and 2) returning aggregated counts of values for specified attributes for SearchListings API. * Amazon Omics Update: Add Git integration and README support for HealthOmics workflows * Updated endpoints.json and partitions.json. * Release 2.32.8. Updated CHANGELOG.md, README.md and all pom.xml. * Update to next snapshot version: 2.32.9-SNAPSHOT * Amazon AppIntegrations Service Update: Amazon AppIntegrations introduces new configuration capabilities to enable customers to manage iframe permissions, control application refresh behavior (per contact or per browser/cross-contact), and run background applications (service). * AWS Key Management Service Update: Doc only update: fixed grammatical errors. * AWS Config Update: Documentation improvements have been made to the EvaluationModel and DescribeConfigurationRecorders APIs. * AWS End User Messaging Social Update: This release introduces new WhatsApp template management APIs that enable customers to programmatically create and submit templates for approval, monitor approval status, and manage the complete template lifecycle * AWS Budgets Update: Adds IPv6 and PrivateLink support for AWS Budgets in IAD. * Amazon Simple Queue Service Update: Documentation updates for Amazon SQS fair queues feature. * AWS Elemental MediaPackage v2 Update: This release adds support for specifying a preferred input for channels using CMAF ingest. * Amazon Elastic Compute Cloud Update: Transit Gateway native integration with AWS Network Firewall. Adding new enum value for the new Transit Gateway Attachment type. * Release 2.32.9. Updated CHANGELOG.md, README.md and all pom.xml. * Update to next snapshot version: 2.32.10-SNAPSHOT * AWS IoT SiteWise Update: Add support for native anomaly detection in IoT SiteWise using new Computation Model APIs * Amazon OpenSearch Ingestion Update: Add Pipeline Role Arn as an optional parameter to the create / update pipeline APIs as an alternative to passing in the pipeline configuration body * AWS Direct Connect Update: Enable MACSec support and features on Interconnects. * Updated endpoints.json and partitions.json. * Release 2.32.10. Updated CHANGELOG.md, README.md and all pom.xml. * Update to next snapshot version: 2.32.11-SNAPSHOT * Rebase #6269: Update PresignedUrlManager to PresignedUrlExtension naming * Integ tests * Refactor AsyncPresignedUrlManager integration tests to extend S3IntegrationTestBase * updated test method names to follow testing guidelines * simplified tests * added checksum test for largeobjects * rename in test files, address nit comment, fix rebase issues --------- Co-authored-by: AWS <>
1 parent a92d372 commit 1c80c05

File tree

3 files changed

+337
-2
lines changed

3 files changed

+337
-2
lines changed

codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-presignedurl-async.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -206,10 +206,10 @@ private void updateRetryStrategyClientConfiguration(SdkClientConfiguration.Build
206206

207207
private SdkClientConfiguration updateSdkClientConfiguration(SdkRequest request, SdkClientConfiguration clientConfiguration) {
208208
List<SdkPlugin> plugins = request.overrideConfiguration().map(c -> c.plugins()).orElse(Collections.emptyList());
209-
SdkClientConfiguration.Builder configuration = clientConfiguration.toBuilder();
210209
if (plugins.isEmpty()) {
211-
return configuration.build();
210+
return clientConfiguration;
212211
}
212+
SdkClientConfiguration.Builder configuration = clientConfiguration.toBuilder();
213213
JsonServiceClientConfigurationBuilder serviceConfigBuilder = new JsonServiceClientConfigurationBuilder(configuration);
214214
for (SdkPlugin plugin : plugins) {
215215
plugin.configureClient(serviceConfigBuilder);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
package software.amazon.awssdk.services.s3.presignedurl;
16+
17+
import org.junit.jupiter.api.BeforeAll;
18+
import software.amazon.awssdk.services.s3.S3AsyncClient;
19+
20+
public class AsyncPresignedUrlExtensionIntegrationTest extends AsyncPresignedUrlExtensionTestSuite {
21+
22+
@BeforeAll
23+
static void setUpIntegrationTest() {
24+
S3AsyncClient s3AsyncClient = s3AsyncClientBuilder().build();
25+
presignedUrlExtension = s3AsyncClient.presignedUrlExtension();
26+
}
27+
28+
@Override
29+
protected S3AsyncClient createS3AsyncClient() {
30+
return s3AsyncClientBuilder().build();
31+
}
32+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,303 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
package software.amazon.awssdk.services.s3.presignedurl;
16+
17+
import static org.apache.commons.lang3.RandomStringUtils.randomAscii;
18+
import static org.assertj.core.api.Assertions.assertThat;
19+
20+
import java.io.ByteArrayInputStream;
21+
import java.net.URL;
22+
import java.nio.charset.StandardCharsets;
23+
import java.nio.file.Path;
24+
import java.time.Duration;
25+
import java.util.ArrayList;
26+
import java.util.List;
27+
import java.util.UUID;
28+
import java.util.concurrent.CompletableFuture;
29+
import java.util.concurrent.TimeUnit;
30+
import java.util.stream.Stream;
31+
32+
import org.junit.jupiter.api.AfterAll;
33+
import org.junit.jupiter.api.BeforeAll;
34+
import org.junit.jupiter.api.Test;
35+
import org.junit.jupiter.api.io.TempDir;
36+
import org.junit.jupiter.params.ParameterizedTest;
37+
import org.junit.jupiter.params.provider.Arguments;
38+
import org.junit.jupiter.params.provider.MethodSource;
39+
40+
import software.amazon.awssdk.core.ResponseBytes;
41+
import software.amazon.awssdk.core.async.AsyncRequestBody;
42+
import software.amazon.awssdk.core.async.AsyncResponseTransformer;
43+
import software.amazon.awssdk.metrics.MetricCollection;
44+
import software.amazon.awssdk.metrics.MetricPublisher;
45+
import software.amazon.awssdk.services.s3.S3AsyncClient;
46+
import software.amazon.awssdk.services.s3.S3IntegrationTestBase;
47+
import software.amazon.awssdk.services.s3.model.DeleteObjectRequest;
48+
import software.amazon.awssdk.services.s3.model.GetObjectResponse;
49+
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
50+
import software.amazon.awssdk.services.s3.presigner.S3Presigner;
51+
import software.amazon.awssdk.services.s3.presigner.model.PresignedGetObjectRequest;
52+
import software.amazon.awssdk.services.s3.presignedurl.model.PresignedUrlDownloadRequest;
53+
import software.amazon.awssdk.services.s3.utils.S3TestUtils;
54+
import software.amazon.awssdk.testutils.service.S3BucketUtils;
55+
import software.amazon.awssdk.utils.Md5Utils;
56+
57+
/**
58+
* Abstract test suite for AsyncPresignedUrlExtension integration tests.
59+
*/
60+
public abstract class AsyncPresignedUrlExtensionTestSuite extends S3IntegrationTestBase {
61+
protected static S3Presigner presigner;
62+
protected static AsyncPresignedUrlExtension presignedUrlExtension;
63+
protected static String testBucket;
64+
65+
@TempDir
66+
static Path temporaryFolder;
67+
68+
protected static String testGetObjectKey;
69+
protected static String testLargeObjectKey;
70+
protected static String testObjectContent;
71+
protected static byte[] testLargeObjectContent;
72+
protected static String expectedLargeObjectMd5;
73+
74+
protected abstract S3AsyncClient createS3AsyncClient();
75+
76+
@BeforeAll
77+
static void setUpTestSuite() throws Exception {
78+
setUp();
79+
80+
presigner = S3Presigner.builder()
81+
.region(DEFAULT_REGION)
82+
.credentialsProvider(CREDENTIALS_PROVIDER_CHAIN)
83+
.build();
84+
testBucket = S3BucketUtils.temporaryBucketName("async-presigned-url-extension-test");
85+
createBucket(testBucket);
86+
testGetObjectKey = generateRandomObjectKey();
87+
testLargeObjectKey = generateRandomObjectKey() + "-large";
88+
testObjectContent = "Hello AsyncPresignedUrlExtension Integration Test";
89+
testLargeObjectContent = randomAscii(5 * 1024 * 1024).getBytes(StandardCharsets.UTF_8);
90+
91+
try (ByteArrayInputStream originalStream = new ByteArrayInputStream(testLargeObjectContent)) {
92+
expectedLargeObjectMd5 = Md5Utils.md5AsBase64(originalStream);
93+
} catch (Exception e) {
94+
throw new RuntimeException("Failed to compute MD5 for test data", e);
95+
}
96+
97+
S3TestUtils.putObject(AsyncPresignedUrlExtensionTestSuite.class, s3, testBucket, testGetObjectKey, testObjectContent);
98+
s3Async.putObject(
99+
PutObjectRequest.builder()
100+
.bucket(testBucket)
101+
.key(testLargeObjectKey)
102+
.build(),
103+
AsyncRequestBody.fromBytes(testLargeObjectContent)
104+
).join();
105+
S3TestUtils.addCleanupTask(AsyncPresignedUrlExtensionTestSuite.class, () -> {
106+
s3.deleteObject(DeleteObjectRequest.builder()
107+
.bucket(testBucket)
108+
.key(testGetObjectKey)
109+
.build());
110+
s3.deleteObject(DeleteObjectRequest.builder()
111+
.bucket(testBucket)
112+
.key(testLargeObjectKey)
113+
.build());
114+
deleteBucketAndAllContents(testBucket);
115+
});
116+
}
117+
118+
@AfterAll
119+
static void tearDownTestSuite() {
120+
try {
121+
S3TestUtils.runCleanupTasks(AsyncPresignedUrlExtensionTestSuite.class);
122+
} catch (Exception e) {
123+
}
124+
if (presigner != null) {
125+
presigner.close();
126+
}
127+
cleanUpResources();
128+
}
129+
130+
@ParameterizedTest(name = "{0}")
131+
@MethodSource("basicFunctionalityTestData")
132+
void getObject_withValidPresignedUrl_returnsContent(String testDescription,
133+
String objectKey,
134+
String expectedContent) throws Exception {
135+
PresignedUrlDownloadRequest request = createRequestForKey(objectKey);
136+
137+
CompletableFuture<ResponseBytes<GetObjectResponse>> future =
138+
presignedUrlExtension.getObject(request, AsyncResponseTransformer.toBytes());
139+
ResponseBytes<GetObjectResponse> response = future.get();
140+
141+
assertThat(response).isNotNull();
142+
if (expectedContent != null) {
143+
assertThat(response.asUtf8String()).isEqualTo(expectedContent);
144+
assertThat(response.response().contentLength()).isEqualTo(expectedContent.length());
145+
} else {
146+
try (ByteArrayInputStream downloadedStream = new ByteArrayInputStream(response.asByteArray())) {
147+
String downloadedMd5 = Md5Utils.md5AsBase64(downloadedStream);
148+
assertThat(downloadedMd5).isEqualTo(expectedLargeObjectMd5);
149+
assertThat(response.asByteArray().length).isEqualTo(testLargeObjectContent.length);
150+
}
151+
assertThat(response.response()).isNotNull();
152+
}
153+
}
154+
155+
@Test
156+
void getObject_withValidPresignedUrl_savesContentToFile() throws Exception {
157+
PresignedUrlDownloadRequest request = createRequestForKey(testGetObjectKey);
158+
Path downloadFile = temporaryFolder.resolve("download-" + UUID.randomUUID() + ".txt");
159+
CompletableFuture<GetObjectResponse> future =
160+
presignedUrlExtension.getObject(request, downloadFile);
161+
GetObjectResponse response = future.get();
162+
163+
assertThat(response).isNotNull();
164+
assertThat(downloadFile).exists();
165+
assertThat(downloadFile).hasContent(testObjectContent);
166+
}
167+
168+
@ParameterizedTest(name = "{0}")
169+
@MethodSource("rangeTestData")
170+
void getObject_withRangeRequest_returnsSpecifiedRange(String testDescription,
171+
String range,
172+
String expectedContent) throws Exception {
173+
PresignedUrlDownloadRequest request = createRequestForKey(testGetObjectKey, range);
174+
175+
CompletableFuture<ResponseBytes<GetObjectResponse>> future =
176+
presignedUrlExtension.getObject(request, AsyncResponseTransformer.toBytes());
177+
ResponseBytes<GetObjectResponse> response = future.get();
178+
179+
assertThat(response.asUtf8String()).isEqualTo(expectedContent);
180+
}
181+
182+
@Test
183+
void getObject_withMultipleRangeRequestsConcurrently_returnsCorrectContent() throws Exception {
184+
String concurrentTestKey = uploadTestObject("concurrent-test", "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ");
185+
List<CompletableFuture<ResponseBytes<GetObjectResponse>>> futures = new ArrayList<>();
186+
187+
futures.add(presignedUrlExtension.getObject(
188+
createRequestForKey(concurrentTestKey, "bytes=0-8"), // "012345678"
189+
AsyncResponseTransformer.toBytes()));
190+
futures.add(presignedUrlExtension.getObject(
191+
createRequestForKey(concurrentTestKey, "bytes=9-17"), // "9ABCDEFGH"
192+
AsyncResponseTransformer.toBytes()));
193+
futures.add(presignedUrlExtension.getObject(
194+
createRequestForKey(concurrentTestKey, "bytes=18-26"), // "IJKLMNOPQ"
195+
AsyncResponseTransformer.toBytes()));
196+
futures.add(presignedUrlExtension.getObject(
197+
createRequestForKey(concurrentTestKey, "bytes=27-35"), // "RSTUVWXYZ"
198+
AsyncResponseTransformer.toBytes()));
199+
200+
CompletableFuture<Void> allFutures = CompletableFuture.allOf(
201+
futures.toArray(new CompletableFuture[0]));
202+
allFutures.get(30, TimeUnit.SECONDS);
203+
204+
StringBuilder result = new StringBuilder();
205+
for (CompletableFuture<ResponseBytes<GetObjectResponse>> future : futures) {
206+
result.append(future.get().asUtf8String());
207+
}
208+
209+
assertThat(result.toString()).isEqualTo("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ");
210+
}
211+
212+
@Test
213+
void getObject_withLargeObjectToFile_savesCompleteContentAndCollectsMetrics() throws Exception {
214+
List<MetricCollection> collectedMetrics = new ArrayList<>();
215+
MetricPublisher metricPublisher = new MetricPublisher() {
216+
@Override
217+
public void publish(MetricCollection metricCollection) {
218+
collectedMetrics.add(metricCollection);
219+
}
220+
@Override
221+
public void close() {}
222+
};
223+
224+
try (S3AsyncClient clientWithMetrics = s3AsyncClientBuilder()
225+
.overrideConfiguration(o -> o.addMetricPublisher(metricPublisher))
226+
.build()) {
227+
228+
AsyncPresignedUrlExtension metricsExtension = clientWithMetrics.presignedUrlExtension();
229+
PresignedUrlDownloadRequest request = createRequestForKey(testLargeObjectKey);
230+
Path downloadFile = temporaryFolder.resolve("large-download-with-metrics-" + UUID.randomUUID() + ".bin");
231+
232+
CompletableFuture<GetObjectResponse> future =
233+
metricsExtension.getObject(request, downloadFile);
234+
GetObjectResponse response = future.get(60, TimeUnit.SECONDS);
235+
236+
assertThat(response).isNotNull();
237+
assertThat(downloadFile).exists();
238+
assertThat(downloadFile.toFile().length()).isEqualTo(testLargeObjectContent.length);
239+
assertThat(collectedMetrics).isNotEmpty();
240+
}
241+
}
242+
243+
static Stream<Arguments> basicFunctionalityTestData() {
244+
return Stream.of(
245+
Arguments.of("getObject_withValidUrl_returnsContent",
246+
testGetObjectKey, testObjectContent),
247+
Arguments.of("getObject_withValidLargeObjectUrl_returnsContent",
248+
testLargeObjectKey, null)
249+
);
250+
}
251+
252+
static Stream<Arguments> rangeTestData() {
253+
String content = "Hello AsyncPresignedUrlExtension Integration Test";
254+
return Stream.of(
255+
Arguments.of("getObject_withPrefix10BytesRange_returnsFirst10Bytes",
256+
"bytes=0-9", content.substring(0, 10)),
257+
Arguments.of("getObject_withSuffix10BytesRange_returnsLast10Bytes",
258+
"bytes=-10", content.substring(content.length() - 10)),
259+
Arguments.of("getObject_withMiddle10BytesRange_returnsMiddle10Bytes",
260+
"bytes=10-19", content.substring(10, 20)),
261+
Arguments.of("getObject_withSingleByteRange_returnsSingleByte",
262+
"bytes=0-0", content.substring(0, 1))
263+
);
264+
}
265+
266+
// Helper methods
267+
private static String generateRandomObjectKey() {
268+
return "async-presigned-url-extension-test-" + UUID.randomUUID();
269+
}
270+
271+
private PresignedUrlDownloadRequest createRequestForKey(String key) {
272+
return PresignedUrlDownloadRequest.builder()
273+
.presignedUrl(createPresignedUrl(key))
274+
.build();
275+
}
276+
277+
private PresignedUrlDownloadRequest createRequestForKey(String key, String range) {
278+
return PresignedUrlDownloadRequest.builder()
279+
.presignedUrl(createPresignedUrl(key))
280+
.range(range)
281+
.build();
282+
}
283+
284+
private URL createPresignedUrl(String key) {
285+
PresignedGetObjectRequest presignedRequest = presigner.presignGetObject(r -> r
286+
.getObjectRequest(req -> req.bucket(testBucket).key(key))
287+
.signatureDuration(Duration.ofMinutes(10)));
288+
return presignedRequest.url();
289+
}
290+
291+
private String uploadTestObject(String keyPrefix, String content) {
292+
String key = keyPrefix + "-" + UUID.randomUUID();
293+
S3TestUtils.putObject(AsyncPresignedUrlExtensionTestSuite.class, s3, testBucket, key, content);
294+
295+
S3TestUtils.addCleanupTask(AsyncPresignedUrlExtensionTestSuite.class, () -> {
296+
s3.deleteObject(DeleteObjectRequest.builder()
297+
.bucket(testBucket)
298+
.key(key)
299+
.build());
300+
});
301+
return key;
302+
}
303+
}

0 commit comments

Comments
 (0)