Skip to content

Commit

Permalink
Merge pull request #272 from mziccard/add-remotegcshelper-test
Browse files Browse the repository at this point in the history
Add RemoteGcsHelper unit tests
  • Loading branch information
aozarov committed Oct 21, 2015
2 parents 99a4091 + 862ea6a commit 0dd3d97
Show file tree
Hide file tree
Showing 2 changed files with 202 additions and 67 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,17 @@

package com.google.gcloud.storage.testing;

import com.google.common.collect.ImmutableMap;
import com.google.gcloud.AuthCredentials;
import com.google.gcloud.storage.BlobInfo;
import com.google.gcloud.RetryParams;
import com.google.gcloud.storage.Storage;
import com.google.gcloud.storage.StorageException;
import com.google.gcloud.storage.StorageOptions;
import com.google.gcloud.storage.testing.RemoteGcsHelper.Option.KeyFromClasspath;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.IOException;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
Expand Down Expand Up @@ -96,35 +93,19 @@ public static String generateBucketName() {
}

/**
* Creates a {@code RemoteGcsHelper} object for the given project id and JSON key path.
* Creates a {@code RemoteGcsHelper} object for the given project id and JSON key input stream.
*
* @param projectId id of the project to be used for running the tests
* @param keyPath path to the JSON key to be used for running the tests
* @param options creation options
* @param keyStream input stream for a JSON key
* @return A {@code RemoteGcsHelper} object for the provided options.
* @throws com.google.gcloud.storage.testing.RemoteGcsHelper.GcsHelperException if the file pointed by
* {@code keyPath} does not exist
* @throws com.google.gcloud.storage.testing.RemoteGcsHelper.GcsHelperException if
* {@code keyStream} is not a valid JSON key stream
*/
public static RemoteGcsHelper create(String projectId, String keyPath, Option... options)
public static RemoteGcsHelper create(String projectId, InputStream keyStream)
throws GcsHelperException {
boolean keyFromClassPath = false;
Map<Class<? extends Option>, Option> optionsMap = Option.asImmutableMap(options);
if (optionsMap.containsKey(KeyFromClasspath.class)) {
keyFromClassPath =
((KeyFromClasspath) optionsMap.get(KeyFromClasspath.class)).keyFromClasspath();
}
try {
InputStream keyFileStream;
if (keyFromClassPath) {
keyFileStream = RemoteGcsHelper.class.getResourceAsStream(keyPath);
if (keyFileStream == null) {
throw new FileNotFoundException(keyPath + " not found in classpath");
}
} else {
keyFileStream = new FileInputStream(keyPath);
}
StorageOptions storageOptions = StorageOptions.builder()
.authCredentials(AuthCredentials.createForJson(keyFileStream))
.authCredentials(AuthCredentials.createForJson(keyStream))
.projectId(projectId)
.retryParams(RetryParams.builder()
.retryMaxAttempts(10)
Expand All @@ -137,6 +118,28 @@ public static RemoteGcsHelper create(String projectId, String keyPath, Option...
.readTimeout(60000)
.build();
return new RemoteGcsHelper(storageOptions);
} catch (IOException ex) {
if (log.isLoggable(Level.WARNING)) {
log.log(Level.WARNING, ex.getMessage());
}
throw GcsHelperException.translate(ex);
}
}

/**
* Creates a {@code RemoteGcsHelper} object for the given project id and JSON key path.
*
* @param projectId id of the project to be used for running the tests
* @param keyPath path to the JSON key to be used for running the tests
* @return A {@code RemoteGcsHelper} object for the provided options.
* @throws com.google.gcloud.storage.testing.RemoteGcsHelper.GcsHelperException if the file
* pointed by {@code keyPath} does not exist
*/
public static RemoteGcsHelper create(String projectId, String keyPath)
throws GcsHelperException {
try {
InputStream keyFileStream = new FileInputStream(keyPath);
return create(projectId, keyFileStream);
} catch (FileNotFoundException ex) {
if (log.isLoggable(Level.WARNING)) {
log.log(Level.WARNING, ex.getMessage());
Expand All @@ -154,13 +157,12 @@ public static RemoteGcsHelper create(String projectId, String keyPath, Option...
* Creates a {@code RemoteGcsHelper} object. Project id and path to JSON key are read from two
* environment variables: {@code GCLOUD_TESTS_PROJECT_ID} and {@code GCLOUD_TESTS_KEY}.
*
* @param options creation options
* @return A {@code RemoteGcsHelper} object for the provided options.
* @throws com.google.gcloud.storage.testing.RemoteGcsHelper.GcsHelperException if environment variables
* {@code GCLOUD_TESTS_PROJECT_ID} and {@code GCLOUD_TESTS_KEY} are not set or if the file
* pointed by {@code GCLOUD_TESTS_KEY} does not exist
* @throws com.google.gcloud.storage.testing.RemoteGcsHelper.GcsHelperException if environment
* variables {@code GCLOUD_TESTS_PROJECT_ID} and {@code GCLOUD_TESTS_KEY} are not set or if
* the file pointed by {@code GCLOUD_TESTS_KEY} does not exist
*/
public static RemoteGcsHelper create(Option... options) throws GcsHelperException {
public static RemoteGcsHelper create() throws GcsHelperException {
String projectId = System.getenv(PROJECT_ID_ENV_VAR);
String keyPath = System.getenv(PRIVATE_KEY_ENV_VAR);
if (projectId == null) {
Expand All @@ -177,7 +179,7 @@ public static RemoteGcsHelper create(Option... options) throws GcsHelperExceptio
}
throw new GcsHelperException(message);
}
return create(projectId, keyPath, options);
return create(projectId, keyPath);
}

private static class DeleteBucketTask implements Callable<Boolean> {
Expand Down Expand Up @@ -210,42 +212,6 @@ public Boolean call() throws Exception {
}
}

public static abstract class Option implements java.io.Serializable {

private static final long serialVersionUID = 8849118657896662369L;

public static final class KeyFromClasspath extends Option {

private static final long serialVersionUID = -5506049413185246821L;

private final boolean keyFromClasspath;

public KeyFromClasspath(boolean keyFromClasspath) {
this.keyFromClasspath = keyFromClasspath;
}

public boolean keyFromClasspath() {
return keyFromClasspath;
}
}

Option() {
// package protected
}

public static KeyFromClasspath keyFromClassPath() {
return new KeyFromClasspath(true);
}

static Map<Class<? extends Option>, Option> asImmutableMap(Option... options) {
ImmutableMap.Builder<Class<? extends Option>, Option> builder = ImmutableMap.builder();
for (Option option : options) {
builder.put(option.getClass(), option);
}
return builder.build();
}
}

public static class GcsHelperException extends RuntimeException {

private static final long serialVersionUID = -7756074894502258736L;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
/*
* Copyright 2015 Google Inc. All Rights Reserved.
*
* 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
*
* http://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 com.google.gcloud.storage;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

import com.google.common.collect.ImmutableList;
import com.google.gcloud.storage.testing.RemoteGcsHelper;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

import org.easymock.EasyMock;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;

public class RemoteGcsHelperTest {

private static final String BUCKET_NAME = "bucket-name";
private static final String PROJECT_ID = "project-id";
private static final String JSON_KEY = "{\n"
+ " \"private_key_id\": \"somekeyid\",\n"
+ " \"private_key\": \"-----BEGIN PRIVATE KEY-----\\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggS"
+ "kAgEAAoIBAQC+K2hSuFpAdrJI\\nnCgcDz2M7t7bjdlsadsasad+fvRSW6TjNQZ3p5LLQY1kSZRqBqylRkzteMOyHg"
+ "aR\\n0Pmxh3ILCND5men43j3h4eDbrhQBuxfEMalkG92sL+PNQSETY2tnvXryOvmBRwa/\\nQP/9dJfIkIDJ9Fw9N4"
+ "Bhhhp6mCcRpdQjV38H7JsyJ7lih/oNjECgYAt\\nknddadwkwewcVxHFhcZJO+XWf6ofLUXpRwiTZakGMn8EE1uVa2"
+ "LgczOjwWHGi99MFjxSer5m9\\n1tCa3/KEGKiS/YL71JvjwX3mb+cewlkcmweBKZHM2JPTk0ZednFSpVZMtycjkbLa"
+ "\\ndYOS8V85AgMBewECggEBAKksaldajfDZDV6nGqbFjMiizAKJolr/M3OQw16K6o3/\\n0S31xIe3sSlgW0+UbYlF"
+ "4U8KifhManD1apVSC3csafaspP4RZUHFhtBywLO9pR5c\\nr6S5aLp+gPWFyIp1pfXbWGvc5VY/v9x7ya1VEa6rXvL"
+ "sKupSeWAW4tMj3eo/64ge\\nsdaceaLYw52KeBYiT6+vpsnYrEkAHO1fF/LavbLLOFJmFTMxmsNaG0tuiJHgjshB\\"
+ "n82DpMCbXG9YcCgI/DbzuIjsdj2JC1cascSP//3PmefWysucBQe7Jryb6NQtASmnv\\nCdDw/0jmZTEjpe4S1lxfHp"
+ "lAhHFtdgYTvyYtaLZiVVkCgYEA8eVpof2rceecw/I6\\n5ng1q3Hl2usdWV/4mZMvR0fOemacLLfocX6IYxT1zA1FF"
+ "JlbXSRsJMf/Qq39mOR2\\nSpW+hr4jCoHeRVYLgsbggtrevGmILAlNoqCMpGZ6vDmJpq6ECV9olliDvpPgWOP+\\nm"
+ "YPDreFBGxWvQrADNbRt2dmGsrsCgYEAyUHqB2wvJHFqdmeBsaacewzV8x9WgmeX\\ngUIi9REwXlGDW0Mz50dxpxcK"
+ "CAYn65+7TCnY5O/jmL0VRxU1J2mSWyWTo1C+17L0\\n3fUqjxL1pkefwecxwecvC+gFFYdJ4CQ/MHHXU81Lwl1iWdF"
+ "Cd2UoGddYaOF+KNeM\\nHC7cmqra+JsCgYEAlUNywzq8nUg7282E+uICfCB0LfwejuymR93CtsFgb7cRd6ak\\nECR"
+ "8FGfCpH8ruWJINllbQfcHVCX47ndLZwqv3oVFKh6pAS/vVI4dpOepP8++7y1u\\ncoOvtreXCX6XqfrWDtKIvv0vjl"
+ "HBhhhp6mCcRpdQjV38H7JsyJ7lih/oNjECgYAt\\nkndj5uNl5SiuVxHFhcZJO+XWf6ofLUregtevZakGMn8EE1uVa"
+ "2AY7eafmoU/nZPT\\n00YB0TBATdCbn/nBSuKDESkhSg9s2GEKQZG5hBmL5uCMfo09z3SfxZIhJdlerreP\\nJ7gSi"
+ "dI12N+EZxYd4xIJh/HFDgp7RRO87f+WJkofMQKBgGTnClK1VMaCRbJZPriw\\nEfeFCoOX75MxKwXs6xgrw4W//AYG"
+ "GUjDt83lD6AZP6tws7gJ2IwY/qP7+lyhjEqN\\nHtfPZRGFkGZsdaksdlaksd323423d+15/UvrlRSFPNj1tWQmNKk"
+ "XyRDW4IG1Oa2p\\nrALStNBx5Y9t0/LQnFI4w3aG\\n-----END PRIVATE KEY-----\\n\",\n"
+ " \"client_email\": \"[email protected]\",\n"
+ " \"client_id\": \"someclientid.apps.googleusercontent.com\",\n"
+ " \"type\": \"service_account\"\n"
+ "}";
private static final InputStream JSON_KEY_STREAM = new ByteArrayInputStream(JSON_KEY.getBytes());
private static final List<BlobInfo> BLOB_LIST = ImmutableList.of(
BlobInfo.builder(BUCKET_NAME, "n1").build(),
BlobInfo.builder(BUCKET_NAME, "n2").build());
private static final StorageException RETRYABLE_EXCEPTION = new StorageException(409, "", true);
private static final StorageException FATAL_EXCEPTION = new StorageException(500, "", false);
private static final ListResult<BlobInfo> BLOB_LIST_RESULT = new ListResult<BlobInfo>() {

@Override
public String nextPageCursor() {
return "listResult";
}

@Override
public ListResult<BlobInfo> nextPage() {
return null;
}

@Override
public Iterator<BlobInfo> iterator() {
return BLOB_LIST.iterator();
}
};
private static String KEY_PATH = "/does/not/exist/key." + UUID.randomUUID().toString() + ".json";

@Rule
public ExpectedException thrown = ExpectedException.none();

@BeforeClass
public static void beforeClass() {
while (Files.exists(Paths.get(JSON_KEY))) {
KEY_PATH = "/does/not/exist/key." + UUID.randomUUID().toString() + ".json";
}
}

@Test
public void testForceDelete() throws InterruptedException, ExecutionException {
Storage storageMock = EasyMock.createMock(Storage.class);
EasyMock.expect(storageMock.list(BUCKET_NAME)).andReturn(BLOB_LIST_RESULT);
for (BlobInfo info : BLOB_LIST) {
EasyMock.expect(storageMock.delete(BUCKET_NAME, info.name())).andReturn(true);
}
EasyMock.expect(storageMock.delete(BUCKET_NAME)).andReturn(true);
EasyMock.replay(storageMock);
assertTrue(RemoteGcsHelper.forceDelete(storageMock, BUCKET_NAME, 5, TimeUnit.SECONDS));
EasyMock.verify(storageMock);
}

@Test
public void testForceDeleteTimeout() throws InterruptedException, ExecutionException {
Storage storageMock = EasyMock.createMock(Storage.class);
EasyMock.expect(storageMock.list(BUCKET_NAME)).andReturn(BLOB_LIST_RESULT).anyTimes();
for (BlobInfo info : BLOB_LIST) {
EasyMock.expect(storageMock.delete(BUCKET_NAME, info.name())).andReturn(true).anyTimes();
}
EasyMock.expect(storageMock.delete(BUCKET_NAME)).andThrow(RETRYABLE_EXCEPTION).anyTimes();
EasyMock.replay(storageMock);
assertTrue(!RemoteGcsHelper.forceDelete(storageMock, BUCKET_NAME, 50, TimeUnit.MICROSECONDS));
EasyMock.verify(storageMock);
}

@Test
public void testForceDeleteFail() throws InterruptedException, ExecutionException {
Storage storageMock = EasyMock.createMock(Storage.class);
EasyMock.expect(storageMock.list(BUCKET_NAME)).andReturn(BLOB_LIST_RESULT);
for (BlobInfo info : BLOB_LIST) {
EasyMock.expect(storageMock.delete(BUCKET_NAME, info.name())).andReturn(true);
}
EasyMock.expect(storageMock.delete(BUCKET_NAME)).andThrow(FATAL_EXCEPTION);
EasyMock.replay(storageMock);
thrown.expect(ExecutionException.class);
try {
RemoteGcsHelper.forceDelete(storageMock, BUCKET_NAME, 5, TimeUnit.SECONDS);
} finally {
EasyMock.verify(storageMock);
}
}

@Test
public void testCreateFromStream() {
RemoteGcsHelper helper = RemoteGcsHelper.create(PROJECT_ID, JSON_KEY_STREAM);
StorageOptions options = helper.options();
assertEquals(PROJECT_ID, options.projectId());
assertEquals(60000, options.connectTimeout());
assertEquals(60000, options.readTimeout());
assertEquals(10, options.retryParams().getRetryMaxAttempts());
assertEquals(6, options.retryParams().getRetryMinAttempts());
assertEquals(30000, options.retryParams().getMaxRetryDelayMillis());
assertEquals(120000, options.retryParams().getTotalRetryPeriodMillis());
assertEquals(250, options.retryParams().getInitialRetryDelayMillis());
}

@Test
public void testCreateNoKey() {
thrown.expect(RemoteGcsHelper.GcsHelperException.class);
thrown.expectMessage(KEY_PATH + " (No such file or directory)");
RemoteGcsHelper.create(PROJECT_ID, KEY_PATH);
}
}

0 comments on commit 0dd3d97

Please sign in to comment.