Skip to content

Commit 1ff50f4

Browse files
authoredJun 9, 2023
Added AWS S3 native tests (#1759)
* Added a native test which uses an apache client to connect to aws s3 * Added a native test which uses an apache client to connect to aws s3 * Added a native test which uses a netty client to connect to aws s3 * Increased timeout * Increased timeout
1 parent ac06548 commit 1ff50f4

File tree

12 files changed

+357
-15
lines changed

12 files changed

+357
-15
lines changed
 

‎aws-sdk-v2/src/main/resources/META-INF/native-image/io.micronaut.aws/micronaut-aws-sdk-v2/reflect-config.json

-15
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,4 @@
11
[
2-
{
3-
"name":"org.apache.commons.logging.impl.Jdk14Logger",
4-
"methods":[{"name":"<init>","parameterTypes":["java.lang.String"] }]
5-
},
6-
{
7-
"name":"org.apache.commons.logging.impl.Log4JLogger"
8-
},
9-
{
10-
"name":"org.apache.commons.logging.impl.LogFactoryImpl",
11-
"methods":[{"name":"<init>","parameterTypes":[] }]
12-
},
13-
{
14-
"name":"org.apache.commons.logging.impl.WeakHashtable",
15-
"methods":[{"name":"<init>","parameterTypes":[] }]
16-
},
172
{
183
"name":"software.amazon.awssdk.services.sts.internal.StsWebIdentityCredentialsProviderFactory",
194
"methods":[{"name":"<init>","parameterTypes":[] }]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
plugins {
2+
id 'io.micronaut.test-resources'
3+
}
4+
5+
micronaut {
6+
testResources {
7+
clientTimeout = 300
8+
version = libs.versions.micronaut.testresources.get()
9+
}
10+
}

‎gradle/libs.versions.toml

+2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
micronaut = "4.0.0-M6"
33
micronaut-docs = "2.0.0"
44
micronaut-test = "4.0.0-M6"
5+
micronaut-testresources = "2.0.0-M9"
56
groovy = "4.0.12"
67
spock = "2.3-groovy-4.0"
78

@@ -93,6 +94,7 @@ jetty-server = { module = 'org.eclipse.jetty:jetty-server', version.ref = 'jetty
9394
jcl-over-slf4j = { module = 'org.slf4j:jcl-over-slf4j', version.ref = 'slf4j' }
9495
junit-jupiter-engine = { module = 'org.junit.jupiter:junit-jupiter-engine' }
9596
junit-jupiter-api = { module = 'org.junit.jupiter:junit-jupiter-api' }
97+
junit-jupiter-params = { module = "org.junit.jupiter:junit-jupiter-params" }
9698
junit-platform-engine = { module = "org.junit.platform:junit-platform-suite-engine" }
9799
aws-java-sdk-lambda = { module = 'com.amazonaws:aws-java-sdk-lambda' }
98100

‎settings.gradle.kts

+1
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ include("test-suite-http-server-tck-function-aws-api-gateway-proxy-payloadv1")
4444
include("test-suite-http-server-tck-function-aws-api-gateway-proxy-payloadv2")
4545
include("test-suite-http-server-tck-function-aws-api-proxy-test")
4646
include("test-suite-kotlin")
47+
include("test-suite-s3")
4748

4849
configure<io.micronaut.build.MicronautBuildSettingsExtension> {
4950
useStandardizedProjectNames.set(true)

‎test-suite-s3/build.gradle.kts

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
plugins {
2+
id("java-library")
3+
id("io.micronaut.build.internal.aws-tests-java")
4+
id("io.micronaut.build.internal.aws-native-tests")
5+
id("io.micronaut.build.internal.aws-tests-resources")
6+
}
7+
8+
dependencies {
9+
annotationProcessor(mn.micronaut.inject.java)
10+
annotationProcessor(mnSerde.micronaut.serde.processor)
11+
12+
implementation(projects.micronautAwsSdkV2)
13+
implementation(libs.awssdk.s3)
14+
implementation(mn.micronaut.http)
15+
implementation(mn.micronaut.http.server.netty)
16+
implementation(mnValidation.micronaut.validation)
17+
implementation(mnSerde.micronaut.serde.jackson)
18+
implementation(mnLogging.logback.classic)
19+
20+
testAnnotationProcessor(mn.micronaut.inject.java)
21+
testImplementation(mn.micronaut.http.client)
22+
testImplementation(libs.junit.jupiter.params)
23+
}
24+
25+
micronaut {
26+
importMicronautPlatform.set(false)
27+
testResources {
28+
additionalModules.add("localstack-s3")
29+
}
30+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package example;
2+
3+
import software.amazon.awssdk.core.exception.SdkException;
4+
import software.amazon.awssdk.services.s3.model.Bucket;
5+
import software.amazon.awssdk.services.s3.model.CreateBucketConfiguration;
6+
import software.amazon.awssdk.services.s3.model.CreateBucketRequest;
7+
import software.amazon.awssdk.services.s3.model.CreateBucketResponse;
8+
import software.amazon.awssdk.services.s3.model.DeleteBucketRequest;
9+
import software.amazon.awssdk.services.s3.model.DeleteBucketResponse;
10+
import software.amazon.awssdk.services.s3.model.ListBucketsResponse;
11+
import software.amazon.awssdk.services.s3.model.S3Exception;
12+
13+
import java.util.stream.Collectors;
14+
15+
abstract class AbstractS3BucketController {
16+
17+
CreateBucketRequest buildCreateBucketRequest(String bucketName) {
18+
return CreateBucketRequest.builder()
19+
.bucket(bucketName)
20+
.createBucketConfiguration(CreateBucketConfiguration.builder().build())
21+
.build();
22+
}
23+
24+
DeleteBucketRequest buildDeleteBucketRequest(String bucketName) {
25+
return DeleteBucketRequest.builder()
26+
.bucket(bucketName)
27+
.build();
28+
}
29+
30+
Result buildResult(CreateBucketResponse createBucketResponse) {
31+
return new Result(createBucketResponse.responseMetadata().requestId(),
32+
String.valueOf(createBucketResponse.sdkHttpResponse().statusCode()),
33+
createBucketResponse.location()
34+
);
35+
}
36+
37+
Result buildResult(DeleteBucketResponse response) {
38+
return new Result(response.responseMetadata().requestId(),
39+
String.valueOf(response.sdkHttpResponse().statusCode()),
40+
null);
41+
}
42+
43+
Result buildResult(Throwable exception) {
44+
if (exception instanceof S3Exception s3Exception) {
45+
return new Result(s3Exception.requestId(), String.valueOf(s3Exception.statusCode()), s3Exception.getMessage());
46+
} else if (exception instanceof SdkException sdkException) {
47+
return new Result("N/A", sdkException.getMessage(), sdkException.getLocalizedMessage());
48+
} else {
49+
return new Result("N/A", exception.getMessage(), null);
50+
}
51+
}
52+
53+
ListBucketsResult buildListBucketsResult(ListBucketsResponse listBucketsResponse) {
54+
return new ListBucketsResult(
55+
listBucketsResponse.responseMetadata().requestId(),
56+
String.valueOf(listBucketsResponse.sdkHttpResponse().statusCode()),
57+
listBucketsResponse.buckets().stream().map(Bucket::name).collect(Collectors.toList()));
58+
}
59+
60+
ListBucketsResult buildListBucketsResult(Throwable exception) {
61+
if (exception instanceof S3Exception s3Exception) {
62+
return new ListBucketsResult(s3Exception.requestId(), String.valueOf(s3Exception.statusCode()), null);
63+
} else if (exception instanceof SdkException sdkException) {
64+
return new ListBucketsResult("N/A", sdkException.getMessage(), null);
65+
} else {
66+
return new ListBucketsResult("N/A", exception.getMessage(), null);
67+
}
68+
}
69+
70+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package example;
2+
3+
import io.micronaut.http.annotation.Controller;
4+
import io.micronaut.http.annotation.Delete;
5+
import io.micronaut.http.annotation.Get;
6+
import io.micronaut.http.annotation.Post;
7+
import software.amazon.awssdk.services.s3.S3AsyncClient;
8+
import software.amazon.awssdk.services.s3.model.CreateBucketRequest;
9+
import software.amazon.awssdk.services.s3.model.DeleteBucketRequest;
10+
11+
import java.util.concurrent.CompletableFuture;
12+
13+
@Controller("async/s3/buckets")
14+
public class AsyncS3BucketController extends AbstractS3BucketController {
15+
16+
private final S3AsyncClient s3AsyncClient;
17+
18+
public AsyncS3BucketController(S3AsyncClient s3AsyncClient) {
19+
this.s3AsyncClient = s3AsyncClient;
20+
}
21+
22+
@Post("/{bucketName}")
23+
public CompletableFuture<Result> createBucket(String bucketName) {
24+
CreateBucketRequest createBucketRequest = buildCreateBucketRequest(bucketName);
25+
return s3AsyncClient.createBucket(createBucketRequest)
26+
.thenApply(this::buildResult)
27+
.exceptionally(ex -> ex.getCause() == null ?
28+
buildResult(ex) :
29+
buildResult(ex.getCause()));
30+
}
31+
32+
@Get
33+
public CompletableFuture<ListBucketsResult> listBuckets() {
34+
return s3AsyncClient.listBuckets()
35+
.thenApply(this::buildListBucketsResult)
36+
.exceptionally(ex -> ex.getCause() == null ?
37+
buildListBucketsResult(ex) :
38+
buildListBucketsResult(ex.getCause()));
39+
}
40+
41+
@Delete("/{bucketName}")
42+
public CompletableFuture<Result> deleteBucket(String bucketName) {
43+
DeleteBucketRequest deleteBucketRequest = buildDeleteBucketRequest(bucketName);
44+
return s3AsyncClient.deleteBucket(deleteBucketRequest)
45+
.thenApply(this::buildResult)
46+
.exceptionally(ex -> ex.getCause() == null ?
47+
buildResult(ex) :
48+
buildResult(ex.getCause()));
49+
}
50+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package example;
2+
3+
import io.micronaut.serde.annotation.Serdeable;
4+
import jakarta.annotation.Nullable;
5+
import jakarta.validation.constraints.NotBlank;
6+
import jakarta.validation.constraints.NotNull;
7+
8+
import java.util.List;
9+
10+
@Serdeable
11+
public class ListBucketsResult extends Result{
12+
13+
@Nullable
14+
private List<String> buckets;
15+
16+
public ListBucketsResult(String requestId, @NotNull @NotBlank String status, @Nullable List<String> buckets) {
17+
super(requestId, status, null);
18+
this.buckets = buckets;
19+
}
20+
21+
public List<String> getBuckets() {
22+
return buckets;
23+
}
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package example;
2+
3+
import io.micronaut.serde.annotation.Serdeable;
4+
import jakarta.annotation.Nullable;
5+
import jakarta.validation.constraints.NotBlank;
6+
import jakarta.validation.constraints.NotNull;
7+
8+
@Serdeable
9+
public class Result {
10+
11+
private final String requestId;
12+
13+
@NotNull
14+
@NotBlank
15+
private final String status;
16+
17+
@Nullable
18+
private final String message;
19+
20+
public Result(String requestId, @NotNull @NotBlank String status, @Nullable String message) {
21+
this.requestId = requestId;
22+
this.status = status;
23+
this.message = message;
24+
}
25+
26+
public String getMessage() {
27+
return message;
28+
}
29+
30+
public String getStatus() {
31+
return status;
32+
}
33+
34+
public String getRequestId() {
35+
return requestId;
36+
}
37+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package example;
2+
3+
import io.micronaut.http.annotation.Controller;
4+
import io.micronaut.http.annotation.Delete;
5+
import io.micronaut.http.annotation.Get;
6+
import io.micronaut.http.annotation.Post;
7+
import software.amazon.awssdk.services.s3.S3Client;
8+
import software.amazon.awssdk.services.s3.model.CreateBucketRequest;
9+
import software.amazon.awssdk.services.s3.model.CreateBucketResponse;
10+
import software.amazon.awssdk.services.s3.model.DeleteBucketRequest;
11+
import software.amazon.awssdk.services.s3.model.DeleteBucketResponse;
12+
import software.amazon.awssdk.services.s3.model.ListBucketsResponse;
13+
14+
@Controller("/s3/buckets")
15+
public class S3BucketController extends AbstractS3BucketController {
16+
17+
private final S3Client s3Client;
18+
19+
public S3BucketController(S3Client s3Client) {
20+
this.s3Client = s3Client;
21+
}
22+
23+
@Post("/{bucketName}")
24+
public Result createBucket(String bucketName) {
25+
try {
26+
CreateBucketRequest createBucketRequest = buildCreateBucketRequest(bucketName);
27+
CreateBucketResponse createBucketResponse = s3Client.createBucket(createBucketRequest);
28+
return buildResult(createBucketResponse);
29+
} catch (Exception exception) {
30+
return buildResult(exception);
31+
}
32+
}
33+
34+
@Get
35+
public ListBucketsResult listBuckets() {
36+
try {
37+
ListBucketsResponse response = s3Client.listBuckets();
38+
return buildListBucketsResult(response);
39+
} catch (Exception exception) {
40+
return buildListBucketsResult(exception);
41+
}
42+
}
43+
44+
@Delete("/{bucketName}")
45+
public Result deleteBucket(String bucketName) {
46+
try {
47+
DeleteBucketRequest deleteBucketRequest = buildDeleteBucketRequest(bucketName);
48+
DeleteBucketResponse response = s3Client.deleteBucket(deleteBucketRequest);
49+
return buildResult(response);
50+
} catch (Exception exception) {
51+
return buildResult(exception);
52+
}
53+
}
54+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package example;
2+
3+
import io.micronaut.context.annotation.Property;
4+
import io.micronaut.http.HttpRequest;
5+
import io.micronaut.http.HttpResponse;
6+
import io.micronaut.http.HttpStatus;
7+
import io.micronaut.http.client.HttpClient;
8+
import io.micronaut.http.client.annotation.Client;
9+
import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
10+
import jakarta.inject.Inject;
11+
import org.junit.jupiter.params.ParameterizedTest;
12+
import org.junit.jupiter.params.provider.ValueSource;
13+
14+
import java.util.Optional;
15+
16+
import static org.junit.jupiter.api.Assertions.assertEquals;
17+
import static org.junit.jupiter.api.Assertions.assertNotNull;
18+
import static org.junit.jupiter.api.Assertions.assertNull;
19+
import static org.junit.jupiter.api.Assertions.assertTrue;
20+
21+
@MicronautTest
22+
@Property(name = "micronaut.http.client.read-timeout", value = "300")
23+
public class S3BucketTest {
24+
25+
@Inject
26+
@Client("/")
27+
HttpClient httpClient;
28+
29+
@ParameterizedTest
30+
@ValueSource(strings = {"/s3/buckets", "/async/s3/buckets"})
31+
void test(String uri) {
32+
String bucketName = uri.startsWith("/async") ? "async-test-bucket" : "test-bucket";
33+
34+
// create a new bucket
35+
HttpRequest createBucketRequest = HttpRequest.POST(uri + "/" + bucketName, "");
36+
HttpResponse<Result> createBucketResponse = httpClient.toBlocking().exchange(createBucketRequest, Result.class);
37+
Optional<Result> createBucketResult = createBucketResponse.getBody();
38+
39+
assertTrue(createBucketResult.isPresent());
40+
assertEquals(String.valueOf(HttpStatus.OK.getCode()), createBucketResult.get().getStatus());
41+
assertTrue(createBucketResult.get().getMessage().contains(bucketName));
42+
43+
// list buckets
44+
ListBucketsResult listBucketsResult = httpClient.toBlocking().retrieve(uri, ListBucketsResult.class);
45+
46+
assertNotNull(listBucketsResult);
47+
assertEquals(String.valueOf(HttpStatus.OK.getCode()), listBucketsResult.getStatus());
48+
assertNotNull(listBucketsResult.getBuckets());
49+
assertEquals(1, listBucketsResult.getBuckets().size());
50+
assertEquals(bucketName, listBucketsResult.getBuckets().get(0));
51+
52+
// delete the bucket
53+
HttpRequest deleteBucketRequest = HttpRequest.DELETE(uri + "/" + bucketName, "");
54+
HttpResponse<Result> deleteBucketResponse = httpClient.toBlocking().exchange(deleteBucketRequest, Result.class);
55+
Optional<Result> deleteBucketResult = deleteBucketResponse.getBody();
56+
57+
assertTrue(deleteBucketResult.isPresent());
58+
assertEquals(String.valueOf(HttpStatus.NO_CONTENT.getCode()), deleteBucketResult.get().getStatus());
59+
assertNull(deleteBucketResult.get().getMessage());
60+
61+
// confirm the bucket deleted
62+
listBucketsResult = httpClient.toBlocking().retrieve(uri, ListBucketsResult.class);
63+
64+
assertNotNull(listBucketsResult);
65+
assertEquals(String.valueOf(HttpStatus.OK.getCode()), listBucketsResult.getStatus());
66+
assertNull(listBucketsResult.getBuckets());
67+
}
68+
69+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<configuration>
2+
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
3+
<encoder>
4+
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
5+
</encoder>
6+
</appender>
7+
<root level="info">
8+
<appender-ref ref="STDOUT" />
9+
</root>
10+
</configuration>

0 commit comments

Comments
 (0)
Please sign in to comment.