Skip to content

Commit 358bdca

Browse files
Implement utilities() and waiter() methods in CrossRegionS3Client that delegate to default S3 client. (#798)
1 parent ed1a49b commit 358bdca

File tree

7 files changed

+160
-1
lines changed

7 files changed

+160
-1
lines changed

spring-cloud-aws-dependencies/pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
<jakarta.mail.version>2.1.0</jakarta.mail.version>
3333
<eclipse.jakarta.mail.version>1.0.0</eclipse.jakarta.mail.version>
3434
<aws-crt.version>0.21.12</aws-crt.version>
35+
<mockito.version>5.3.1</mockito.version>
3536
</properties>
3637

3738
<dependencyManagement>

spring-cloud-aws-s3-parent/spring-cloud-aws-s3-cross-region-client/pom.xml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,16 @@
2222
<groupId>org.springframework</groupId>
2323
<artifactId>spring-core</artifactId>
2424
</dependency>
25+
<dependency>
26+
<groupId>org.testcontainers</groupId>
27+
<artifactId>localstack</artifactId>
28+
<scope>test</scope>
29+
</dependency>
30+
<dependency>
31+
<groupId>org.testcontainers</groupId>
32+
<artifactId>junit-jupiter</artifactId>
33+
<scope>test</scope>
34+
</dependency>
2535
</dependencies>
2636

2737
<build>

spring-cloud-aws-s3-parent/spring-cloud-aws-s3-cross-region-client/src/main/java/io/awspring/cloud/s3/crossregion/CrossRegionS3Client.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@
2626
import software.amazon.awssdk.regions.Region;
2727
import software.amazon.awssdk.services.s3.S3Client;
2828
import software.amazon.awssdk.services.s3.S3ClientBuilder;
29+
import software.amazon.awssdk.services.s3.S3Utilities;
2930
import software.amazon.awssdk.services.s3.model.*;
31+
import software.amazon.awssdk.services.s3.waiters.S3Waiter;
3032
import software.amazon.awssdk.utils.SdkAutoCloseable;
3133

3234
public class CrossRegionS3Client extends AbstractCrossRegionS3Client {
@@ -154,4 +156,24 @@ public HeadBucketResponse headBucket(HeadBucketRequest request) throws AwsServic
154156
public ListObjectsResponse listObjects(ListObjectsRequest request) throws AwsServiceException, SdkClientException {
155157
return executeInBucketRegion(request.bucket(), c -> c.listObjects(request), false);
156158
}
159+
160+
/**
161+
* Returns {@link S3Utilities} that use the default S3 client.
162+
*
163+
* @return the S3 utilities
164+
*/
165+
@Override
166+
public S3Utilities utilities() {
167+
return executeInDefaultRegion(S3Client::utilities);
168+
}
169+
170+
/**
171+
* Returns {@link S3Waiter} that use the default S3 client.
172+
*
173+
* @return the S3 waiter
174+
*/
175+
@Override
176+
public S3Waiter waiter() {
177+
return executeInDefaultRegion(S3Client::waiter);
178+
}
157179
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/*
2+
* Copyright 2013-2022 the original author or authors.
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+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.awspring.cloud.s3.crossregion;
17+
18+
import static org.assertj.core.api.Assertions.assertThat;
19+
import static org.testcontainers.shaded.org.awaitility.Awaitility.await;
20+
21+
import java.net.URL;
22+
import java.util.concurrent.CompletableFuture;
23+
import java.util.concurrent.atomic.AtomicReference;
24+
import org.junit.jupiter.api.BeforeAll;
25+
import org.junit.jupiter.api.Test;
26+
import org.testcontainers.containers.localstack.LocalStackContainer;
27+
import org.testcontainers.junit.jupiter.Container;
28+
import org.testcontainers.junit.jupiter.Testcontainers;
29+
import org.testcontainers.utility.DockerImageName;
30+
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
31+
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
32+
import software.amazon.awssdk.core.internal.waiters.ResponseOrException;
33+
import software.amazon.awssdk.core.waiters.WaiterResponse;
34+
import software.amazon.awssdk.regions.Region;
35+
import software.amazon.awssdk.services.s3.S3Client;
36+
import software.amazon.awssdk.services.s3.model.HeadBucketResponse;
37+
38+
/**
39+
* Integration tests for {@link CrossRegionS3Client}.
40+
*
41+
* @author Maciej Walkowiak
42+
*/
43+
@Testcontainers
44+
class CrossRegionS3ClientIntegrationTests {
45+
46+
@Container
47+
static LocalStackContainer localstack = new LocalStackContainer(
48+
DockerImageName.parse("localstack/localstack:1.4.0"));
49+
50+
private static S3Client client;
51+
52+
@BeforeAll
53+
static void beforeAll() {
54+
// region and credentials are irrelevant for test, but must be added to make
55+
// test work on environments without AWS cli configured
56+
StaticCredentialsProvider credentialsProvider = StaticCredentialsProvider
57+
.create(AwsBasicCredentials.create(localstack.getAccessKey(), localstack.getSecretKey()));
58+
client = new CrossRegionS3Client(
59+
S3Client.builder().region(Region.of(localstack.getRegion())).credentialsProvider(credentialsProvider)
60+
.endpointOverride(localstack.getEndpointOverride(LocalStackContainer.Service.S3)));
61+
}
62+
63+
@Test
64+
void utilitiesDelegateToDefaultClient() {
65+
URL url = client.utilities().getUrl(r -> r.key("key").bucket("foo"));
66+
assertThat(url).isNotNull();
67+
}
68+
69+
@Test
70+
void waiterDelegateToDefaultClient() {
71+
AtomicReference<ResponseOrException<HeadBucketResponse>> result = new AtomicReference<>();
72+
CompletableFuture.runAsync(() -> {
73+
WaiterResponse<HeadBucketResponse> bucket = client.waiter().waitUntilBucketExists(r -> r.bucket("bucket"));
74+
result.set(bucket.matched());
75+
});
76+
77+
client.createBucket(r -> r.bucket("bucket"));
78+
79+
await().untilAsserted(() -> {
80+
assertThat(result.get()).isNotNull();
81+
assertThat(result.get().response()).satisfies(it -> {
82+
assertThat(it).isPresent();
83+
});
84+
});
85+
}
86+
87+
}

spring-cloud-aws-s3-parent/spring-cloud-aws-s3-cross-region-client/src/test/java/io/awspring/cloud/s3/crossregion/CrossRegionS3ClientTests.java

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,12 @@
1818
import static org.assertj.core.api.Assertions.assertThat;
1919
import static org.assertj.core.api.Assertions.assertThatThrownBy;
2020
import static org.mockito.ArgumentMatchers.any;
21-
import static org.mockito.Mockito.*;
21+
import static org.mockito.Mockito.mock;
22+
import static org.mockito.Mockito.never;
23+
import static org.mockito.Mockito.times;
24+
import static org.mockito.Mockito.verify;
25+
import static org.mockito.Mockito.verifyNoInteractions;
26+
import static org.mockito.Mockito.when;
2227

2328
import java.util.HashMap;
2429
import java.util.Map;
@@ -32,10 +37,12 @@
3237
import software.amazon.awssdk.regions.Region;
3338
import software.amazon.awssdk.services.s3.S3Client;
3439
import software.amazon.awssdk.services.s3.S3ClientBuilder;
40+
import software.amazon.awssdk.services.s3.S3Utilities;
3541
import software.amazon.awssdk.services.s3.model.CreateBucketRequest;
3642
import software.amazon.awssdk.services.s3.model.HeadBucketRequest;
3743
import software.amazon.awssdk.services.s3.model.ListObjectsRequest;
3844
import software.amazon.awssdk.services.s3.model.S3Exception;
45+
import software.amazon.awssdk.services.s3.waiters.S3Waiter;
3946

4047
/**
4148
* Unit tests for {@link CrossRegionS3Client}. Integration testing with Localstack is not possible due to:
@@ -159,6 +166,22 @@ void headBucketUsedWhenHeaderMissing() {
159166
verify(defaultClient, times(1)).createBucket(CreateBucketRequest.builder().bucket("first-bucket").build());
160167
}
161168

169+
@Test
170+
void utilitiesUsesDefaultClient() {
171+
S3Utilities utilities = mock(S3Utilities.class);
172+
when(defaultClient.utilities()).thenReturn(utilities);
173+
crossRegionS3Client.utilities().getUrl(r -> r.bucket("first-bucket"));
174+
verify(defaultClient).utilities();
175+
}
176+
177+
@Test
178+
void waiterUsesDefaultClient() {
179+
S3Waiter waiter = mock(S3Waiter.class);
180+
when(defaultClient.waiter()).thenReturn(waiter);
181+
crossRegionS3Client.waiter().waitUntilBucketExists(r -> r.bucket("first-bucket"));
182+
verify(defaultClient).waiter();
183+
}
184+
162185
@SuppressWarnings("unchecked")
163186
private void createBucket(String s, Region region) {
164187
when(defaultClient.listObjects(any(Consumer.class))).thenCallRealMethod();
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<configuration>
2+
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
3+
<encoder>
4+
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n</pattern>
5+
</encoder>
6+
</appender>
7+
8+
<root level="INFO">
9+
<appender-ref ref="STDOUT"/>
10+
</root>
11+
12+
<logger name="org.testcontainers" level="INFO"/>
13+
<logger name="com.github.dockerjava" level="WARN"/>
14+
<logger name="io.awspring" level="INFO"/>
15+
</configuration>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
mock-maker-inline

0 commit comments

Comments
 (0)