Repository: googleapis/java-storage
Pull request #1434 (2022-06-06 21:37:44)

fix: update request method of HttpStorageRpc to properly configure offset on requests
When invoking downloadTo(..., OutputStream) if a retry was attempted the proper byte
offset was not being sent in the retried request. Update logic of HttpStorageRpc.read
to manually set the range header rather than trying to rely on MediaDownloader to do
it along with not automatically decompressing the byte stream.

Update ITRetryConformanceTest to run Scenario 8 test cases, which cover resuming a
download which could have caught this error sooner.

Update StorageException.translate(IOException) to classify `IOException: Premature EOF`
as the existing retryable reason `connectionClosedPrematurely`. Add case to
DefaultRetryHandlingBehaviorTest to ensure conformance to this categorization.

Break downloadTo integration test out into their own class, and separate
the multiple scenarios being tested in the same method.

Related to https://github.com/googleapis/java-storage/issues/1425


Patch:
diff --git a/google-cloud-storage/pom.xml b/google-cloud-storage/pom.xml
index 3aeb684b..b522b6db 100644
--- a/google-cloud-storage/pom.xml
+++ b/google-cloud-storage/pom.xml
@@ -16,7 +16,7 @@
   </parent>
   <properties>
     <site.installationModule>google-cloud-storage</site.installationModule>
-    <kms.version>0.96.1</kms.version>
+    <kms.version>0.96.2</kms.version>
   </properties>
   <dependencies>
     <dependency>
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageException.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageException.java
index 194c9d21..cb718e35 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageException.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageException.java
@@ -108,9 +108,11 @@ public final class StorageException extends BaseHttpServiceException {
    * @returns {@code StorageException}
    */
   public static StorageException translate(IOException exception) {
-    if (exception.getMessage().contains("Connection closed prematurely")) {
-      return new StorageException(
-          0, exception.getMessage(), CONNECTION_CLOSED_PREMATURELY, exception);
+    String message = exception.getMessage();
+    if (message != null
+        && (message.contains("Connection closed prematurely")
+            || message.contains("Premature EOF"))) {
+      return new StorageException(0, message, CONNECTION_CLOSED_PREMATURELY, exception);
     } else {
       // default
       return new StorageException(exception);
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpc.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpc.java
index 70f34932..05086abc 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpc.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpc.java
@@ -24,6 +24,7 @@ import static java.net.HttpURLConnection.HTTP_NOT_FOUND;
 import com.google.api.client.googleapis.batch.BatchRequest;
 import com.google.api.client.googleapis.batch.json.JsonBatchCallback;
 import com.google.api.client.googleapis.json.GoogleJsonError;
+import com.google.api.client.googleapis.media.MediaHttpDownloader;
 import com.google.api.client.http.ByteArrayContent;
 import com.google.api.client.http.EmptyContent;
 import com.google.api.client.http.GenericUrl;
@@ -283,7 +284,7 @@ public class HttpStorageRpc implements StorageRpc {
   }
 
   private static StorageException translate(IOException exception) {
-    return new StorageException(exception);
+    return StorageException.translate(exception);
   }
 
   private static StorageException translate(GoogleJsonError exception) {
@@ -750,10 +751,14 @@ public class HttpStorageRpc implements StorageRpc {
       } else {
         req.setReturnRawInputStream(false);
       }
-      req.getMediaHttpDownloader().setBytesDownloaded(position);
-      req.getMediaHttpDownloader().setDirectDownloadEnabled(true);
+
+      if (position > 0) {
+        req.getRequestHeaders().setRange(String.format("bytes=%d-", position));
+      }
+      MediaHttpDownloader mediaHttpDownloader = req.getMediaHttpDownloader();
+      mediaHttpDownloader.setDirectDownloadEnabled(true);
       req.executeMedia().download(outputStream);
-      return req.getMediaHttpDownloader().getNumBytesDownloaded();
+      return mediaHttpDownloader.getNumBytesDownloaded();
     } catch (IOException ex) {
       span.setStatus(Status.UNKNOWN.withDescription(ex.getMessage()));
       StorageException serviceException = translate(ex);
diff --git a/pom.xml b/pom.xml
index 3a2b6333..a159a2ba 100644
--- a/pom.xml
+++ b/pom.xml
@@ -75,12 +75,12 @@
       <dependency>
         <groupId>com.google.apis</groupId>
         <artifactId>google-api-services-storage</artifactId>
-        <version>v1-rev20220509-1.32.1</version>
+        <version>v1-rev20220604-1.32.1</version>
       </dependency>
       <dependency>
         <groupId>com.google.cloud</groupId>
         <artifactId>google-cloud-pubsub</artifactId>
-        <version>1.119.0</version>
+        <version>1.119.1</version>
         <scope>test</scope>
       </dependency>
       <dependency>
diff --git a/samples/install-without-bom/pom.xml b/samples/install-without-bom/pom.xml
index 23aa8374..eab0f915 100644
--- a/samples/install-without-bom/pom.xml
+++ b/samples/install-without-bom/pom.xml
@@ -61,7 +61,7 @@
     <dependency>
       <groupId>com.google.cloud</groupId>
       <artifactId>google-cloud-pubsub</artifactId>
-      <version>1.119.0</version>
+      <version>1.119.1</version>
       <scope>test</scope>
     </dependency>
   </dependencies>
diff --git a/samples/native-image-sample/pom.xml b/samples/native-image-sample/pom.xml
index f54c8817..40711930 100644
--- a/samples/native-image-sample/pom.xml
+++ b/samples/native-image-sample/pom.xml
@@ -61,7 +61,7 @@
     <dependency>
       <groupId>com.google.cloud</groupId>
       <artifactId>google-cloud-pubsub</artifactId>
-      <version>1.119.0</version>
+      <version>1.119.1</version>
       <scope>test</scope>
     </dependency>
   </dependencies>
diff --git a/samples/snapshot/pom.xml b/samples/snapshot/pom.xml
index 9cdb5ead..930c62eb 100644
--- a/samples/snapshot/pom.xml
+++ b/samples/snapshot/pom.xml
@@ -52,7 +52,7 @@
     <dependency>
       <groupId>com.google.cloud</groupId>
       <artifactId>google-cloud-pubsub</artifactId>
-      <version>1.119.0</version>
+      <version>1.119.1</version>
       <scope>test</scope>
     </dependency>
   </dependencies>
diff --git a/samples/snippets/pom.xml b/samples/snippets/pom.xml
index 58bc0cd2..09c165e4 100644
--- a/samples/snippets/pom.xml
+++ b/samples/snippets/pom.xml
@@ -72,7 +72,7 @@
     <dependency>
       <groupId>com.google.cloud</groupId>
       <artifactId>google-cloud-pubsub</artifactId>
-      <version>1.119.0</version>
+      <version>1.119.1</version>
       <scope>test</scope>
     </dependency>
   </dependencies>


Test patch:
diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/DefaultRetryHandlingBehaviorTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/DefaultRetryHandlingBehaviorTest.java
index ad5653cf..587577a5 100644
--- a/google-cloud-storage/src/test/java/com/google/cloud/storage/DefaultRetryHandlingBehaviorTest.java
+++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/DefaultRetryHandlingBehaviorTest.java
@@ -19,6 +19,7 @@ package com.google.cloud.storage;
 import static com.google.common.collect.ImmutableList.toImmutableList;
 import static com.google.common.collect.ImmutableSet.toImmutableSet;
 import static com.google.common.truth.Truth.assertWithMessage;
+import static org.junit.Assert.fail;
 
 import com.fasterxml.jackson.core.JsonToken;
 import com.fasterxml.jackson.core.io.JsonEOFException;
@@ -96,6 +97,7 @@ public final class DefaultRetryHandlingBehaviorTest {
     } else if (shouldRetry && !defaultShouldRetryResult && !legacyShouldRetryResult) {
       actualBehavior = Behavior.SAME;
       message = "both are rejecting when we want a retry";
+      fail(message);
     } else if (shouldRetry && defaultShouldRetryResult && legacyShouldRetryResult) {
       actualBehavior = Behavior.SAME;
       message = "both are allowing";
@@ -111,6 +113,7 @@ public final class DefaultRetryHandlingBehaviorTest {
     } else if (!shouldRetry && defaultShouldRetryResult && legacyShouldRetryResult) {
       actualBehavior = Behavior.SAME;
       message = "both are too permissive";
+      fail(message);
     } else if (!shouldRetry && defaultShouldRetryResult && !legacyShouldRetryResult) {
       actualBehavior = Behavior.DEFAULT_MORE_PERMISSIBLE;
       message = "default is too permissive";
@@ -298,7 +301,7 @@ public final class DefaultRetryHandlingBehaviorTest {
     STORAGE_EXCEPTION_GOOGLE_JSON_ERROR_503(new StorageException(C.JSON_503)),
     STORAGE_EXCEPTION_GOOGLE_JSON_ERROR_504(new StorageException(C.JSON_504)),
     STORAGE_EXCEPTION_SOCKET_TIMEOUT_EXCEPTION(new StorageException(C.SOCKET_TIMEOUT_EXCEPTION)),
-    STORAGE_EXCEPTION_SOCKET_EXCEPTION(new StorageException(C.SOCKET_EXCEPTION)),
+    STORAGE_EXCEPTION_SOCKET_EXCEPTION(StorageException.translate(C.SOCKET_EXCEPTION)),
     STORAGE_EXCEPTION_SSL_EXCEPTION(new StorageException(C.SSL_EXCEPTION)),
     STORAGE_EXCEPTION_SSL_EXCEPTION_CONNECTION_SHUTDOWN(
         new StorageException(C.SSL_EXCEPTION_CONNECTION_SHUTDOWN)),
@@ -322,6 +325,9 @@ public final class DefaultRetryHandlingBehaviorTest {
             "connectionClosedPrematurely",
             "connectionClosedPrematurely",
             C.CONNECTION_CLOSED_PREMATURELY)),
+    STORAGE_EXCEPTION_0_CONNECTION_CLOSED_PREMATURELY_IO_CAUSE_NO_REASON(
+        StorageException.translate(C.CONNECTION_CLOSED_PREMATURELY)),
+    STORAGE_EXCEPTION_0_IO_PREMATURE_EOF(StorageException.translate(C.IO_PREMATURE_EOF)),
     EMPTY_JSON_PARSE_ERROR(new IllegalArgumentException("no JSON input found")),
     JACKSON_EOF_EXCEPTION(C.JACKSON_EOF_EXCEPTION),
     STORAGE_EXCEPTION_0_JACKSON_EOF_EXCEPTION(
@@ -400,6 +406,7 @@ public final class DefaultRetryHandlingBehaviorTest {
           new JsonEOFException(null, JsonToken.VALUE_STRING, "parse-exception");
       private static final MalformedJsonException GSON_MALFORMED_EXCEPTION =
           new MalformedJsonException("parse-exception");
+      private static final IOException IO_PREMATURE_EOF = new IOException("Premature EOF");
 
       private static HttpResponseException newHttpResponseException(
           int httpStatusCode, String name) {
@@ -919,6 +926,28 @@ public final class DefaultRetryHandlingBehaviorTest {
                 HandlerCategory.NONIDEMPOTENT,
                 ExpectRetry.NO,
                 Behavior.DEFAULT_MORE_STRICT),
+            new Case(
+                ThrowableCategory
+                    .STORAGE_EXCEPTION_0_CONNECTION_CLOSED_PREMATURELY_IO_CAUSE_NO_REASON,
+                HandlerCategory.IDEMPOTENT,
+                ExpectRetry.YES,
+                Behavior.SAME),
+            new Case(
+                ThrowableCategory
+                    .STORAGE_EXCEPTION_0_CONNECTION_CLOSED_PREMATURELY_IO_CAUSE_NO_REASON,
+                HandlerCategory.NONIDEMPOTENT,
+                ExpectRetry.NO,
+                Behavior.DEFAULT_MORE_STRICT),
+            new Case(
+                ThrowableCategory.STORAGE_EXCEPTION_0_IO_PREMATURE_EOF,
+                HandlerCategory.IDEMPOTENT,
+                ExpectRetry.YES,
+                Behavior.SAME),
+            new Case(
+                ThrowableCategory.STORAGE_EXCEPTION_0_IO_PREMATURE_EOF,
+                HandlerCategory.NONIDEMPOTENT,
+                ExpectRetry.NO,
+                Behavior.DEFAULT_MORE_STRICT),
             new Case(
                 ThrowableCategory.STORAGE_EXCEPTION_0_INTERNAL_ERROR,
                 HandlerCategory.IDEMPOTENT,
diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/conformance/retry/ITRetryConformanceTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/conformance/retry/ITRetryConformanceTest.java
index b713a222..8b584778 100644
--- a/google-cloud-storage/src/test/java/com/google/cloud/storage/conformance/retry/ITRetryConformanceTest.java
+++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/conformance/retry/ITRetryConformanceTest.java
@@ -154,7 +154,7 @@ public class ITRetryConformanceTest {
                     .and(
                         (m, trc) ->
                             trc.getScenarioId()
-                                < 7) // Temporarily exclude resumable media scenarios
+                                != 7) // Temporarily exclude resumable upload scenarios
                 )
             .build();
 
diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/conformance/retry/RpcMethodMappings.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/conformance/retry/RpcMethodMappings.java
index 668fa902..259c2a7d 100644
--- a/google-cloud-storage/src/test/java/com/google/cloud/storage/conformance/retry/RpcMethodMappings.java
+++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/conformance/retry/RpcMethodMappings.java
@@ -18,6 +18,7 @@ package com.google.cloud.storage.conformance.retry;
 
 import static com.google.cloud.storage.conformance.retry.CtxFunctions.ResourceSetup.defaultSetup;
 import static com.google.cloud.storage.conformance.retry.CtxFunctions.ResourceSetup.serviceAccount;
+import static com.google.common.base.Predicates.and;
 import static com.google.common.base.Predicates.not;
 import static com.google.common.truth.Truth.assertThat;
 import static org.junit.Assert.assertTrue;
@@ -66,6 +67,7 @@ import com.google.cloud.storage.conformance.retry.RpcMethodMappings.Mappings.Not
 import com.google.cloud.storage.conformance.retry.RpcMethodMappings.Mappings.ObjectAcl;
 import com.google.cloud.storage.conformance.retry.RpcMethodMappings.Mappings.Objects;
 import com.google.cloud.storage.conformance.retry.RpcMethodMappings.Mappings.ServiceAccount;
+import com.google.common.base.Predicate;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ListMultimap;
@@ -108,6 +110,11 @@ import java.util.stream.Collectors;
 final class RpcMethodMappings {
   private static final Logger LOGGER = Logger.getLogger(RpcMethodMappings.class.getName());
 
+  private static final Predicate<TestRetryConformance> groupIsDownload =
+      methodGroupIs("storage.objects.download");
+  private static final Predicate<TestRetryConformance> groupIsResumableUpload =
+      methodGroupIs("storage.resumable.upload");
+
   static final int _2MiB = 2 * 1024 * 1024;
   final Multimap<RpcMethod, RpcMethodMapping> funcMap;
 
@@ -1079,7 +1086,8 @@ final class RpcMethodMappings {
       private static void get(ArrayList<RpcMethodMapping> a) {
         a.add(
             RpcMethodMapping.newBuilder(39, objects.get)
-                .withApplicable(not(TestRetryConformance::isPreconditionsProvided))
+                .withApplicable(
+                    and(not(TestRetryConformance::isPreconditionsProvided), not(groupIsDownload)))
                 .withSetup(defaultSetup.andThen(Local.blobInfoWithoutGeneration))
                 .withTest(
                     (ctx, c) ->
@@ -1088,13 +1096,15 @@ final class RpcMethodMappings {
                 .build());
         a.add(
             RpcMethodMapping.newBuilder(239, objects.get)
-                .withApplicable(TestRetryConformance::isPreconditionsProvided)
+                .withApplicable(
+                    and(TestRetryConformance::isPreconditionsProvided, not(groupIsDownload)))
                 .withTest(
                     (ctx, c) ->
                         ctx.peek(state -> ctx.getStorage().get(state.getBlob().getBlobId())))
                 .build());
         a.add(
             RpcMethodMapping.newBuilder(40, objects.get)
+                .withApplicable(not(groupIsDownload))
                 .withTest(
                     (ctx, c) ->
                         ctx.map(
@@ -1108,6 +1118,7 @@ final class RpcMethodMappings {
                 .build());
         a.add(
             RpcMethodMapping.newBuilder(41, objects.get)
+                .withApplicable(not(groupIsDownload))
                 .withTest(
                     (ctx, c) ->
                         ctx.map(
@@ -1196,7 +1207,8 @@ final class RpcMethodMappings {
                 .build());
         a.add(
             RpcMethodMapping.newBuilder(60, objects.get)
-                .withApplicable(not(TestRetryConformance::isPreconditionsProvided))
+                .withApplicable(
+                    and(not(TestRetryConformance::isPreconditionsProvided), not(groupIsDownload)))
                 .withTest((ctx, c) -> ctx.peek(state -> assertTrue(state.getBlob().exists())))
                 .build());
         a.add(
@@ -1297,10 +1309,12 @@ final class RpcMethodMappings {
                 .build());
         a.add(
             RpcMethodMapping.newBuilder(75, objects.get)
+                .withApplicable(not(groupIsDownload))
                 .withTest((ctx, c) -> ctx.peek(state -> state.getBlob().reload()))
                 .build());
         a.add(
             RpcMethodMapping.newBuilder(76, objects.get)
+                .withApplicable(not(groupIsDownload))
                 .withTest(
                     (ctx, c) ->
                         ctx.peek(
@@ -1311,7 +1325,8 @@ final class RpcMethodMappings {
                 .build());
         a.add(
             RpcMethodMapping.newBuilder(107, objects.get)
-                .withApplicable(not(TestRetryConformance::isPreconditionsProvided))
+                .withApplicable(
+                    and(not(TestRetryConformance::isPreconditionsProvided), not(groupIsDownload)))
                 .withTest(
                     (ctx, c) ->
                         ctx.map(state -> state.with(state.getBucket().get(c.getObjectName()))))
@@ -1321,7 +1336,8 @@ final class RpcMethodMappings {
       private static void insert(ArrayList<RpcMethodMapping> a) {
         a.add(
             RpcMethodMapping.newBuilder(46, objects.insert)
-                .withApplicable(TestRetryConformance::isPreconditionsProvided)
+                .withApplicable(
+                    and(TestRetryConformance::isPreconditionsProvided, not(groupIsResumableUpload)))
                 .withSetup(defaultSetup.andThen(Local.blobInfoWithGenerationZero))
                 .withTest(
                     (ctx, c) ->
@@ -1336,7 +1352,8 @@ final class RpcMethodMappings {
                 .build());
         a.add(
             RpcMethodMapping.newBuilder(47, objects.insert)
-                .withApplicable(TestRetryConformance::isPreconditionsProvided)
+                .withApplicable(
+                    and(TestRetryConformance::isPreconditionsProvided, not(groupIsResumableUpload)))
                 .withSetup(defaultSetup.andThen(Local.blobInfoWithGenerationZero))
                 .withTest(
                     (ctx, c) ->
@@ -1932,4 +1949,8 @@ final class RpcMethodMappings {
       private static void put(ArrayList<RpcMethodMapping> a) {}
     }
   }
+
+  private static Predicate<TestRetryConformance> methodGroupIs(String s) {
+    return (c) -> s.equals(c.getMethod().getGroup());
+  }
 }
diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/conformance/retry/TestRetryConformance.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/conformance/retry/TestRetryConformance.java
index b9b8456d..03d67145 100644
--- a/google-cloud-storage/src/test/java/com/google/cloud/storage/conformance/retry/TestRetryConformance.java
+++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/conformance/retry/TestRetryConformance.java
@@ -24,6 +24,7 @@ import com.google.auth.oauth2.ServiceAccountCredentials;
 import com.google.cloud.conformance.storage.v1.InstructionList;
 import com.google.cloud.conformance.storage.v1.Method;
 import com.google.common.base.Joiner;
+import com.google.common.base.Suppliers;
 import com.google.errorprone.annotations.Immutable;
 import java.io.IOException;
 import java.io.InputStream;
@@ -37,7 +38,9 @@ import java.time.Instant;
 import java.time.ZoneId;
 import java.time.ZoneOffset;
 import java.time.format.DateTimeFormatter;
+import java.util.function.Supplier;
 import java.util.stream.Collectors;
+import java.util.stream.IntStream;
 
 /**
  * An individual resolved test case correlating config from {@link
@@ -58,13 +61,16 @@ final class TestRetryConformance {
     BASE_ID = formatter.format(now).replaceAll("[:]", "").substring(0, 6);
   }
 
+  private static final int _512KiB = 512 * 1024;
+  private static final int _8MiB = 8 * 1024 * 1024;
+
   private final String projectId;
   private final String bucketName;
   private final String bucketName2;
   private final String userProject;
   private final String objectName;
 
-  private final byte[] helloWorldUtf8Bytes = "Hello, World!!!".getBytes(StandardCharsets.UTF_8);
+  private final Supplier<byte[]> lazyHelloWorldUtf8Bytes;
   private final Path helloWorldFilePath = resolvePathForResource();
   private final ServiceAccountCredentials serviceAccountCredentials =
       resolveServiceAccountCredentials();
@@ -126,6 +132,33 @@ final class TestRetryConformance {
         String.format(
             "%s_s%03d-%s-m%03d_obj1",
             BASE_ID, scenarioId, instructionsString.toLowerCase(), mappingId);
+    lazyHelloWorldUtf8Bytes =
+        Suppliers.memoize(
+            () -> {
+              // define a lazy supplier for bytes.
+              // Not all tests need data for an object, though some tests - resumable upload - needs
+              // more than 8MiB.
+              // We want to avoid allocating 8.1MiB for each test unnecessarily, especially since we
+              // instantiate all permuted test cases. ~1000 * 8.1MiB ~~ > 8GiB.
+              String helloWorld = "Hello, World!";
+              int baseDataSize;
+              switch (method.getName()) {
+                case "storage.objects.insert":
+                  baseDataSize = _8MiB + 1;
+                  break;
+                case "storage.objects.get":
+                  baseDataSize = _512KiB;
+                  break;
+                default:
+                  baseDataSize = helloWorld.length();
+                  break;
+              }
+              int endInclusive = (baseDataSize / helloWorld.length());
+              return IntStream.rangeClosed(1, endInclusive)
+                  .mapToObj(i -> helloWorld)
+                  .collect(Collectors.joining())
+                  .getBytes(StandardCharsets.UTF_8);
+            });
   }
 
   public String getProjectId() {
@@ -153,7 +186,7 @@ final class TestRetryConformance {
   }
 
   public byte[] getHelloWorldUtf8Bytes() {
-    return helloWorldUtf8Bytes;
+    return lazyHelloWorldUtf8Bytes.get();
   }
 
   public Path getHelloWorldFilePath() {
diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITDownloadToTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITDownloadToTest.java
new file mode 100644
index 00000000..0904c574
--- /dev/null
+++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITDownloadToTest.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2022 Google LLC
+ *
+ * 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.cloud.storage.it;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.fail;
+
+import com.google.cloud.storage.BlobId;
+import com.google.cloud.storage.BlobInfo;
+import com.google.cloud.storage.BucketInfo;
+import com.google.cloud.storage.Storage;
+import com.google.cloud.storage.StorageOptions;
+import com.google.cloud.storage.testing.RemoteStorageHelper;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.zip.GZIPOutputStream;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public final class ITDownloadToTest {
+
+  private static final String BUCKET = RemoteStorageHelper.generateBucketName();
+  private static final byte[] helloWorldTextBytes = "hello world".getBytes();
+  private static final byte[] helloWorldGzipBytes = gzipBytes(helloWorldTextBytes);
+
+  private static Storage storage;
+  private static BlobId blobId;
+
+  @BeforeClass
+  public static void beforeClass() {
+    BucketInfo bucketInfo = BucketInfo.of(BUCKET);
+    blobId = BlobId.of(BUCKET, "zipped_blob");
+
+    BlobInfo blobInfo =
+        BlobInfo.newBuilder(blobId).setContentEncoding("gzip").setContentType("text/plain").build();
+
+    storage = StorageOptions.newBuilder().build().getService();
+    storage.create(bucketInfo);
+    storage.create(blobInfo, helloWorldGzipBytes);
+  }
+
+  @Test
+  public void downloadTo_returnRawInputStream_yes() throws IOException {
+    Path helloWorldTxtGz = File.createTempFile("helloWorld", ".txt.gz").toPath();
+    storage.downloadTo(
+        blobId, helloWorldTxtGz, Storage.BlobSourceOption.shouldReturnRawInputStream(true));
+
+    byte[] actualTxtGzBytes = Files.readAllBytes(helloWorldTxtGz);
+    if (Arrays.equals(actualTxtGzBytes, helloWorldTextBytes)) {
+      fail("expected gzipped bytes, but got un-gzipped bytes");
+    }
+    assertThat(actualTxtGzBytes).isEqualTo(helloWorldGzipBytes);
+  }
+
+  @Test
+  public void downloadTo_returnRawInputStream_no() throws IOException {
+    Path helloWorldTxt = File.createTempFile("helloWorld", ".txt").toPath();
+    storage.downloadTo(
+        blobId, helloWorldTxt, Storage.BlobSourceOption.shouldReturnRawInputStream(false));
+    byte[] actualTxtBytes = Files.readAllBytes(helloWorldTxt);
+    assertThat(actualTxtBytes).isEqualTo(helloWorldTextBytes);
+  }
+
+  private static byte[] gzipBytes(byte[] bytes) {
+    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+    try (OutputStream out = new GZIPOutputStream(byteArrayOutputStream)) {
+      out.write(bytes);
+    } catch (IOException ignore) {
+    }
+
+    return byteArrayOutputStream.toByteArray();
+  }
+}
diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITStorageTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITStorageTest.java
index b1863b38..462b4bbd 100644
--- a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITStorageTest.java
+++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITStorageTest.java
@@ -119,7 +119,6 @@ import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileInputStream;
-import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.lang.reflect.Method;
@@ -127,7 +126,6 @@ import java.net.URL;
 import java.net.URLConnection;
 import java.nio.ByteBuffer;
 import java.nio.file.Files;
-import java.nio.file.Path;
 import java.security.Key;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -147,7 +145,6 @@ import java.util.logging.Level;
 import java.util.logging.Logger;
 import java.util.stream.StreamSupport;
 import java.util.zip.GZIPInputStream;
-import java.util.zip.GZIPOutputStream;
 import javax.crypto.spec.SecretKeySpec;
 import org.apache.http.client.HttpClient;
 import org.apache.http.client.methods.HttpPost;
@@ -906,39 +903,6 @@ public class ITStorageTest {
     }
   }
 
-  @Test
-  public void testGetBlobRawInput() throws IOException {
-    Path file = File.createTempFile("temp", ".txt").toPath();
-    Files.write(file, "hello world".getBytes());
-
-    File gzippedFile = File.createTempFile("temp", ".gz");
-
-    GZIPOutputStream gzipOutputStream = new GZIPOutputStream(new FileOutputStream(gzippedFile));
-    Files.copy(file, gzipOutputStream);
-    gzipOutputStream.close();
-
-    String blobName = "zipped_blob";
-    BlobId blobId = BlobId.of(BUCKET, blobName);
-    BlobInfo blobInfo =
-        BlobInfo.newBuilder(blobId).setContentEncoding("gzip").setContentType("text/plain").build();
-
-    storage.createFrom(blobInfo, gzippedFile.toPath());
-
-    Path rawInputGzippedFile = File.createTempFile("rawinputgzippedfile", ".txt").toPath();
-
-    storage.downloadTo(
-        blobId, rawInputGzippedFile, Storage.BlobSourceOption.shouldReturnRawInputStream(true));
-
-    assertArrayEquals(
-        Files.readAllBytes(gzippedFile.toPath()), Files.readAllBytes(rawInputGzippedFile));
-
-    Path unzippedFile = File.createTempFile("unzippedfile", ".txt").toPath();
-    storage.downloadTo(
-        blobId, unzippedFile, Storage.BlobSourceOption.shouldReturnRawInputStream(false));
-
-    assertArrayEquals("hello world".getBytes(), Files.readAllBytes(unzippedFile));
-  }
-
   @Test(timeout = 5000)
   public void testListBlobsSelectedFields() throws InterruptedException {
     String[] blobNames = {
@@ -3699,25 +3663,6 @@ public class ITStorageTest {
     }
   }
 
-  @Test
-  public void testUploadFromDownloadTo() throws Exception {
-    String blobName = "test-uploadFrom-downloadTo-blob";
-    BlobId blobId = BlobId.of(BUCKET, blobName);
-    BlobInfo blobInfo = BlobInfo.newBuilder(blobId).build();
-
-    Path tempFileFrom = Files.createTempFile("ITStorageTest_", ".tmp");
-    Files.write(tempFileFrom, BLOB_BYTE_CONTENT);
-    Blob blob = storage.createFrom(blobInfo, tempFileFrom);
-    assertEquals(BUCKET, blob.getBucket());
-    assertEquals(blobName, blob.getName());
-    assertEquals(BLOB_BYTE_CONTENT.length, (long) blob.getSize());
-
-    Path tempFileTo = Files.createTempFile("ITStorageTest_", ".tmp");
-    storage.downloadTo(blobId, tempFileTo);
-    byte[] readBytes = Files.readAllBytes(tempFileTo);
-    assertArrayEquals(BLOB_BYTE_CONTENT, readBytes);
-  }
-
   @Test
   public void testUploadWithEncryption() throws Exception {
     String blobName = "test-upload-withEncryption";