14
14
*/
15
15
package software .amazon .awssdk .services .s3 .presignedurl ;
16
16
17
+ import static org .apache .commons .lang3 .RandomStringUtils .randomAscii ;
17
18
import static org .assertj .core .api .Assertions .assertThat ;
18
- import static org .assertj .core .api .Assertions .assertThatThrownBy ;
19
19
20
20
import java .net .URL ;
21
+ import java .nio .charset .StandardCharsets ;
21
22
import java .nio .file .Path ;
22
23
import java .time .Duration ;
23
24
import java .util .ArrayList ;
24
25
import java .util .List ;
25
26
import java .util .UUID ;
26
27
import java .util .concurrent .CompletableFuture ;
27
- import java .util .concurrent .ExecutionException ;
28
28
import java .util .concurrent .TimeUnit ;
29
29
import java .util .stream .Stream ;
30
30
31
31
import org .junit .jupiter .api .AfterAll ;
32
32
import org .junit .jupiter .api .BeforeAll ;
33
- import org .junit .jupiter .api .BeforeEach ;
34
33
import org .junit .jupiter .api .Test ;
35
34
import org .junit .jupiter .api .io .TempDir ;
36
35
import org .junit .jupiter .params .ParameterizedTest ;
40
39
import software .amazon .awssdk .core .ResponseBytes ;
41
40
import software .amazon .awssdk .core .async .AsyncRequestBody ;
42
41
import software .amazon .awssdk .core .async .AsyncResponseTransformer ;
43
- import software .amazon .awssdk .core .exception .SdkClientException ;
44
42
import software .amazon .awssdk .metrics .MetricCollection ;
45
43
import software .amazon .awssdk .metrics .MetricPublisher ;
46
44
import software .amazon .awssdk .services .s3 .S3AsyncClient ;
47
45
import software .amazon .awssdk .services .s3 .S3IntegrationTestBase ;
48
46
import software .amazon .awssdk .services .s3 .model .DeleteObjectRequest ;
49
47
import software .amazon .awssdk .services .s3 .model .GetObjectResponse ;
50
- import software .amazon .awssdk .services .s3 .model .NoSuchKeyException ;
51
48
import software .amazon .awssdk .services .s3 .model .PutObjectRequest ;
52
- import software .amazon .awssdk .services .s3 .model .S3Exception ;
53
49
import software .amazon .awssdk .services .s3 .presigner .S3Presigner ;
54
50
import software .amazon .awssdk .services .s3 .presigner .model .PresignedGetObjectRequest ;
55
51
import software .amazon .awssdk .services .s3 .presignedurl .model .PresignedUrlGetObjectRequest ;
61
57
*/
62
58
public abstract class AsyncPresignedUrlManagerTestSuite extends S3IntegrationTestBase {
63
59
protected static S3Presigner presigner ;
64
- protected AsyncPresignedUrlManager presignedUrlManager ;
60
+ protected static AsyncPresignedUrlManager presignedUrlManager ;
65
61
protected static String testBucket ;
66
62
67
63
@ TempDir
68
64
static Path temporaryFolder ;
69
65
70
66
protected static String testGetObjectKey ;
71
67
protected static String testLargeObjectKey ;
72
- protected static String testNonExistentKey ;
73
68
protected static String testObjectContent ;
74
69
protected static byte [] testLargeObjectContent ;
75
70
@@ -87,12 +82,8 @@ static void setUpTestSuite() throws Exception {
87
82
createBucket (testBucket );
88
83
testGetObjectKey = generateRandomObjectKey ();
89
84
testLargeObjectKey = generateRandomObjectKey () + "-large" ;
90
- testNonExistentKey = generateRandomObjectKey () + "-nonexistent" ;
91
85
testObjectContent = "Hello AsyncPresignedUrlManager Integration Test" ;
92
- testLargeObjectContent = new byte [5 * 1024 * 1024 ];
93
- for (int i = 0 ; i < testLargeObjectContent .length ; i ++) {
94
- testLargeObjectContent [i ] = (byte ) (i % 256 );
95
- }
86
+ testLargeObjectContent = randomAscii (5 * 1024 * 1024 ).getBytes (StandardCharsets .UTF_8 );
96
87
S3TestUtils .putObject (AsyncPresignedUrlManagerTestSuite .class , s3 , testBucket , testGetObjectKey , testObjectContent );
97
88
s3Async .putObject (
98
89
PutObjectRequest .builder ()
@@ -114,10 +105,16 @@ static void setUpTestSuite() throws Exception {
114
105
});
115
106
}
116
107
117
- @ BeforeEach
118
- void setUpEach () {
119
- S3AsyncClient s3AsyncClient = createS3AsyncClient ();
120
- presignedUrlManager = s3AsyncClient .presignedUrlManager ();
108
+ @ AfterAll
109
+ static void tearDownTestSuite () {
110
+ try {
111
+ S3TestUtils .runCleanupTasks (AsyncPresignedUrlManagerTestSuite .class );
112
+ } catch (Exception e ) {
113
+ }
114
+ if (presigner != null ) {
115
+ presigner .close ();
116
+ }
117
+ cleanUpResources ();
121
118
}
122
119
123
120
@ ParameterizedTest (name = "{0}" )
@@ -152,31 +149,6 @@ void getObject_withValidPresignedUrl_savesContentToFile() throws Exception {
152
149
assertThat (downloadFile ).hasContent (testObjectContent );
153
150
}
154
151
155
- @ Test
156
- void getObject_withConsumerBuilder_returnsContent () throws Exception {
157
- URL presignedUrl = createPresignedUrl (testGetObjectKey );
158
-
159
- CompletableFuture <ResponseBytes <GetObjectResponse >> bytesFuture =
160
- presignedUrlManager .getObject (
161
- builder -> builder .presignedUrl (presignedUrl ),
162
- AsyncResponseTransformer .toBytes ());
163
- ResponseBytes <GetObjectResponse > bytesResponse = bytesFuture .get ();
164
-
165
- assertThat (bytesResponse ).isNotNull ();
166
- assertThat (bytesResponse .asUtf8String ()).isEqualTo (testObjectContent );
167
-
168
- Path downloadFile = temporaryFolder .resolve ("consumer-builder-download-" + UUID .randomUUID () + ".txt" );
169
- CompletableFuture <GetObjectResponse > fileFuture =
170
- presignedUrlManager .getObject (
171
- builder -> builder .presignedUrl (presignedUrl ),
172
- AsyncResponseTransformer .toFile (downloadFile ));
173
- GetObjectResponse fileResponse = fileFuture .get ();
174
-
175
- assertThat (fileResponse ).isNotNull ();
176
- assertThat (downloadFile ).exists ();
177
- assertThat (downloadFile ).hasContent (testObjectContent );
178
- }
179
-
180
152
@ ParameterizedTest (name = "{0}" )
181
153
@ MethodSource ("rangeTestData" )
182
154
void getObject_withRangeRequest_returnsSpecifiedRange (String testDescription ,
@@ -191,55 +163,6 @@ void getObject_withRangeRequest_returnsSpecifiedRange(String testDescription,
191
163
assertThat (response .asUtf8String ()).isEqualTo (expectedContent );
192
164
}
193
165
194
- @ ParameterizedTest (name = "{0}" )
195
- @ MethodSource ("errorHandlingTestData" )
196
- void getObject_withInvalidRequest_throwsExpectedException (String testDescription ,
197
- String errorType ,
198
- Class <? extends Exception > expectedExceptionType ) throws Exception {
199
-
200
- PresignedUrlGetObjectRequest request = createErrorRequest (errorType );
201
- CompletableFuture <ResponseBytes <GetObjectResponse >> future =
202
- presignedUrlManager .getObject (request , AsyncResponseTransformer .toBytes ());
203
-
204
- switch (errorType ) {
205
- case "nonExistentKey" :
206
- assertThatThrownBy (future ::get )
207
- .isInstanceOf (ExecutionException .class )
208
- .satisfies (ex -> {
209
- Throwable cause = ex .getCause ();
210
- assertThat (cause ).satisfiesAnyOf (
211
- c -> assertThat (c ).isInstanceOf (NoSuchKeyException .class ),
212
- c -> assertThat (c ).isInstanceOf (SdkClientException .class )
213
- );
214
- });
215
- break ;
216
- case "invalidUrl" :
217
- case "expiredUrl" :
218
- assertThatThrownBy (future ::get )
219
- .isInstanceOf (ExecutionException .class )
220
- .satisfies (ex -> {
221
- Throwable cause = ex .getCause ();
222
- assertThat (cause ).satisfiesAnyOf (
223
- c -> assertThat (c ).isInstanceOf (S3Exception .class ),
224
- c -> assertThat (c ).isInstanceOf (SdkClientException .class )
225
- );
226
- });
227
- break ;
228
- case "malformedUrl" :
229
- assertThatThrownBy (future ::get )
230
- .isInstanceOf (ExecutionException .class )
231
- .satisfies (ex -> {
232
- Throwable cause = ex .getCause ();
233
- // Accept either IllegalArgumentException or network-related exceptions
234
- assertThat (cause ).satisfiesAnyOf (
235
- c -> assertThat (c ).isInstanceOf (IllegalArgumentException .class ),
236
- c -> assertThat (c ).isInstanceOf (SdkClientException .class )
237
- );
238
- });
239
- break ;
240
- }
241
- }
242
-
243
166
@ Test
244
167
void getObject_withMultipleRangeRequestsConcurrently_returnsCorrectContent () throws Exception {
245
168
String concurrentTestKey = uploadTestObject ("concurrent-test" , "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" );
@@ -271,49 +194,7 @@ void getObject_withMultipleRangeRequestsConcurrently_returnsCorrectContent() thr
271
194
}
272
195
273
196
@ Test
274
- void getObject_withLargeObjectRanges_returnsCorrectChunks () throws Exception {
275
- List <CompletableFuture <ResponseBytes <GetObjectResponse >>> futures = new ArrayList <>();
276
- int chunkSize = 1024 * 1024 ;
277
-
278
- for (int i = 0 ; i < 4 ; i ++) {
279
- int start = i * chunkSize ;
280
- int end = start + chunkSize - 1 ;
281
- String range = String .format ("bytes=%d-%d" , start , end );
282
- futures .add (presignedUrlManager .getObject (
283
- createRequestForKey (testLargeObjectKey , range ),
284
- AsyncResponseTransformer .toBytes ()));
285
- }
286
-
287
- CompletableFuture <Void > allFutures = CompletableFuture .allOf (
288
- futures .toArray (new CompletableFuture [0 ]));
289
- allFutures .get (60 , TimeUnit .SECONDS );
290
- for (int i = 0 ; i < futures .size (); i ++) {
291
- ResponseBytes <GetObjectResponse > response = futures .get (i ).get ();
292
- assertThat (response .asByteArray ()).hasSize (chunkSize );
293
- byte [] chunk = response .asByteArray ();
294
- int baseOffset = i * chunkSize ;
295
- for (int j = 0 ; j < chunk .length ; j ++) {
296
- int expectedValue = (baseOffset + j ) % 256 ;
297
- assertThat (chunk [j ]).isEqualTo ((byte ) expectedValue );
298
- }
299
- }
300
- }
301
-
302
- @ Test
303
- void getObject_withLargeObjectToFile_savesCompleteContent () throws Exception {
304
- PresignedUrlGetObjectRequest request = createRequestForKey (testLargeObjectKey );
305
- Path downloadFile = temporaryFolder .resolve ("large-download-" + UUID .randomUUID () + ".bin" );
306
- CompletableFuture <GetObjectResponse > future =
307
- presignedUrlManager .getObject (request , downloadFile );
308
- GetObjectResponse response = future .get ();
309
-
310
- assertThat (response ).isNotNull ();
311
- assertThat (downloadFile ).exists ();
312
- assertThat (downloadFile .toFile ().length ()).isEqualTo (testLargeObjectContent .length );
313
- }
314
-
315
- @ Test
316
- void getObject_withClientMetrics_collectsMetrics () throws Exception {
197
+ void getObject_withLargeObjectToFile_savesCompleteContentAndCollectsMetrics () throws Exception {
317
198
List <MetricCollection > collectedMetrics = new ArrayList <>();
318
199
MetricPublisher metricPublisher = new MetricPublisher () {
319
200
@ Override
@@ -324,43 +205,31 @@ public void publish(MetricCollection metricCollection) {
324
205
public void close () {}
325
206
};
326
207
327
- try (S3AsyncClient clientWithMetrics = S3AsyncClient . builder ()
208
+ try (S3AsyncClient clientWithMetrics = s3AsyncClientBuilder ()
328
209
.overrideConfiguration (o -> o .addMetricPublisher (metricPublisher ))
329
210
.build ()) {
330
211
331
212
AsyncPresignedUrlManager metricsManager = clientWithMetrics .presignedUrlManager ();
332
- PresignedUrlGetObjectRequest request = createRequestForKey (testGetObjectKey );
333
-
334
- CompletableFuture <ResponseBytes <GetObjectResponse >> future =
335
- metricsManager .getObject (request , AsyncResponseTransformer .toBytes ());
336
- ResponseBytes <GetObjectResponse > response = future .get (30 , TimeUnit .SECONDS );
213
+ PresignedUrlGetObjectRequest request = createRequestForKey (testLargeObjectKey );
214
+ Path downloadFile = temporaryFolder .resolve ("large-download-with-metrics-" + UUID .randomUUID () + ".bin" );
215
+
216
+ CompletableFuture <GetObjectResponse > future =
217
+ metricsManager .getObject (request , downloadFile );
218
+ GetObjectResponse response = future .get (60 , TimeUnit .SECONDS );
337
219
338
220
assertThat (response ).isNotNull ();
221
+ assertThat (downloadFile ).exists ();
222
+ assertThat (downloadFile .toFile ().length ()).isEqualTo (testLargeObjectContent .length );
339
223
assertThat (collectedMetrics ).isNotEmpty ();
340
224
}
341
225
}
342
226
343
- @ Test
344
- void getObject_withBuilderPattern_returnsContent () throws Exception {
345
- PresignedUrlGetObjectRequest request = PresignedUrlGetObjectRequest .builder ()
346
- .presignedUrl (createPresignedUrl (testGetObjectKey ))
347
- .build ();
348
-
349
- CompletableFuture <ResponseBytes <GetObjectResponse >> future =
350
- presignedUrlManager .getObject (request , AsyncResponseTransformer .toBytes ());
351
-
352
- ResponseBytes <GetObjectResponse > response = future .get ();
353
- assertThat (response .asUtf8String ()).isEqualTo (testObjectContent );
354
- }
355
-
356
227
static Stream <Arguments > basicFunctionalityTestData () {
357
228
return Stream .of (
358
229
Arguments .of ("getObject_withValidUrl_returnsContent" ,
359
230
testGetObjectKey , testObjectContent ),
360
231
Arguments .of ("getObject_withValidLargeObjectUrl_returnsContent" ,
361
- testLargeObjectKey , null ),
362
- Arguments .of ("getObject_withBuilderPattern_returnsContent" ,
363
- testGetObjectKey , testObjectContent )
232
+ testLargeObjectKey , null )
364
233
);
365
234
}
366
235
@@ -378,32 +247,6 @@ static Stream<Arguments> rangeTestData() {
378
247
);
379
248
}
380
249
381
- static Stream <Arguments > errorHandlingTestData () {
382
- return Stream .of (
383
- Arguments .of ("getObject_withNonExistentKey_throwsNoSuchKeyException" ,
384
- "nonExistentKey" , NoSuchKeyException .class ),
385
- Arguments .of ("getObject_withInvalidUrl_throwsS3Exception" ,
386
- "invalidUrl" , S3Exception .class ),
387
- Arguments .of ("getObject_withExpiredUrl_throwsS3Exception" ,
388
- "expiredUrl" , S3Exception .class ),
389
- Arguments .of ("getObject_withMalformedUrl_throwsIllegalArgumentException" ,
390
- "malformedUrl" , IllegalArgumentException .class )
391
- );
392
- }
393
-
394
- @ AfterAll
395
- static void tearDownTestSuite () {
396
- try {
397
- S3TestUtils .runCleanupTasks (AsyncPresignedUrlManagerTestSuite .class );
398
- } catch (Exception e ) {
399
- }
400
-
401
- if (presigner != null ) {
402
- presigner .close ();
403
- }
404
- cleanUpResources ();
405
- }
406
-
407
250
// Helper methods
408
251
private static String generateRandomObjectKey () {
409
252
return "async-presigned-url-manager-test-" + UUID .randomUUID ();
@@ -429,39 +272,6 @@ private URL createPresignedUrl(String key) {
429
272
return presignedRequest .url ();
430
273
}
431
274
432
- private PresignedUrlGetObjectRequest createErrorRequest (String errorType ) {
433
- switch (errorType ) {
434
- case "nonExistentKey" :
435
- return createRequestForKey (testNonExistentKey );
436
- case "invalidUrl" :
437
- return createRequestForKey ("invalid-key-that-does-not-exist-" + UUID .randomUUID ());
438
- case "expiredUrl" :
439
- PresignedGetObjectRequest expiredRequest = presigner .presignGetObject (r -> r
440
- .getObjectRequest (req -> req .bucket (testBucket ).key (testGetObjectKey ))
441
- .signatureDuration (Duration .ofSeconds (1 ))); // Minimum valid duration
442
- try {
443
- Thread .sleep (2000 );
444
- } catch (InterruptedException e ) {
445
- Thread .currentThread ().interrupt ();
446
- throw new RuntimeException ("Interrupted while waiting for URL to expire" , e );
447
- }
448
-
449
- return PresignedUrlGetObjectRequest .builder ()
450
- .presignedUrl (expiredRequest .url ())
451
- .build ();
452
- case "malformedUrl" :
453
- try {
454
- return PresignedUrlGetObjectRequest .builder ()
455
- .presignedUrl (new URL ("http://invalid-hostname-that-does-not-exist" ))
456
- .build ();
457
- } catch (Exception e ) {
458
- throw new RuntimeException (e );
459
- }
460
- default :
461
- throw new IllegalArgumentException ("Unknown error type: " + errorType );
462
- }
463
- }
464
-
465
275
private String uploadTestObject (String keyPrefix , String content ) {
466
276
String key = keyPrefix + "-" + UUID .randomUUID ();
467
277
S3TestUtils .putObject (AsyncPresignedUrlManagerTestSuite .class , s3 , testBucket , key , content );
0 commit comments