diff --git a/pom.xml b/pom.xml index ffcbcc5..9d7bfdc 100644 --- a/pom.xml +++ b/pom.xml @@ -233,7 +233,7 @@ - + org.apache.maven.plugins maven-assembly-plugin @@ -287,6 +287,11 @@ surefire-testng ${surefire.version} + + org.apache.maven.surefire + surefire-junit-platform + ${surefire.version} + all @@ -381,8 +386,6 @@ 1.7.28 ${project.groupId}.shaded 2.22.2 - - 2021-11-26T09:00:00Z @@ -430,6 +433,13 @@ provided + + org.junit.jupiter + junit-jupiter + 5.8.1 + + provided + com.fasterxml.jackson.dataformat jackson-dataformat-xml diff --git a/src/main/java/org/gaul/s3proxy/junit/S3ProxyExtension.java b/src/main/java/org/gaul/s3proxy/junit/S3ProxyExtension.java new file mode 100644 index 0000000..87c9cd0 --- /dev/null +++ b/src/main/java/org/gaul/s3proxy/junit/S3ProxyExtension.java @@ -0,0 +1,108 @@ +/* + * Copyright 2014-2021 Andrew Gaul + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gaul.s3proxy.junit; + +import java.net.URI; + +import org.gaul.s3proxy.AuthenticationType; +import org.junit.jupiter.api.extension.AfterEachCallback; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExtensionContext; + +/** + * A JUnit 5 Extension that manages an S3Proxy instance which tests + * can use as an S3 API endpoint. + */ +public final class S3ProxyExtension + implements AfterEachCallback, BeforeEachCallback { + + private final S3ProxyJunitCore core; + + public static final class Builder { + + private final S3ProxyJunitCore.Builder builder; + + private Builder() { + builder = new S3ProxyJunitCore.Builder(); + } + + public Builder withCredentials(AuthenticationType authType, + String accessKey, String secretKey) { + builder.withCredentials(authType, accessKey, secretKey); + return this; + } + + public Builder withCredentials(String accessKey, String secretKey) { + builder.withCredentials(accessKey, secretKey); + return this; + } + + public Builder withSecretStore(String path, String password) { + builder.withSecretStore(path, password); + return this; + } + + public Builder withPort(int port) { + builder.withPort(port); + return this; + } + + public Builder withBlobStoreProvider(String blobStoreProvider) { + builder.withBlobStoreProvider(blobStoreProvider); + return this; + } + + public Builder ignoreUnknownHeaders() { + builder.ignoreUnknownHeaders(); + return this; + } + + public S3ProxyExtension build() { + return new S3ProxyExtension(this); + } + } + + private S3ProxyExtension(Builder builder) { + core = new S3ProxyJunitCore(builder.builder); + } + + public static Builder builder() { + return new Builder(); + } + + @Override + public void beforeEach(ExtensionContext extensionContext) throws Exception { + core.beforeEach(); + } + + @Override + public void afterEach(ExtensionContext extensionContext) { + core.afterEach(); + } + + public URI getUri() { + return core.getUri(); + } + + public String getAccessKey() { + return core.getAccessKey(); + } + + public String getSecretKey() { + return core.getSecretKey(); + } +} diff --git a/src/main/java/org/gaul/s3proxy/junit/S3ProxyJunitCore.java b/src/main/java/org/gaul/s3proxy/junit/S3ProxyJunitCore.java new file mode 100644 index 0000000..2003da8 --- /dev/null +++ b/src/main/java/org/gaul/s3proxy/junit/S3ProxyJunitCore.java @@ -0,0 +1,177 @@ +/* + * Copyright 2014-2021 Andrew Gaul + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gaul.s3proxy.junit; + +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.nio.file.Files; +import java.util.Properties; + +import org.apache.commons.io.FileUtils; +import org.eclipse.jetty.util.component.AbstractLifeCycle; +import org.gaul.s3proxy.AuthenticationType; +import org.gaul.s3proxy.S3Proxy; +import org.jclouds.ContextBuilder; +import org.jclouds.blobstore.BlobStore; +import org.jclouds.blobstore.BlobStoreContext; +import org.jclouds.blobstore.domain.StorageMetadata; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class S3ProxyJunitCore { + + private static final Logger logger = LoggerFactory.getLogger( + S3ProxyJunitCore.class); + + private static final String LOCALHOST = "127.0.0.1"; + + private final String accessKey; + private final String secretKey; + private final String endpointFormat; + private final S3Proxy s3Proxy; + + private final BlobStoreContext blobStoreContext; + private URI endpointUri; + private final File blobStoreLocation; + + public static final class Builder { + private AuthenticationType authType = AuthenticationType.NONE; + private String accessKey; + private String secretKey; + private String secretStorePath; + private String secretStorePassword; + private int port = -1; + private boolean ignoreUnknownHeaders; + private String blobStoreProvider = "filesystem"; + + public Builder withCredentials(AuthenticationType authType, + String accessKey, String secretKey) { + this.authType = authType; + this.accessKey = accessKey; + this.secretKey = secretKey; + return this; + } + + public Builder withCredentials(String accessKey, String secretKey) { + return withCredentials(AuthenticationType.AWS_V2_OR_V4, accessKey, + secretKey); + } + + public Builder withSecretStore(String path, String password) { + secretStorePath = path; + secretStorePassword = password; + return this; + } + + public Builder withPort(int port) { + this.port = port; + return this; + } + + public Builder withBlobStoreProvider(String blobStoreProvider) { + this.blobStoreProvider = blobStoreProvider; + return this; + } + + public Builder ignoreUnknownHeaders() { + ignoreUnknownHeaders = true; + return this; + } + + public S3ProxyJunitCore build() { + return new S3ProxyJunitCore(this); + } + } + + S3ProxyJunitCore(Builder builder) { + accessKey = builder.accessKey; + secretKey = builder.secretKey; + + Properties properties = new Properties(); + try { + blobStoreLocation = Files.createTempDirectory("S3Proxy") + .toFile(); + properties.setProperty("jclouds.filesystem.basedir", + blobStoreLocation.getCanonicalPath()); + } catch (IOException e) { + throw new RuntimeException("Unable to initialize Blob Store", e); + } + + blobStoreContext = ContextBuilder.newBuilder( + builder.blobStoreProvider) + .credentials(accessKey, secretKey) + .overrides(properties).build(BlobStoreContext.class); + + S3Proxy.Builder s3ProxyBuilder = S3Proxy.builder() + .blobStore(blobStoreContext.getBlobStore()) + .awsAuthentication(builder.authType, accessKey, secretKey) + .ignoreUnknownHeaders(builder.ignoreUnknownHeaders); + + if (builder.secretStorePath != null || + builder.secretStorePassword != null) { + s3ProxyBuilder.keyStore(builder.secretStorePath, + builder.secretStorePassword); + } + + int port = Math.max(builder.port, 0); + endpointFormat = "http://%s:%d"; + String endpoint = String.format(endpointFormat, LOCALHOST, port); + s3ProxyBuilder.endpoint(URI.create(endpoint)); + + s3Proxy = s3ProxyBuilder.build(); + } + + public final void beforeEach() throws Exception { + logger.debug("S3 proxy is starting"); + s3Proxy.start(); + while (!s3Proxy.getState().equals(AbstractLifeCycle.STARTED)) { + Thread.sleep(10); + } + endpointUri = URI.create(String.format(endpointFormat, LOCALHOST, + s3Proxy.getPort())); + logger.debug("S3 proxy is running"); + } + + public final void afterEach() { + logger.debug("S3 proxy is stopping"); + try { + s3Proxy.stop(); + BlobStore blobStore = blobStoreContext.getBlobStore(); + for (StorageMetadata metadata : blobStore.list()) { + blobStore.deleteContainer(metadata.getName()); + } + blobStoreContext.close(); + } catch (Exception e) { + throw new RuntimeException("Unable to stop S3 proxy", e); + } + FileUtils.deleteQuietly(blobStoreLocation); + logger.debug("S3 proxy has stopped"); + } + + public final URI getUri() { + return endpointUri; + } + + public final String getAccessKey() { + return accessKey; + } + + public final String getSecretKey() { + return secretKey; + } +} diff --git a/src/main/java/org/gaul/s3proxy/junit/S3ProxyRule.java b/src/main/java/org/gaul/s3proxy/junit/S3ProxyRule.java index 506ceb0..b21fbd1 100644 --- a/src/main/java/org/gaul/s3proxy/junit/S3ProxyRule.java +++ b/src/main/java/org/gaul/s3proxy/junit/S3ProxyRule.java @@ -16,25 +16,12 @@ package org.gaul.s3proxy.junit; -import java.io.File; -import java.io.IOException; import java.net.URI; -import java.nio.file.Files; -import java.util.Properties; import com.google.common.annotations.Beta; -import org.apache.commons.io.FileUtils; -import org.eclipse.jetty.util.component.AbstractLifeCycle; import org.gaul.s3proxy.AuthenticationType; -import org.gaul.s3proxy.S3Proxy; -import org.jclouds.ContextBuilder; -import org.jclouds.blobstore.BlobStore; -import org.jclouds.blobstore.BlobStoreContext; -import org.jclouds.blobstore.domain.StorageMetadata; import org.junit.rules.ExternalResource; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * A JUnit Rule that manages an S3Proxy instance which tests can use as an S3 @@ -42,63 +29,45 @@ import org.slf4j.LoggerFactory; */ @Beta public final class S3ProxyRule extends ExternalResource { - private static final Logger logger = LoggerFactory.getLogger( - S3ProxyRule.class); - private static final String LOCALHOST = "127.0.0.1"; - - private final String accessKey; - private final String secretKey; - private final String endpointFormat; - private final S3Proxy s3Proxy; - - private final BlobStoreContext blobStoreContext; - private URI endpointUri; - private final File blobStoreLocation; + private final S3ProxyJunitCore core; public static final class Builder { - private AuthenticationType authType = AuthenticationType.NONE; - private String accessKey; - private String secretKey; - private String secretStorePath; - private String secretStorePassword; - private int port = -1; - private boolean ignoreUnknownHeaders; - private String blobStoreProvider = "filesystem"; - private Builder() { } + private final S3ProxyJunitCore.Builder builder; + + private Builder() { + builder = new S3ProxyJunitCore.Builder(); + } public Builder withCredentials(AuthenticationType authType, - String accessKey, String secretKey) { - this.authType = authType; - this.accessKey = accessKey; - this.secretKey = secretKey; + String accessKey, String secretKey) { + builder.withCredentials(authType, accessKey, secretKey); return this; } public Builder withCredentials(String accessKey, String secretKey) { - return withCredentials(AuthenticationType.AWS_V2_OR_V4, accessKey, - secretKey); + builder.withCredentials(accessKey, secretKey); + return this; } public Builder withSecretStore(String path, String password) { - secretStorePath = path; - secretStorePassword = password; + builder.withSecretStore(path, password); return this; } public Builder withPort(int port) { - this.port = port; + builder.withPort(port); return this; } public Builder withBlobStoreProvider(String blobStoreProvider) { - this.blobStoreProvider = blobStoreProvider; + builder.withBlobStoreProvider(blobStoreProvider); return this; } public Builder ignoreUnknownHeaders() { - ignoreUnknownHeaders = true; + builder.ignoreUnknownHeaders(); return this; } @@ -108,41 +77,7 @@ public final class S3ProxyRule extends ExternalResource { } private S3ProxyRule(Builder builder) { - accessKey = builder.accessKey; - secretKey = builder.secretKey; - - Properties properties = new Properties(); - try { - blobStoreLocation = Files.createTempDirectory("S3ProxyRule") - .toFile(); - properties.setProperty("jclouds.filesystem.basedir", - blobStoreLocation.getCanonicalPath()); - } catch (IOException e) { - throw new RuntimeException("Unable to initialize Blob Store", e); - } - - blobStoreContext = ContextBuilder.newBuilder( - builder.blobStoreProvider) - .credentials(accessKey, secretKey) - .overrides(properties).build(BlobStoreContext.class); - - S3Proxy.Builder s3ProxyBuilder = S3Proxy.builder() - .blobStore(blobStoreContext.getBlobStore()) - .awsAuthentication(builder.authType, accessKey, secretKey) - .ignoreUnknownHeaders(builder.ignoreUnknownHeaders); - - if (builder.secretStorePath != null || - builder.secretStorePassword != null) { - s3ProxyBuilder.keyStore(builder.secretStorePath, - builder.secretStorePassword); - } - - int port = builder.port < 0 ? 0 : builder.port; - endpointFormat = "http://%s:%d"; - String endpoint = String.format(endpointFormat, LOCALHOST, port); - s3ProxyBuilder.endpoint(URI.create(endpoint)); - - s3Proxy = s3ProxyBuilder.build(); + core = new S3ProxyJunitCore(builder.builder); } public static Builder builder() { @@ -151,43 +86,23 @@ public final class S3ProxyRule extends ExternalResource { @Override protected void before() throws Throwable { - logger.debug("S3 proxy is starting"); - s3Proxy.start(); - while (!s3Proxy.getState().equals(AbstractLifeCycle.STARTED)) { - Thread.sleep(10); - } - endpointUri = URI.create(String.format(endpointFormat, LOCALHOST, - s3Proxy.getPort())); - logger.debug("S3 proxy is running"); + core.beforeEach(); } @Override protected void after() { - logger.debug("S3 proxy is stopping"); - try { - s3Proxy.stop(); - BlobStore blobStore = blobStoreContext.getBlobStore(); - for (StorageMetadata metadata : blobStore.list()) { - blobStore.deleteContainer(metadata.getName()); - } - blobStoreContext.close(); - } catch (Exception e) { - throw new RuntimeException("Unable to stop S3 proxy", e); - } - FileUtils.deleteQuietly(blobStoreLocation); - logger.debug("S3 proxy has stopped"); + core.afterEach(); } public URI getUri() { - return endpointUri; + return core.getUri(); } public String getAccessKey() { - return accessKey; + return core.getAccessKey(); } public String getSecretKey() { - return secretKey; + return core.getSecretKey(); } - } diff --git a/src/test/java/org/gaul/s3proxy/junit/S3ProxyExtensionTest.java b/src/test/java/org/gaul/s3proxy/junit/S3ProxyExtensionTest.java new file mode 100644 index 0000000..cc11887 --- /dev/null +++ b/src/test/java/org/gaul/s3proxy/junit/S3ProxyExtensionTest.java @@ -0,0 +1,96 @@ +/* + * Copyright 2014-2021 Andrew Gaul + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gaul.s3proxy.junit; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; + +import com.amazonaws.auth.AWSStaticCredentialsProvider; +import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.client.builder.AwsClientBuilder; +import com.amazonaws.regions.Regions; +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.AmazonS3ClientBuilder; +import com.amazonaws.services.s3.model.Bucket; +import com.amazonaws.services.s3.model.S3ObjectSummary; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +/** + * This is an example of how one would use the S3Proxy JUnit extension in a unit + * test as opposed to a proper test of the S3ProxyExtension class. + */ +public class S3ProxyExtensionTest { + + @RegisterExtension + static final S3ProxyExtension EXTENSION = S3ProxyExtension + .builder() + .withCredentials("access", "secret") + .build(); + + private static final String MY_TEST_BUCKET = "my-test-bucket"; + + private AmazonS3 s3Client; + + @BeforeEach + public final void setUp() throws Exception { + s3Client = AmazonS3ClientBuilder + .standard() + .withCredentials( + new AWSStaticCredentialsProvider( + new BasicAWSCredentials( + EXTENSION.getAccessKey(), EXTENSION.getSecretKey()))) + .withEndpointConfiguration( + new AwsClientBuilder.EndpointConfiguration( + EXTENSION.getUri().toString(), Regions.US_EAST_1.getName())) + .build(); + + s3Client.createBucket(MY_TEST_BUCKET); + } + + @Test + public final void listBucket() { + List buckets = s3Client.listBuckets(); + assertThat(buckets).hasSize(1); + assertThat(buckets.get(0).getName()) + .isEqualTo(MY_TEST_BUCKET); + } + + @Test + public final void uploadFile() throws Exception { + String testInput = "content"; + s3Client.putObject(MY_TEST_BUCKET, "file.txt", testInput); + + List summaries = s3Client + .listObjects(MY_TEST_BUCKET) + .getObjectSummaries(); + assertThat(summaries).hasSize(1); + assertThat(summaries.get(0).getKey()).isEqualTo("file.txt"); + assertThat(summaries.get(0).getSize()).isEqualTo(testInput.length()); + } + + @Test + public final void doesBucketExistV2() { + assertThat(s3Client.doesBucketExistV2(MY_TEST_BUCKET)).isTrue(); + + // Issue #299 + assertThat(s3Client.doesBucketExistV2("nonexistingbucket")).isFalse(); + } +}