View Javadoc
1   package com.github.davidmoten.aws.lw.client;
2   
3   import static org.junit.Assert.assertEquals;
4   import static org.junit.Assert.assertFalse;
5   import static org.junit.Assert.assertTrue;
6   
7   import java.io.ByteArrayInputStream;
8   import java.io.IOException;
9   import java.io.UncheckedIOException;
10  import java.nio.charset.StandardCharsets;
11  import java.util.Arrays;
12  import java.util.HashMap;
13  import java.util.List;
14  import java.util.Map;
15  import java.util.Map.Entry;
16  import java.util.Optional;
17  import java.util.concurrent.TimeUnit;
18  
19  import org.junit.Assert;
20  import org.junit.Test;
21  
22  import com.github.davidmoten.http.test.server.Server;
23  
24  public class ClientTest {
25  
26      private static final HttpClientTesting hc = HttpClientTesting.INSTANCE;
27  
28      private static final Client s3 = Client //
29              .s3() //
30              .region("ap-southeast-2") //
31              .accessKey("123") //
32              .secretKey("456") //
33              .httpClient(hc) //
34              .build();
35  
36      @Test
37      public void test() {
38          Client client = Client //
39                  .s3() //
40                  .region("us-west-1") //
41                  .accessKey("123") //
42                  .secretKey("456") //
43                  .httpClient(hc) //
44                  .build();
45          // create a bucket
46          client //
47                  .path("MyBucket") //
48                  .metadata("category", "something") //
49                  .query("type", "thing") //
50                  .attribute("color", "red") //
51                  .attribute("color", "blue") //
52                  .attributePrefix("Message") //
53                  .attribute("name", "hi") //
54                  .attribute("name", "there") //
55                  .method(HttpMethod.PUT) //
56                  .requestBody("hi there") //
57                  .region("ap-southeast-2") //
58                  .connectTimeout(5, TimeUnit.SECONDS) //
59                  .readTimeout(6, TimeUnit.SECONDS) //
60                  .retryMaxAttempts(1) //
61                  .retryBackoffFactor(1.0) //
62                  .retryInitialInterval(10, TimeUnit.MILLISECONDS) //
63                  .retryMaxInterval(1, TimeUnit.SECONDS) //
64                  .retryJitter(0) //
65                  .execute();
66          assertEquals(
67                  "https://s3.ap-southeast-2.amazonaws.com/MyBucket?type=thing&Attribute.1.Name=color&Attribute.1.Value=red&Attribute.2.Name=color&Attribute.2.Value=blue&Message.1.Name=name&Message.1.Value=hi&Message.2.Name=name&Message.2.Value=there",
68                  hc.endpointUrl.toString());
69          assertEquals("PUT", hc.httpMethod);
70          assertEquals("9b96a1fe1d548cbbc960cc6a0286668fd74a763667b06366fb2324269fcabaa4",
71                  hc.headers.get("x-amz-content-sha256"));
72          String authorization = hc.headers.get("Authorization");
73          assertTrue(authorization.startsWith("AWS4-HMAC-SHA256 Credential="));
74          assertTrue(authorization.contains(
75                  "/ap-southeast-2/s3/aws4_request, SignedHeaders=content-length;host;x-amz-content-sha256;x-amz-date;x-amz-meta-category"));
76          assertEquals("8", hc.headers.get("content-length"));
77          assertEquals("s3.ap-southeast-2.amazonaws.com", hc.headers.get("Host"));
78          assertTrue(hc.headers.get("x-amz-date").endsWith("Z"));
79          assertEquals("something", hc.headers.get("x-amz-meta-category"));
80          assertEquals("hi there", hc.requestBodyString());
81          assertEquals(5000, hc.connectTimeoutMs);
82          assertEquals(6000, hc.readTimeoutMs);
83      }
84  
85      @Test
86      public void testQueryParameterWithoutValue() {
87          Client client = Client //
88                  .s3() //
89                  .region("us-west-1") //
90                  .accessKey("123") //
91                  .secretKey("456") //
92                  .httpClient(hc) //
93                  .build();
94          client //
95                  .path("mybucket", "myobject") //
96                  .query("uploads") //
97                  .method(HttpMethod.POST) //
98                  .execute(); // normally returns uploadId but just want to check url and signature
99                              // right
100         assertEquals("https://s3.us-west-1.amazonaws.com/mybucket/myobject?uploads",
101                 hc.endpointUrl.toString());
102         assertEquals("POST", hc.httpMethod);
103         assertEquals("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
104                 hc.headers.get("x-amz-content-sha256"));
105         String authorization = hc.headers.get("Authorization");
106         assertTrue(authorization.startsWith("AWS4-HMAC-SHA256 Credential="));
107         assertTrue(authorization.contains(
108                 "/us-west-1/s3/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date"));
109         assertEquals("s3.us-west-1.amazonaws.com", hc.headers.get("Host"));
110         assertTrue(hc.headers.get("x-amz-date").endsWith("Z"));
111     }
112 
113     @Test
114     public void testUnsignedPayload() {
115         Client client = Client //
116                 .s3() //
117                 .region("us-west-1") //
118                 .accessKey("123") //
119                 .secretKey("456") //
120                 .httpClient(hc) //
121                 .build();
122         // create a bucket
123         client //
124                 .path("MyBucket") //
125                 .metadata("category", "something") //
126                 .query("type", "thing") //
127                 .attribute("color", "red") //
128                 .attribute("color", "blue") //
129                 .attributePrefix("Message") //
130                 .attribute("name", "hi") //
131                 .attribute("name", "there") //
132                 .method(HttpMethod.PUT) //
133                 .requestBody("hi there") //
134                 .unsignedPayload() //
135                 .region("ap-southeast-2") //
136                 .connectTimeout(5, TimeUnit.SECONDS) //
137                 .readTimeout(6, TimeUnit.SECONDS) //
138                 .execute();
139         assertEquals(
140                 "https://s3.ap-southeast-2.amazonaws.com/MyBucket?type=thing&Attribute.1.Name=color&Attribute.1.Value=red&Attribute.2.Name=color&Attribute.2.Value=blue&Message.1.Name=name&Message.1.Value=hi&Message.2.Name=name&Message.2.Value=there",
141                 hc.endpointUrl.toString());
142         assertEquals("PUT", hc.httpMethod);
143         assertEquals("UNSIGNED-PAYLOAD", hc.headers.get("x-amz-content-sha256"));
144         String authorization = hc.headers.get("Authorization");
145         assertTrue(authorization.startsWith("AWS4-HMAC-SHA256 Credential="));
146         assertTrue(authorization.contains(
147                 "/ap-southeast-2/s3/aws4_request, SignedHeaders=content-length;host;x-amz-content-sha256;x-amz-date;x-amz-meta-category"));
148         assertEquals("8", hc.headers.get("content-length"));
149         assertEquals("s3.ap-southeast-2.amazonaws.com", hc.headers.get("Host"));
150         assertTrue(hc.headers.get("x-amz-date").endsWith("Z"));
151         assertEquals("something", hc.headers.get("x-amz-meta-category"));
152         assertEquals("hi there", hc.requestBodyString());
153         assertEquals(5000, hc.connectTimeoutMs);
154         assertEquals(6000, hc.readTimeoutMs);
155     }
156     
157     @Test
158     public void testRegionNoneUsesUsEast1InSignature() {
159         Client client = Client //
160                 .iam() //
161                 .regionNone()
162                 .accessKey("123") //
163                 .secretKey("456") //
164                 .httpClient(hc) //
165                 .build();
166         // create a bucket
167         client //
168                 .query("Action", "GetUser") //
169                 .query("Version", "2010-05-08") //
170                 .execute();
171         assertEquals(
172                 "https://iam.amazonaws.com/?Action=GetUser&Version=2010-05-08",
173                 hc.endpointUrl.toString());
174         String authorization = hc.headers.get("Authorization");
175         assertTrue(authorization.contains("/us-east-1/iam/aws4_request"));
176         assertEquals("iam.amazonaws.com", hc.headers.get("Host"));
177     }
178 
179     @Test(expected = IllegalArgumentException.class)
180     public void testBadConnectTimeout() {
181         Client //
182                 .s3() //
183                 .region("ap-southeast-2") //
184                 .accessKey("123") //
185                 .secretKey("456") //
186                 .connectTimeout(-1, TimeUnit.SECONDS);
187     }
188 
189     @Test(expected = IllegalArgumentException.class)
190     public void testBadReadTimeout() {
191         Client //
192                 .s3() //
193                 .region("ap-southeast-2") //
194                 .accessKey("123") //
195                 .secretKey("456") //
196                 .readTimeout(-1, TimeUnit.SECONDS);
197     }
198 
199     @Test(expected = IllegalArgumentException.class)
200     public void testBadConnectTimeout2() {
201         s3.path().connectTimeout(-1, TimeUnit.SECONDS);
202     }
203 
204     @Test(expected = IllegalArgumentException.class)
205     public void testBadReadTimeout2() {
206         s3.path().readTimeout(-1, TimeUnit.SECONDS);
207     }
208     
209     @Test(expected = IllegalArgumentException.class)
210     public void testBadRetryInitialInterval() {
211         s3.path().retryInitialInterval(-1, TimeUnit.SECONDS);
212     }
213     
214     @Test(expected = IllegalArgumentException.class)
215     public void testBadRetryMaxInterval() {
216         s3.path().retryMaxInterval(-1, TimeUnit.SECONDS);
217     }
218     
219     @Test(expected = IllegalArgumentException.class)
220     public void testBadRetryMaxAttempts() {
221         s3.path().retryMaxAttempts(-1);
222     }
223     
224     @Test(expected = IllegalArgumentException.class)
225     public void testBadRetryJitter() {
226         s3.path().retryJitter(-1);
227     }
228     
229     @Test(expected = IllegalArgumentException.class)
230     public void testBadRetryBackoffFactor() {
231         s3.path().retryBackoffFactor(-1);
232     }
233 
234     @Test
235     public void testTimeoutsAtClientLevel() {
236         Client client = Client //
237                 .s3() //
238                 .region("ap-southeast-2") //
239                 .accessKey("123") //
240                 .secretKey("456") //
241                 .connectTimeout(5, TimeUnit.SECONDS) //
242                 .readTimeout(6, TimeUnit.SECONDS) //
243                 .httpClient(hc) //
244                 .build();
245         // create a bucket
246         client //
247                 .path("MyBucket") //
248                 .method(HttpMethod.PUT) //
249                 .requestBody("hi there") //
250                 .execute();
251 
252         assertEquals(5000, hc.connectTimeoutMs);
253         assertEquals(6000, hc.readTimeoutMs);
254     }
255 
256     @Test(expected = MaxAttemptsExceededException.class)
257     public void testThrows() {
258         Client client = Client //
259                 .s3() //
260                 .region("ap-southeast-2") //
261                 .accessKey("123") //
262                 .secretKey("456") //
263                 .connectTimeout(5, TimeUnit.SECONDS) //
264                 .readTimeout(6, TimeUnit.SECONDS) //
265                 .httpClient(HttpClientTesting.THROWING) //
266                 .retryMaxAttempts(1) //
267                 .build();
268 
269         // create a bucket
270         client //
271                 .path("MyBucket") //
272                 .method(HttpMethod.PUT) //
273                 .requestBody("hi there") //
274                 .execute();
275     }
276 
277     @Test
278     public void testDefaultClientFromEnvironment() {
279         Map<String, String> map = new HashMap<>();
280         map.put("AWS_REGION", "ap-southeast-2");
281         map.put("AWS_ACCESS_KEY_ID", "123");
282         map.put("AWS_SECRET_ACCESS_KEY", "abc");
283         Client client = Client.s3().environment(name -> map.get(name)).defaultClient().build();
284         assertEquals("ap-southeast-2", client.region().get());
285         Credentials c = client.credentials();
286         assertEquals("123", c.accessKey());
287         assertEquals("abc", c.secretKey());
288         assertFalse(c.sessionToken().isPresent());
289     }
290 
291     @Test
292     public void testDefaultClientFromSystemProperties() {
293         System.setProperty("aws.accessKeyId", "123");
294         System.setProperty("aws.secretKey", "abc");
295         Client client = Client.s3().region("ap-southeast-2").credentialsFromSystemProperties()
296                 .build();
297         assertEquals("ap-southeast-2", client.region().get());
298         Credentials c = client.credentials();
299         assertEquals("123", c.accessKey());
300         assertEquals("abc", c.secretKey());
301         assertFalse(c.sessionToken().isPresent());
302     }
303 
304     @Test
305     public void testNoPathOrUrlSet() {
306         s3.query("number", "four") //
307                 .method(HttpMethod.PUT) //
308                 .execute();
309         assertEquals("https://s3.ap-southeast-2.amazonaws.com/?number=four",
310                 hc.endpointUrl.toString());
311     }
312 
313     @Test
314     public void testUrlSet() {
315         s3.url("https://blah") //
316                 .method(HttpMethod.PUT) //
317                 .execute();
318         assertEquals("https://blah", hc.endpointUrl.toString());
319     }
320 
321     @Test
322     public void testAttribute() {
323         s3.attribute("colour", "blue") //
324                 .attribute("color", "green") //
325                 .method(HttpMethod.PUT) //
326                 .execute();
327         assertEquals(
328                 "https://s3.ap-southeast-2.amazonaws.com/?Attribute.1.Name=colour&Attribute.1.Value=blue&Attribute.2.Name=color&Attribute.2.Value=green",
329                 hc.endpointUrl.toString());
330     }
331 
332     @Test
333     public void testAttributePrefix() {
334         s3.attribute("colour", "blue") //
335                 .attribute("color", "green") //
336                 .attributePrefix("surface") //
337                 .attribute("texture", "rough") //
338                 .method(HttpMethod.PUT) //
339                 .execute();
340         assertEquals(
341                 "https://s3.ap-southeast-2.amazonaws.com/?Attribute.1.Name=colour&Attribute.1.Value=blue&Attribute.2.Name=color&Attribute.2.Value=green&surface.1.Name=texture&surface.1.Value=rough",
342                 hc.endpointUrl.toString());
343     }
344 
345     @Test
346     public void testAttributePrefix2() {
347         s3.attributePrefix("surface") //
348                 .attribute("color", "green") //
349                 .method(HttpMethod.PUT) //
350                 .execute();
351         assertEquals(
352                 "https://s3.ap-southeast-2.amazonaws.com/?surface.1.Name=color&surface.1.Value=green",
353                 hc.endpointUrl.toString());
354     }
355 
356     @Test
357     public void testServerOkResponse2() throws InterruptedException {
358         for (int i = 0; i < 10; i++) {
359             Client client = Client //
360                     .s3() //
361                     .region("ap-southeast-2") //
362                     .accessKey("123") //
363                     .secretKey("456") //
364                     .clock(() -> 1622695846902L) //
365                     .connectTimeout(10, TimeUnit.SECONDS) //
366                     .readTimeout(10, TimeUnit.SECONDS) //
367                     .retryMaxAttempts(1) //
368                     .build();
369             try (Server server = Server.start()) {
370                 server.response().body("<a>hello</a>").add();
371                 String text = client //
372                         .url(server.baseUrl()) //
373                         .requestBody("hi there") //
374                         .responseAsXml() //
375                         .content(); //
376                 assertEquals("hello", text);
377             }
378         }
379     }
380 
381     @Test
382     public void testServerErrorResponse() throws IOException {
383         Client client = Client //
384                 .s3() //
385                 .region("ap-southeast-2") //
386                 .accessKey("123") //
387                 .secretKey("456") //
388                 .clock(() -> 1622695846902L) //
389                 .retryMaxAttempts(1) //
390                 .build();
391         try (Server server = Server.start()) {
392             server.response().body("hello").statusCode(500).add();
393             try {
394                 client.url(server.baseUrl()) //
395                         .requestBody("hi there") //
396                         .responseAsUtf8(); //
397                 Assert.fail();
398             } catch (ServiceException e) {
399                 assertEquals(500, e.statusCode());
400                 assertEquals("hello", e.message());
401             }
402         }
403     }
404 
405     @Test
406     public void testResponseExists() throws IOException {
407         Client client = Client //
408                 .s3() //
409                 .region("ap-southeast-2") //
410                 .accessKey("123") //
411                 .secretKey("456") //
412                 .clock(() -> 1622695846902L) //
413                 .build();
414         try (Server server = Server.start()) {
415             server.response().body("hello").statusCode(404).add();
416             assertFalse(client.url(server.baseUrl()).exists()); //
417         }
418     }
419 
420     @Test
421     public void testServerErrorCustomExceptions() throws IOException {
422         Client client = Client //
423                 .s3() //
424                 .region("ap-southeast-2") //
425                 .accessKey("123") //
426                 .secretKey("456") //
427                 .clock(() -> 1622695846902L) //
428                 .exception(r -> !r.isOk(), r -> new UnsupportedOperationException()) //
429                 .retryMaxAttempts(1)
430                 .build();
431         try (Server server = Server.start()) {
432             server.response().body("hello").statusCode(500).add();
433             try {
434                 client.url(server.baseUrl()) //
435                         .requestBody("hi there") //
436                         .responseAsUtf8(); //
437                 Assert.fail();
438             } catch (UnsupportedOperationException e) {
439                 // all good
440             }
441         }
442     }
443 
444     @Test
445     public void testServerErrorCustomExceptionsPassThrough() throws IOException {
446         Client client = Client //
447                 .s3() //
448                 .region("ap-southeast-2") //
449                 .accessKey("123") //
450                 .secretKey("456") //
451                 .clock(() -> 1622695846902L) //
452                 .exception(r -> !r.isOk() && r.statusCode() == 404,
453                         r -> new UnsupportedOperationException()) //
454                 .retryMaxAttempts(1) //
455                 .build();
456         try (Server server = Server.start()) {
457             server.response().body("hello").statusCode(500).add();
458             try {
459                 client.url(server.baseUrl()) //
460                         .requestBody("hi there") //
461                         .responseAsUtf8(); //
462                 Assert.fail();
463             } catch (ServiceException e) {
464                 // all good
465             }
466         }
467     }
468 
469     @Test
470     public void testServerErrorExceptionFactory() throws IOException {
471         Client client = Client //
472                 .s3() //
473                 .region("ap-southeast-2") //
474                 .accessKey("123") //
475                 .secretKey("456") //
476                 .clock(() -> 1622695846902L) //
477                 .exceptionFactory(r -> {
478                     if (r.isOk()) {
479                         return Optional.empty();
480                     } else {
481                         return Optional.of(new UnsupportedOperationException());
482                     }
483                 }) //
484                 .retryMaxAttempts(1) //
485                 .build();
486         try (Server server = Server.start()) {
487             server.response().body("hello").statusCode(500).add();
488             try {
489                 client.url(server.baseUrl()) //
490                         .requestBody("hi there") //
491                         .responseAsUtf8(); //
492                 Assert.fail();
493             } catch (UnsupportedOperationException e) {
494                 // all good
495             }
496         }
497     }
498     
499     @Test
500     public void testWithServerNoResponseBody() throws IOException {
501         try {
502             Client client = Client //
503                     .s3() //
504                     .region("ap-southeast-2") //
505                     .accessKey("123") //
506                     .secretKey("456") //
507                     .clock(() -> 1622695846902L) //
508                     .build();
509             try (Server server = Server.start()) {
510                 server.response().statusCode(200).add();
511                 String text = client.url(server.baseUrl()) //
512                         .method(HttpMethod.PUT) //
513                         .requestBody("hi there") //
514                         .responseAsUtf8(); //
515                 assertEquals("", text);
516             }
517         } catch (Throwable t) {
518             t.printStackTrace();
519             throw t;
520         }
521     }
522 
523     @Test
524     public void testPresignedUrlWithRequestBody() {
525         Client client = Client //
526                 .s3() //
527                 .region("us-west-1") //
528                 .accessKey("123") //
529                 .secretKey("456") //
530                 .clock(() -> 1622695846902L) //
531                 .build();
532         // create a bucket
533         String presignedUrl = client //
534                 .path("MyBucket") //
535                 .query("type", "thing") //
536                 .method(HttpMethod.PUT) //
537                 .requestBody("hi there") //
538                 .region("ap-southeast-2") //
539                 .presignedUrl(5, TimeUnit.DAYS);
540         assertEquals(
541                 "https://s3.ap-southeast-2.amazonaws.com/MyBucket?type=thing&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=123/20210603/ap-southeast-2/s3/aws4_request&X-Amz-Date=20210603T045046Z&X-Amz-Expires=432000&X-Amz-SignedHeaders=content-length;host;x-amz-content-sha256&X-Amz-Signature=3f27d3fe5e595d787990866d05112cd73e21be2275bf02269b640bc9b7c35ec6",
542                 presignedUrl);
543     }
544 
545     @Test
546     public void testPresignedUrlWithRequestBodyUnsignedPayload() {
547         Client client = Client //
548                 .s3() //
549                 .region("us-west-1") //
550                 .accessKey("123") //
551                 .secretKey("456") //
552                 .clock(() -> 1622695846902L) //
553                 .build();
554         // create a bucket
555         String presignedUrl = client //
556                 .path("MyBucket") //
557                 .query("type", "thing") //
558                 .method(HttpMethod.PUT) //
559                 .requestBody("hi there") //
560                 .region("ap-southeast-2") //
561                 .unsignedPayload() //
562                 .presignedUrl(5, TimeUnit.DAYS);
563         assertEquals(
564                 "https://s3.ap-southeast-2.amazonaws.com/MyBucket?type=thing&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=123/20210603/ap-southeast-2/s3/aws4_request&X-Amz-Date=20210603T045046Z&X-Amz-Expires=432000&X-Amz-SignedHeaders=host;x-amz-content-sha256&X-Amz-Signature=7cf6d7fe3bab3b9f23c08aec974bd8007a1c93d8e5009d4d77d0b742f3a3dcb2",
565                 presignedUrl);
566     }
567 
568     @Test
569     public void testPresignedUrlWithoutRequestBody() {
570         Client client = Client //
571                 .s3() //
572                 .region("us-west-1") //
573                 .accessKey("123") //
574                 .secretKey("456") //
575                 .clock(() -> 1622695846902L) //
576                 .build();
577         // create a bucket
578         String presignedUrl = client //
579                 .path("MyBucket") //
580                 .query("type", "thing") //
581                 .method(HttpMethod.PUT) //
582                 .region("ap-southeast-2") //
583                 .presignedUrl(5, TimeUnit.DAYS);
584         assertEquals(
585                 "https://s3.ap-southeast-2.amazonaws.com/MyBucket?type=thing&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=123/20210603/ap-southeast-2/s3/aws4_request&X-Amz-Date=20210603T045046Z&X-Amz-Expires=432000&X-Amz-SignedHeaders=host;x-amz-content-sha256&X-Amz-Signature=b14df0b38e6a1dadce2f340483bd61db69730da26d571e1f0cfacea993372085",
586                 presignedUrl);
587     }
588 
589     @Test
590     public void testPresignedUrlWithoutRequestBodyWithSessionToken() {
591         Client client = Client //
592                 .s3() //
593                 .region("us-west-1") //
594                 .credentials(Credentials.of("123", "456", "abc")) //
595                 .clock(() -> 1622695846902L) //
596                 .build();
597         // create a bucket
598         String presignedUrl = client //
599                 .path("MyBucket") //
600                 .query("type", "thing") //
601                 .method(HttpMethod.PUT) //
602                 .region("ap-southeast-2") //
603                 .presignedUrl(5, TimeUnit.DAYS);
604         assertEquals(
605                 "https://s3.ap-southeast-2.amazonaws.com/MyBucket?type=thing&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=123/20210603/ap-southeast-2/s3/aws4_request&X-Amz-Date=20210603T045046Z&X-Amz-Expires=432000&X-Amz-SignedHeaders=host;x-amz-content-sha256&X-Amz-Signature=93dd388b414d719042afedc24393baeaf1d7d37bfe6394f125a26aa2b29d3426&X-Amz-Security-Token=abc",
606                 presignedUrl);
607     }
608 
609     @Test
610     public void testPresignedUrlWhenUrlHasPort() {
611         Client client = Client //
612                 .s3() //
613                 .region("us-west-1") //
614                 .accessKey("123") //
615                 .secretKey("456") //
616                 .clock(() -> 1622695846902L) //
617                 .build();
618         // create a bucket
619         String presignedUrl = client //
620                 .url("https://s3.myserver.com:8443") //
621                 .method(HttpMethod.PUT) //
622                 .region("ap-southeast-2") //
623                 .presignedUrl(5, TimeUnit.DAYS);
624         assertEquals(
625                 "https://s3.myserver.com:8443?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=123/20210603/ap-southeast-2/s3/aws4_request&X-Amz-Date=20210603T045046Z&X-Amz-Expires=432000&X-Amz-SignedHeaders=host;x-amz-content-sha256&X-Amz-Signature=e78feecf8da1d5c8029f117bb8bd10779d420ce02e838461e3abfafe7d565a5c",
626                 presignedUrl);
627     }
628 
629     @Test
630     public void testPresignedUrlWithoutQueryParameters() {
631         Client client = Client //
632                 .s3() //
633                 .region("us-west-1") //
634                 .accessKey("123") //
635                 .secretKey("456") //
636                 .clock(() -> 1622695846902L) //
637                 .build();
638         // create a bucket
639         String presignedUrl = client //
640                 .path("MyBucket") //
641                 .method(HttpMethod.PUT) //
642                 .region("ap-southeast-2") //
643                 .presignedUrl(5, TimeUnit.DAYS);
644         assertEquals(
645                 "https://s3.ap-southeast-2.amazonaws.com/MyBucket?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=123/20210603/ap-southeast-2/s3/aws4_request&X-Amz-Date=20210603T045046Z&X-Amz-Expires=432000&X-Amz-SignedHeaders=host;x-amz-content-sha256&X-Amz-Signature=4ffc22f4b86b0514a29994c92bbf0342e2dccc66cdceae670414c847baa338ef",
646                 presignedUrl);
647     }
648 
649     @Test
650     public void testAuthorizationSignedRequest() {
651         Client s3 = Client //
652                 .s3() //
653                 .region("ap-southeast-2") //
654                 .credentials(Credentials.of("123", "456", "789")).clock(() -> 1622695846902L) //
655                 .httpClient(hc) //
656                 .build();
657 
658         s3 //
659                 .path("myBucket/myObject.txt") //
660                 .query("Type", "Thing") //
661                 .header("my-header", "blah") //
662                 .header("my-header", "blah2") //
663                 .requestBody("something") //
664                 .response();
665 
666         assertEquals("GET", hc.httpMethod);
667         assertEquals("something", hc.requestBodyString());
668         assertEquals("https://s3.ap-southeast-2.amazonaws.com/myBucket/myObject.txt?Type=Thing",
669                 hc.endpointUrl.toString());
670         Map<String, String> a = new HashMap<>();
671         a.put("my-header", "blah,blah2");
672         a.put("x-amz-content-sha256",
673                 "3fc9b689459d738f8c88a3a48aa9e33542016b7a4052e001aaa536fca74813cb");
674         a.put("Authorization",
675                 "AWS4-HMAC-SHA256 Credential=123/20210603/ap-southeast-2/s3/aws4_request, SignedHeaders=content-length;host;my-header;x-amz-content-sha256;x-amz-date;x-amz-security-token, Signature=72983b3d44575f7b8fea5dd7148a764a7031122154387a30764c56d171906c80");
676         a.put("Host", "s3.ap-southeast-2.amazonaws.com");
677         a.put("x-amz-date", "20210603T045046Z");
678         a.put("content-length", "" + 9);
679         a.put("x-amz-security-token", "789");
680         for (Entry<String, String> entry : hc.headers.entrySet()) {
681             assertEquals(a.get(entry.getKey()), entry.getValue());
682         }
683         assertEquals(a.size(), hc.headers.size());
684     }
685 
686     @Test(expected = MaxAttemptsExceededException.class)
687     public void testUrlDoesNotExist() {
688         Client s3 = Client.s3().region("ap-southeast-2").accessKey("123").secretKey("456").retryMaxAttempts(1).build();
689         s3.url("https://doesnotexist.z21894649.com").execute();
690     }
691 
692     @Test
693     public void testOtherServiceNames() {
694         Client s3 = Client.s3().region("ap-southeast-2").accessKey("123").secretKey("abc").build();
695         assertEquals("iam", Client.iam().from(s3).build().serviceName());
696         assertEquals("ec2", Client.ec2().from(s3).build().serviceName());
697         assertEquals("lambda", Client.lambda().from(s3).build().serviceName());
698         assertEquals("s3", Client.s3().from(s3).build().serviceName());
699         assertEquals("sns", Client.sns().from(s3).build().serviceName());
700         assertEquals("sqs", Client.sqs().from(s3).build().serviceName());
701         assertEquals("hi", Client.service("hi").from(s3).build().serviceName());
702     }
703     
704     @Test
705     public void testRetriesFailTwiceThenSucceed() {
706         Client client = Client //
707                 .s3() //
708                 .region("ap-southeast-2") //
709                 .accessKey("123") //
710                 .secretKey("456") //
711                 .clock(() -> 1622695846902L) //
712                 .retryInitialInterval(100, TimeUnit.MILLISECONDS) //
713                 .build();
714         try (Server server = Server.start()) {
715             server.response().statusCode(408).body("timed out").add();
716             server.response().statusCode(408).body("timed out").add();
717             server.response().statusCode(200).body("stuff").add();
718             String text = client.url(server.baseUrl()) //
719                     .method(HttpMethod.PUT) //
720                     .requestBody("hi there") //
721                     .responseAsUtf8(); //
722             assertEquals("stuff", text);
723         }
724     }
725 
726     @Test(expected=IllegalArgumentException.class)
727     public void testNegativeRetryInitialInterval() {
728         Client //
729                 .s3() //
730                 .region("ap-southeast-2") //
731                 .accessKey("123") //
732                 .secretKey("456") //
733                 .clock(() -> 1622695846902L) //
734                 .retryInitialInterval(-1, TimeUnit.MILLISECONDS);
735     }
736     
737     @Test(expected=IllegalArgumentException.class)
738     public void testNegativeRetryMaxInterval() {
739         Client //
740                 .s3() //
741                 .region("ap-southeast-2") //
742                 .accessKey("123") //
743                 .secretKey("456") //
744                 .clock(() -> 1622695846902L) //
745                 .retryMaxInterval(-1, TimeUnit.MILLISECONDS);
746     }
747     
748     @Test(expected=IllegalArgumentException.class)
749     public void testNegativeRetryJitter() {
750         Client //
751                 .s3() //
752                 .region("ap-southeast-2") //
753                 .accessKey("123") //
754                 .secretKey("456") //
755                 .clock(() -> 1622695846902L) //
756                 .retryJitter(-1);
757     }
758     
759     @Test(expected=IllegalArgumentException.class)
760     public void testTooLargeRetryJitter() {
761         Client //
762                 .s3() //
763                 .region("ap-southeast-2") //
764                 .accessKey("123") //
765                 .secretKey("456") //
766                 .clock(() -> 1622695846902L) //
767                 .retryJitter(2);
768     }
769     
770     @Test(expected=IllegalArgumentException.class)
771     public void testNegativeRetryMaxAttempts() {
772         Client //
773                 .s3() //
774                 .region("ap-southeast-2") //
775                 .accessKey("123") //
776                 .secretKey("456") //
777                 .clock(() -> 1622695846902L) //
778                 .retryMaxAttempts(-1);
779     }
780     
781     @Test(expected=IllegalArgumentException.class)
782     public void testNegativeRetryBackoffFactor() {
783         Client //
784                 .s3() //
785                 .region("ap-southeast-2") //
786                 .accessKey("123") //
787                 .secretKey("456") //
788                 .clock(() -> 1622695846902L) //
789                 .retryBackoffFactor(-1.0);
790     }
791     
792     @Test
793     public void testRetriesFailTwiceThenHitMaxAttempts() {
794         Client client = Client //
795                 .s3() //
796                 .region("ap-southeast-2") //
797                 .accessKey("123") //
798                 .secretKey("456") //
799                 .clock(() -> 1622695846902L) //
800                 .retryInitialInterval(100, TimeUnit.MILLISECONDS) //
801                 .retryMaxAttempts(2) //
802                 .retryStatusCodes(408) //
803                 .build();
804         try (Server server = Server.start()) {
805             server.response().statusCode(408).body("timed out").add();
806             server.response().statusCode(408).body("timed out").add();
807             server.response().statusCode(200).body("stuff").add();
808             client.url(server.baseUrl()) //
809                     .method(HttpMethod.PUT) //
810                     .requestBody("hi there") //
811                     .responseAsUtf8(); //
812         } catch (ServiceException e) {
813             assertEquals(408, e.statusCode());
814         }
815     }
816 
817     @Test
818     public void testRetriesFailTwiceThenSucceedGivenIOExceptions() {
819         HttpClientTestingWithQueue hc = new HttpClientTestingWithQueue();
820         hc.add(new IOException("boo"));
821         hc.add(new IOException("boo2"));
822         hc.add(createResponseInputStream(200, "stuff"));
823         Client client = Client //
824                 .s3() //
825                 .region("ap-southeast-2") //
826                 .accessKey("123") //
827                 .secretKey("456") //
828                 .clock(() -> 1622695846902L) //
829                 .retryInitialInterval(100, TimeUnit.MILLISECONDS) //
830                 .retryMaxAttempts(0) //
831                 .httpClient(hc) //
832                 .build();
833         String text = client //
834                 .path("myBucket", "myObject.txt") //
835                 .responseAsUtf8();
836         hc.urls().forEach(System.out::println);
837         assertEquals("stuff", text);
838     }
839     
840     @Test
841     public void testRetriesFailTwiceThenThrowFinalIOExceptions() {
842         HttpClientTestingWithQueue hc = new HttpClientTestingWithQueue();
843         hc.add(new IOException("boo"));
844         hc.add(new IOException("boo2"));
845         hc.add(createResponseInputStream(200, "stuff"));
846         Client client = Client //
847                 .s3() //
848                 .region("ap-southeast-2") //
849                 .accessKey("123") //
850                 .secretKey("456") //
851                 .clock(() -> 1622695846902L) //
852                 .retryInitialInterval(100, TimeUnit.MILLISECONDS) //
853                 .retryMaxAttempts(2) //
854                 .retryBackoffFactor(2.0) //
855                 .retryMaxInterval(3, TimeUnit.SECONDS) //
856                 .retryJitter(0) //
857                 .retryStatusCodes(400) //
858                 .retryException(e -> e instanceof IOException && e.getMessage().startsWith("boo")) //
859                 .httpClient(hc) //
860                 .build();
861         try {
862             client //
863                     .path("myBucket", "myObject.txt") //
864                     .responseAsUtf8();
865             Assert.fail();
866         } catch (MaxAttemptsExceededException e) {
867             assertEquals("boo2", e.getCause().getMessage());
868         }
869     }
870 
871     @Test
872     public void testDontRetryException() {
873         HttpClientTestingWithQueue hc = new HttpClientTestingWithQueue();
874         hc.add(new IOException("boo"));
875         Client client = Client //
876                 .s3() //
877                 .region("ap-southeast-2") //
878                 .accessKey("123") //
879                 .secretKey("456") //
880                 .clock(() -> 1622695846902L) //
881                 .retryInitialInterval(100, TimeUnit.MILLISECONDS) //
882                 .retryMaxAttempts(2) //
883                 .retryBackoffFactor(2.0) //
884                 .retryMaxInterval(3, TimeUnit.SECONDS) //
885                 .retryJitter(0) //
886                 .retryStatusCodes(400) //
887                 .retryException(e -> false) //
888                 .httpClient(hc) //
889                 .build();
890         try {
891             client //
892                     .path("myBucket", "myObject.txt") //
893                     .responseAsUtf8();
894             Assert.fail();
895         } catch (UncheckedIOException e) {
896             assertEquals("boo", e.getCause().getMessage());
897         }
898     }
899     
900     private static ResponseInputStream createResponseInputStream(int statusCode, String text) {
901         Map<String, List<String>> headers = new HashMap<>();
902         byte[] bytes = text.getBytes(StandardCharsets.UTF_8);
903         headers.put("Content-Length", Arrays.asList(Integer.toString(bytes.length)));
904         return new ResponseInputStream( //
905                 () -> {
906                 }, //
907                 statusCode, //
908                 headers, //
909                 new ByteArrayInputStream(bytes));
910     }
911     
912 }